npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

yumerize

v0.3.1

Published

Lightweight API documentation & API hitter for Node.js — zero-config, auto-discovers routes, works with Express, NestJS, Fastify

Readme

yumerize

Lightweight API documentation and API hitter for Node.js. Zero-config, auto-discovers your routes, and serves a beautiful dark-themed UI to explore and test your endpoints.

Works with Express, NestJS, Fastify, or any framework via the adapter pattern.

No YAML files. No decorators. No complex setup. Just plug it in and your docs are ready.


Why yumerize?

Setting up Swagger/OpenAPI can be tedious: YAML specs, decorator clutter, manual route registration. yumerize takes a simpler approach:

  • Multi-framework — adapter pattern supports Express, NestJS, Fastify, and custom frameworks
  • Auto-discovery — scans your app and finds every registered route automatically
  • Optional annotations — add yumerize.doc() to any route to enrich it with params, descriptions, and response examples
  • Built-in API hitter — test endpoints directly from the documentation UI in your browser
  • Global auth token — hit a login endpoint once and the token is auto-saved for all subsequent requests
  • Full request builder — params, headers, authorization, and all body types (raw JSON, form-data, x-www-form-urlencoded, binary)
  • Auto-prefilled requests — body, query, path, and header params are automatically filled from the example values in your doc() annotations
  • Response benchmarking — documented example responses (with status codes and bodies) are shown alongside the request builder so you can compare actual responses against the agreed contract
  • Request history — every sent request is logged with full request/response details for review
  • DOCX export — export all documented routes as a formatted Word document with one click
  • Minimal setup — 3 lines of code, no config files

Installation

npm install yumerize

Note: yumerize is framework-agnostic. For Express-based apps, Express 4.18+ or 5.x is required. For NestJS, install the NestJS dependencies as shown in the NestJS section.


Quick Start

import express from "express";
import yumerize from "yumerize";

const app = express();
app.use(express.json());

// 1. Initialize yumerize
const api = yumerize({
  title: "My API",
  version: "1.0.0",
  baseUrl: "http://localhost:3000",
});

// 2. Capture middleware — place BEFORE your routes
app.use(api.capture());

// 3. Define your routes as usual
app.get("/users", (req, res) => {
  res.json([{ id: 1, name: "John" }]);
});

app.post("/users", (req, res) => {
  res.status(201).json({ id: 2, name: req.body.name });
});

// 4. Scan & serve — place AFTER all routes
api.scan(app);
app.use("/docs", api.serve());

app.listen(3000, () => {
  console.log("Server running at http://localhost:3000");
  console.log("Docs at http://localhost:3000/docs");
});

That's it. Open /docs in your browser to see all your routes and test them.


Adding Documentation Details

Use api.doc() as middleware on any route to add rich metadata. This is completely optional — routes without doc() will still appear in the UI.

app.get(
  "/users/:id",
  api.doc({
    method: "GET",
    path: "/users/:id",
    tag: "Users", // Groups routes in the sidebar
    summary: "Get user by ID",
    description: "Returns a single user object by their unique identifier.",
    params: [
      {
        name: "id",
        in: "path", // "path" | "query" | "body" | "header"
        type: "number",
        required: true,
        description: "User ID",
        example: 1,
      },
    ],
    responses: [
      { status: 200, description: "User found", body: { id: 1, name: "John" } },
      { status: 404, description: "User not found" },
    ],
  }),
  (req, res) => {
    res.json({ id: req.params.id, name: "John" });
  },
);

Documentation UI Features

The built-in UI at /docs is a full-featured API client with three sidebar panels:

Collections

Browse all discovered routes grouped by tag. Click any route to open it in a new tab. Method and URL are locked (read-only) since they come from your code, but everything else is editable. When you open a route, the request builder is auto-prefilled from your doc() annotations:

  • Query & path params — filled from each param's example value
  • Headers — filled from header param example values
  • Body — if a body param has an example, the body editor is auto-set to raw JSON with the example pretty-printed, ready to send

Response Benchmarking

When you document example responses in doc() (the responses array), they are shown in the response panel before you send a request. This lets you compare the actual response against the agreed contract:

  • Each documented status code gets its own tab (e.g. 200 User found, 404 Not found)
  • Example bodies are pretty-printed for easy reading
  • A label indicates it is a benchmark example, prompting you to send a real request to compare

Once you click Send, the response panel switches to the actual response data.

Request Builder

Each open request tab has four sections:

  • Params — query and path parameters with key/value/description fields. Checked rows are included in the request.
  • Authorization — choose from multiple auth types:
    • No Auth — no authentication header sent
    • Inherit Token — uses the global token (see below), so you set it once and reuse everywhere
    • Bearer Token — per-request token
    • Basic Auth — username/password encoded as Base64
    • API Key — custom header key/value
  • Headers — custom request headers with enable/disable toggles
  • Body — full body editor supporting:
    • none — no body
    • form-data — multipart key-value fields
    • x-www-form-urlencoded — URL-encoded fields
    • raw — raw body with format selector (JSON, XML, Text, HTML, JavaScript)
    • binary — file upload

Global Token

The Globals sidebar tab lets you manage a shared Bearer token:

  1. Hit your login endpoint with auth set to Inherit Token
  2. If the response (2xx) contains a token field (access_token, accessToken, token, authToken, or id_token, including nested data.*), it is auto-extracted and saved
  3. All subsequent requests with Inherit Token auth automatically include it

You can also paste a token manually in the Globals tab or clear it anytime. The token persists in localStorage across browser sessions.

History

Every sent request is logged in the History sidebar tab with a complete read-only detail view showing the full request config (params, headers, auth, body) and response (status, time, size, headers, body). Click any history item to review it. Use the Clear History button to wipe all entries.

DOCX Export

Click the Export DOCX button in the top bar to download a formatted Word document (.docx) of all your documented routes. The document includes:

  • Title page with API name, description, version, and base URL
  • Table of contents grouped by tag
  • Per endpoint: HTTP method (color-coded), path, summary, description, parameters table, example request body, and all documented responses with example bodies

The export is served from the same docs path, so no extra setup is needed. You can also trigger it programmatically:

app.use("/docs", api.serve()); // serves UI + handles /docs/export.docx

// Or as a standalone route
app.use("/export.docx", api.exportDocx());

You can also access the export directly via GET /docs/export.docx.


Configuration

Pass a config object when initializing yumerize(). All fields are optional:

const api = yumerize({
  title: "My API", // Documentation title          (default: "Yumerize API Docs")
  version: "2.0.0", // API version                  (default: "1.0.0")
  description: "My API...", // Short description            (default: "")
  baseUrl: "http://...", // Base URL shown in the UI     (default: "")
  path: "/docs", // Docs route path (informational only)
  adapter: new MyAdapter(), // Framework adapter            (default: ExpressAdapter)
});

If no adapter is provided, yumerize defaults to ExpressAdapter for backward compatibility.


API Reference

yumerize(options?)

Initializes yumerize. Accepts a config object with an optional adapter field. Returns a YumerizeInstance.

// Express (default adapter)
const api = yumerize({ title: "My API" });

// NestJS
import { NestJSAdapter } from "yumerize/adapters";
const api = yumerize({ title: "My API", adapter: new NestJSAdapter() });

// Custom
const api = yumerize({ title: "My API", adapter: new MyAdapter() });

api.capture()

Returns middleware that captures response data. Must be placed before your routes.

app.use(api.capture());

api.doc(meta)

Returns route middleware that annotates the endpoint with documentation metadata. Place it before your handler.

YumerizeEndpointMeta fields:

| Field | Type | Required | Description | | ------------- | ------------------------ | -------- | --------------------------------- | | method | string | Yes | HTTP method (GET, POST, etc.) | | path | string | Yes | Route path (/users/:id) | | tag | string | No | Group name shown in the sidebar | | summary | string | No | Short one-line description | | description | string | No | Longer explanation | | params | YumerizeParam[] | No | Parameter definitions | | responses | YumerizeResponse[] | No | Expected response examples | | headers | Record<string, string> | No | Custom response headers |

YumerizeParam fields:

| Field | Type | Required | Description | | ------------- | --------- | -------- | --------------------------------------------------- | | name | string | Yes | Parameter name | | in | string | Yes | Location: "query", "body", "path", "header" | | type | string | Yes | Value type (string, number, boolean, etc.) | | required | boolean | No | Whether the parameter is required | | description | string | No | What the parameter does | | example | any | No | Example value shown in the UI |

YumerizeResponse fields:

| Field | Type | Required | Description | | ------------- | -------- | -------- | ---------------------------- | | status | number | Yes | HTTP status code | | description | string | No | Description of this response | | body | any | No | Example response body |

api.scan(app)

Scans the app to discover all registered routes. Call after all routes are defined. The scanning behavior depends on the adapter:

  • Express/NestJS: walks the Express router tree (app._router.stack)
  • Custom adapters: uses whatever mechanism the adapter implements
api.scan(app);

api.serve()

Returns middleware that serves the documentation UI as HTML. Also intercepts requests to /export.docx within the mounted path to serve the DOCX export automatically.

app.use("/docs", api.serve());

api.exportDocx()

Returns middleware that responds with a DOCX file containing all documented routes.

// As a standalone route
app.use("/export.docx", api.exportDocx());

// Or via the serve middleware (handled automatically)
app.use("/docs", api.serve());
// GET /docs/export.docx triggers the export

api.getRoutes()

Returns the array of all discovered/annotated routes. Useful for testing or logging.

console.log(api.getRoutes());
// [ { method: "GET", fullPath: "/users", tag: "Users", ... }, ... ]

Using with Express Router

yumerize works seamlessly with express.Router(). Define your routes in separate files and yumerize will discover them all.

routes/users.ts

import { Router, Request, Response } from "express";

const router = Router();

router.get("/", (req: Request, res: Response) => {
  res.json([{ id: 1, name: "John" }]);
});

router.post("/", (req: Request, res: Response) => {
  res.status(201).json({ id: 2, name: req.body.name });
});

router.get("/:id", (req: Request, res: Response) => {
  res.json({ id: req.params.id, name: "John" });
});

export default router;

app.ts

import express from "express";
import yumerize from "yumerize";
import usersRoute from "./routes/users";

const app = express();
app.use(express.json());

const api = yumerize({ title: "My API", baseUrl: "http://localhost:3000" });

app.use(api.capture());

// Mount routers
app.use("/users", usersRoute);

// Scan & serve
api.scan(app);
app.use("/docs", api.serve());

app.listen(3000);

Routes will appear as GET /users, POST /users, GET /users/:id in the documentation.


Using with NestJS

NestJS runs on top of Express (or Fastify) by default. yumerize's NestJSAdapter accesses the underlying HTTP adapter to discover routes automatically.

// main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import yumerize from "yumerize";
import { NestJSAdapter } from "yumerize/adapters";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const api = yumerize({
    title: "My NestJS API",
    baseUrl: "http://localhost:3000",
    adapter: new NestJSAdapter(),
  });

  app.use(api.capture()); // Capture responses
  api.scan(app); // Auto-discover NestJS routes
  app.use("/docs", api.serve());

  await app.listen(3000);
}
bootstrap();

Your NestJS controllers (@Get, @Post, etc.) will be auto-discovered and appear in the docs UI.


Using with Fastify (Custom Adapter)

Fastify has a different internal architecture, so you implement the FrameworkAdapter interface. This gives you full control:

import Fastify from "fastify";
import yumerize from "yumerize";
import { FrameworkAdapter } from "yumerize/adapters";
import { renderHtml } from "yumerize";
import { upsertRoute } from "yumerize";

class FastifyAdapter implements FrameworkAdapter {
  readonly name = "fastify";

  scan(_app: unknown): void {
    // Fastify routes must be registered manually via route()
  }

  route(meta: YumerizeEndpointMeta): void {
    upsertRoute({ ...meta, fullPath: meta.path });
  }

  capture() {
    return (_req: unknown, _res: unknown, next?: unknown) => {
      if (typeof next === "function") (next as () => void)();
    };
  }

  doc(meta: YumerizeEndpointMeta) {
    this.route(meta);
    return [
      (_req: unknown, _res: unknown, next: unknown) => {
        if (typeof next === "function") (next as () => void)();
      },
    ];
  }

  serve() {
    return (_req: unknown, res: any) => {
      res.header("Content-Type", "text/html; charset=utf-8");
      res.send(renderHtml());
    };
  }

  exportDocx() {
    return async (_req: unknown, res: any) => {
      const buffer = await exportDocx();
      res.header("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
      res.send(buffer);
    };
  }
}

const fastify = Fastify();
const api = yumerize({ adapter: new FastifyAdapter() });

fastify.get("/users", async (_req, reply) => {
  api.doc({
    method: "GET",
    path: "/users",
    tag: "Users",
    summary: "List users",
  });
  reply.send([{ id: 1, name: "John" }]);
});

api.scan(fastify);
fastify.use("/docs", api.serve());

fastify.listen({ port: 3000 });

See example/fastify/server.ts for a complete working example.


Creating a Custom Adapter

To support a framework that yumerize doesn't have a built-in adapter for, implement the FrameworkAdapter interface:

import yumerize from "yumerize";
import { FrameworkAdapter } from "yumerize/adapters";

class MyAdapter implements FrameworkAdapter {
  readonly name = "my-framework";

  scan(app: unknown): void {
    // Walk your framework's router tree and call upsertRoute() for each route
  }

  capture() {
    // Return middleware that intercepts responses
    return (req, res, next) => {
      /* ... */ next();
    };
  }

  doc(meta) {
    // Return middleware that registers route metadata
    return [
      (req, res, next) => {
        /* register meta */ next();
      },
    ];
  }

  serve() {
    // Return middleware that serves the HTML docs
    return (req, res) => {
      res.html(renderHtml());
    };
  }

  exportDocx() {
    // Return middleware that serves the DOCX export
    return async (req, res) => {
      const buffer = await exportDocx();
      res.send(buffer);
    };
  }
}

const api = yumerize({ adapter: new MyAdapter() });

TypeScript

yumerize is written in TypeScript and ships with type declarations. All types are auto-resolved when you import the package.

import yumerize, {
  type YumerizeInstance,
  type YumerizeConfig,
  type YumerizeEndpointMeta,
} from "yumerize";
import {
  ExpressAdapter,
  NestJSAdapter,
  type FrameworkAdapter,
} from "yumerize/adapters";

// Express (default)
const api: YumerizeInstance = yumerize({
  title: "My API",
  version: "1.0.0",
});

// Or with an explicit adapter
const api2: YumerizeInstance = yumerize({
  title: "My NestJS API",
  adapter: new NestJSAdapter(),
});

// Full type safety on doc metadata
api.doc({
  method: "GET",
  path: "/users",
  params: [
    {
      name: "page",
      in: "query", // autocompletes: "query" | "body" | "path" | "header"
      type: "number",
      required: false,
    },
  ],
});

Examples

Working examples are included for multiple frameworks:

Express:

npm install
npm run build
npx ts-node example/server.ts
# Open http://localhost:3213/docs

NestJS:

cd example/nestjs
npm install
npm start
# Open http://localhost:3214/docs

Fastify (custom adapter):

cd example/fastify
npm install
npm start
# Open http://localhost:3215/docs

How It Works

  1. Framework adapter — translates between yumerize's core and your framework's internals (Express, NestJS, Fastify, or custom)
  2. api.capture() — the adapter provides middleware that wraps res.json() to record response bodies and status codes
  3. api.scan(app) — the adapter walks the framework's router tree to find all registered routes
  4. api.doc() — stores rich metadata (params, descriptions, responses) alongside a route
  5. api.serve() — renders a single-page HTML UI with an embedded API hitter using fetch(). Also handles DOCX export requests.
  6. api.exportDocx() — generates a formatted Word document from all documented route metadata

Testing Locally Before Publishing

If you want to test yumerize in another project before publishing to npm, use npm link:

1. Register yumerize globally (from the yumerize package directory):

cd D:\projects\yumerize
npm link

2. Link it in your other project:

cd D:\path\to\your-project
npm link yumerize

Now in your project you can import it normally:

import yumerize from "yumerize";

3. After making changes to yumerize source, rebuild:

cd D:\projects\yumerize
npm run build

Changes are reflected immediately in your other project since npm link creates a symlink.

4. When done testing, unlink:

# In your project
cd D:\path\to\your-project
npm unlink yumerize

# In the yumerize package
cd D:\projects\yumerize
npm unlink -g

License

MIT