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

the-owl

v2.0.0

Published

Generate interactive API docs from your functional tests

Readme

the-owl

Generate interactive HTML API docs from your functional tests — a browsable site where, when served live, each captured request is editable and fireable right from the page.

the-owl watches the request/response traffic of your Express app while its functional tests run, then turns that traffic into a browsable docs site — no hand-written .yml or @jsdoc to keep in sync. You mark the requests you care about, run your tests, and the-owl build renders the result.

How it works

capture middleware → Collector → .owl/*.json → `the-owl build` → docs/site/
  • Connect the capture middleware to your Express app.
  • Mark the requests you want documented in your tests (per request, opt-in).
  • Run your tests with CREATE_DOCS=true; each test process drains its captured Examples to .owl/*.json.
  • Build the docs: the-owl build merges every .owl/*.json into a single Catalog and writes the site to docs/site/.

Each captured request/response pair is an Example. Examples sharing the same method + route form an Endpoint; the merge of all Endpoints is the Catalog the docs app renders.

Install

npm install --save-dev the-owl

Quickstart (Vitest)

This mirrors examples/01-minimal.

1. Connect the capture middleware (src/server.ts):

import express, { type Express } from "express";
import theOwl from "the-owl";

export const createApp = (): Express => {
  const app = express();
  theOwl.connect(app); // capture middleware — mount before body parsers/routes
  app.use(express.json());

  app.get("/health", (_req, res) => res.status(200).send("OK"));
  app.get("/users", (_req, res) =>
    res.status(200).json([{ id: 1, name: "John" }, { id: 2, name: "Paul" }]));
  return app;
};

2. Mark the requests you want documented and drain after the run (test/health.test.ts):

import { afterAll, beforeAll, describe, expect, it } from "vitest";
import type { Server } from "node:http";
import theOwl from "the-owl";
import { owlHeaders } from "the-owl/vitest";
import { startServer, stopServer } from "./helpers";

let server: Server;
let base: string;

beforeAll(async () => {
  ({ server, base } = await startServer());
});
afterAll(async () => {
  theOwl.save(); // drain this file's Examples to .owl/
  await stopServer(server);
});

describe("[get] /health", () => {
  it("(200) returns the application status", async () => {
    const res = await fetch(`${base}/health`, {
      headers: { "your-custom-header": "appears in the docs", ...owlHeaders() },
    });
    expect(res.status).toBe(200);
    expect(await res.text()).toBe("OK");
  });
});

Opt-in is per request. Only requests carrying owlHeaders() (the x-test-name header) are captured — attach it to the specific request you want documented, never to a shared request helper. The test's title becomes the Example name.

3. Wire the pipeline in package.json:

{
  "scripts": {
    "test": "vitest run",
    "test:create-docs": "rimraf .owl docs && CREATE_DOCS=true vitest run && the-owl build"
  }
}

save() only writes files when CREATE_DOCS is set, so your everyday npm test (and watch mode) stays clean. Run npm run test:create-docs to generate the docs.

Two delivery modes

Both modes read the same the-owl build output:

  • Static — open or host the generated docs/site/ directory directly (docs/site/index.html + assets). Read-only, but every Example offers a Copy as cURL command you can run against your own API. Set the base URL baked into those commands with the THE_OWL_DOCS_HOST env var at build time (e.g. THE_OWL_DOCS_HOST=https://api.example.com the-owl build); it defaults to http://localhost:3000.
  • Live route — mount theOwl.docs() to serve the docs from your running app, e.g. only in staging:
import theOwl from "the-owl";

if (process.env.NODE_ENV === "staging") {
  app.use("/docs", theOwl.docs());
}

In this mode the docs are interactive: expand any Example, edit its path params / query / headers / body, and click Try it out to fire a real same-origin request and see the live response. The captured x-test-name header is dropped automatically, and any redacted value is shown as an empty field you must fill before firing. The static build has no server to call, so it stays read-only.

Both modes also expose a Copy as cURL button on each Example. In live mode the command reflects your current edits and targets the page's own origin; in static mode it uses the THE_OWL_DOCS_HOST base URL. Redacted or missing values appear as <CHANGE_ME:name> so it's obvious what to fill in before running the command.

Either way, the docs page carries a left navigation that lists every endpoint as a collapsible group of its Examples. Selecting one deep-links to that Example via the URL hash — expanding and scrolling to it — so you can share a link straight to a single Example, and reloading that URL reopens it. On narrow screens the navigation collapses behind a ☰ menu.

Secret redaction

Captured traffic is sanitized before it touches disk. By default, sensitive header values (authorization, cookie, set-cookie, proxy-authorization) and body/query keys (password, token, secret, …) are masked with «redacted» — the key is kept, the value is replaced. Customize via connect(app, options). See the API docs.

Environment variables

the-owl reads a few environment variables — one while your tests run, the rest at the-owl build time (one is also read by the live theOwl.docs() router). See examples/02-elaborate for THE_OWL_DOCS_HOST and THE_OWL_GROUPING_REGEX wired into its test:create-docs scripts, and examples/03-site-output-dir for THE_OWL_SITE_OUTPUT_DIR.

| Variable | Read at | Purpose | Example | | --- | --- | --- | --- | | CREATE_DOCS | test run | Gate for save(): when unset, draining is a no-op, so everyday vitest run and watch mode never write to .owl/. Set it only on the run that (re)generates docs. | CREATE_DOCS=true vitest run | | THE_OWL_DOCS_HOST | the-owl build | Base URL baked into the static build's Copy as cURL commands. Defaults to http://localhost:3000. | THE_OWL_DOCS_HOST=https://api.example.com the-owl build | | THE_OWL_GROUPING_REGEX | the-owl build | Strips the longest matching leading path prefix before the sidebar groups Endpoints, so grouping falls on the next segment (^/api$ groups /api/v1/… under V1). When unset, Endpoints group by their first path segment. | THE_OWL_GROUPING_REGEX=^/api$ the-owl build | | THE_OWL_SITE_OUTPUT_DIR | the-owl build & live theOwl.docs() | Full directory the static site is written to (no site/ suffix appended). Relative paths resolve against the cwd; absolute paths are used as-is. Defaults to docs/site. The live docs() router reads it to locate catalog.json too. | THE_OWL_SITE_OUTPUT_DIR=public/api-docs the-owl build |

THE_OWL_DOCS and PORT in examples/02-elaborate are that example app's own conventions (whether to mount theOwl.docs(), and which port to listen on) — not variables the-owl itself reads.

Motivation

Enforce functional-test development by earning something tangible from it.

API contracts usually change in code while the documentation rots, because it lives in a separate .yml or @jsdoc that developers forget to update. the-owl was built on the idea that all changes should be made in code — write the tests, get the docs.

Documentation

Please see the files in the /documentation directory:

Contributing

Please refer to this document.