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

swagger-og

v0.0.2

Published

Convention-based OpenAPI generation from Koa routes and TypeScript contract types

Readme

swagger-og

Convention-based OpenAPI generation for Koa apps. An endpoint appears in Swagger only when the controller handler wires a response type. Contract files hold type definitions; they are not enough on their own.


Integration guide

How types are resolved (read this first)

  1. swagger-og scans routes, then the matching controller handler.

  2. Visible in Swagger only if that handler is correctly configured, typically:

    const response: GetMyAttendanceResponse = { success: true, data };
    ctx.body = response;
  3. After that reference is found, swagger-og loads the type definition from files matching contractFiles, then follows imports into typeRoots (domain types).

  4. Not visible: ctx.body = { success: true, data } with no response type. The route still works; it is just omitted from Swagger.

  5. Incorrectly configured: a *Response type is exported in contract files but the controller never wires it. Wire it, or delete the unused export.

There is no public flag. Wiring the type in the controller is what makes an endpoint visible.

Type definitions are not hardcoded to src/contracts/api/. Each service sets contractFiles in its own openapi.config.ts.


Install

npm install swagger-og
npm install koa2-swagger-ui   # for serving /api-docs

Monorepo / local:

"dependencies": {
  "swagger-og": "file:../swagger-og"
}

Quick start

1. Config — openapi.config.ts at your project root:

import * as path from 'path';

const projectRoot = path.resolve(__dirname);

export default {
  tsconfigPath: './tsconfig.json',
  mode: 'controller',
  routeFiles: [path.join(projectRoot, 'src/routes/*.ts')],
  controllerFiles: [path.join(projectRoot, 'src/controllers/**/*.ts')],
  contractFiles: [path.join(projectRoot, 'src/contracts/api/**/*.api.ts')],
  typeRoots: [path.join(projectRoot, 'src/types/**/*.types.ts')],
  outputFile: path.join(projectRoot, 'dist/openapi.json'),
  info: { title: 'My API', version: '1.0.0', description: 'HTTP API contracts.' },
};

2. Wire a handler:

import { GetMyAttendanceResponse } from '../contracts/api/timetable/attendance.api';

static getMyAttendance = async (ctx: any) => {
  const data = await getUserAttendanceService(...);
  const response: GetMyAttendanceResponse = { success: true, data };
  ctx.body = response;
};

3. Verify & build:

npx swagger-og check --config openapi.config.ts
npx swagger-og check --config openapi.config.ts --strict   # fail CI on incorrectly configured types
npm run build

Configuration: paths and globs

routeFiles, controllerFiles, contractFiles, and typeRoots are string[] glob patterns. Multiple entries are allowed. There is no built-in default folder.

The glob is part of the path string: folder + pattern together.

src/apis/contracts/**/*.api.ts
│                  │  │
│                  │  └─ file name filter
│                  └─ nested folders
└─ your directory

Different folder (new service)

If contracts live at src/apis/contracts instead of src/contracts/api:

contractFiles: [
  path.join(projectRoot, 'src/apis/contracts/**/*.api.ts'),
],

Controllers in that service import from src/apis/contracts and wire const response: GetXResponse. Change controllerFiles / routeFiles the same way if those trees differ. Each property is independent.

Multiple trees at once

contractFiles: [
  path.join(projectRoot, 'src/contracts/api/**/*.api.ts'),
  path.join(projectRoot, 'src/modules/**/api/*.api.ts'),
  path.join(projectRoot, '../shared-api/**/*.api.ts'),
],

Putting "/" does not mean “scan every file in the repo.”

These fields are glob patterns, not a start directory. "/" is treated as a path, not a recursive filesystem walk. A directory path without a file pattern (e.g. src/contracts/api) also does not recurse into files inside it. Use ** to include nested folders.

| Value | What it matches | |-------|-----------------| | '/' | Not “all files” — not a recursive repo scan | | 'src/contracts/api' | That directory itself, not files inside it | | 'src/foo/*.ts' | Files in that folder only (not nested) | | 'src/foo/**/*.ts' | That folder and all nested folders |


Controller detection

swagger-og looks in the handler body for:

| Signal | Used as | |--------|---------| | const response: GetXResponse = ... then ctx.body = response | response (required for visibility) | | ctx.body = x satisfies GetXResponse | response (optional alternative) | | const body: MarkXRequestBody = ... | request body | | parseGetXQuery(...) | query | | const params: GetXParams = ... | path params |

No wiring → not visible in Swagger. The route still works.


Add an endpoint to Swagger

  1. Route last argument must be Controller.methodName.
  2. Define export type GetXResponse = ... under whatever folder you listed in contractFiles.
  3. Wire it in the controller (const response: GetXResponse = ...; ctx.body = response).
  4. Run swagger-og check.

swagger-og check report

| Label | Meaning | In Swagger? | |-------|---------|-------------| | Visible | Controller is correctly configured (wired response type) | Yes | | Not visible | No wired response type | No (OK) | | Incorrectly configured | Contract *Response exists but controller does not wire it | No — wire or delete |

--strict exits with code 1 only on incorrectly configured items, not on not-visible routes.


Serve docs

import { registerSwaggerRoutes } from 'swagger-og';

if (process.env.SWAGGER_DOCS_ENABLED === 'true') {
  registerSwaggerRoutes(router, {
    specPath: path.join(__dirname, 'openapi.json'),
    specFallbackPath: path.join(process.cwd(), 'dist/openapi.json'),
  });
}

CLI

swagger-og generate [--config openapi.config.ts]
swagger-og check    [--config openapi.config.ts] [--strict]

Pipeline

  1. Parse routes → Controller.handler
  2. Scan controller handler for a wired response type
  3. If found, resolve that type from contractFiles and imports (typeRoots)
  4. Generate JSON Schema
  5. Write OpenAPI 3.0.3 spec