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

@bamboo-studio/swaggerify

v0.0.6

Published

A swagger generation tool for bamboo atlas api scaffold

Downloads

272

Readme

Swaggerify

Automatic OpenAPI 3.0.3 documentation generator for TypeScript backends. Scans route definitions and controller decorators to produce a complete Swagger spec — no manual YAML needed.

How it works

Route file (AST scan)
    → routes with paths, methods, schema names
        → Controller instances (dynamic import + reflect-metadata)
            → Schema classes (dynamic import + class-validator)
                → OpenAPI 3.0.3 JSON
  1. Route scanningscanRoutesFromSource() parses a TypeScript route class with ts-morph and extracts paths, HTTP methods, and schema class names from validationMiddleware() calls.
  2. Controller enrichmentenrichRoutesWithControllerMetadata() dynamically imports and instantiates controller classes, reads decorator metadata via reflect-metadata, and optionally resolves schema class names to JSON Schema objects.
  3. Schema resolutionscanSchemas() maps schema class names to file paths. classValidatorToObject() converts class-validator-decorated classes to JSON Schema.
  4. OpenAPI generationroutesToOpenAPI() assembles the full OpenAPI 3.0.3 document.

Installation

From npm

npm i @bamboo-studio/swaggerify

Local (monorepo / file: reference)

# In the swaggerify project, build first:
npm run build

# In your consumer project:
npm install file:../path/to/swaggerify

Your tsconfig.json must enable decorator metadata:

{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

CLI Usage

The easiest way to use Swaggerify is the CLI. After installing and decorating your controllers and schemas, run:

npx swaggerify generate

This will scan your routes, enrich them with decorator metadata, and write swagger.json to the project root.

Verbose output

Pass -v / --verbose to print every detected route with its controller, tags, auth, body / query / params schemas (including resolved property names) and responses (status code, content type, body shape, description):

npx swaggerify generate -v

Example output:

[swaggerify] Scanning routes in /app/src/routes
[swaggerify]   src/routes/user.route.ts → 4 route(s)
[swaggerify] Found 4 route(s) in 1 file(s)
[swaggerify] Enriching with controller metadata...

[swaggerify] Routes

  GET    /users
    controller   UserController.getUsers
    tags         users
    auth         bearer
    query        UserListRequest → { firstName, email, page }
    responses
      200  application/json       { results, total }
      404  application/json       { code, message }       "Not found"

  POST   /users
    controller   UserController.createUser
    body         CreateUserRequest → { firstName*, email*, age }
    responses
      201  application/json       { id, firstName, email }
      400  application/json       { code, message }       "Bad request"

  DELETE /users/:id
    controller   UserController.deleteUser
    params       { id }
    responses
      204  (no body)                                       "No Content"

  GET    /users/:id/report.pdf
    controller   UserController.getReport
    params       { id }
    responses
      200  application/pdf        string                   "PDF report"

[swaggerify] ✓ Generated /app/swagger.json (4 path(s))

Colors and required-field markers (* next to required properties) are emitted when the output is a TTY.

Config file

Create swaggerify.config.ts in your project root to customise paths and metadata:

import type { SwaggerifyConfig } from 'swaggerify';

export default {
    routesPath: 'src/routes', // default
    controllersPath: 'src/controllers', // default
    schemasPath: 'src/schemas', // default
    output: 'swagger.json', // default
    title: 'My API',
    version: '1.0.0',
    tags: [
        { name: 'Users', description: 'User management' },
        { name: 'Auth', description: 'Authentication' },
    ],
} satisfies SwaggerifyConfig;

All fields are optional — defaults are shown above.

Requires ts-node in the consumer project to load TypeScript config and controller files:

npm install --save-dev ts-node

Quick Start

import fs from 'fs';
import { scanRoutesFromSource, enrichRoutesWithControllerMetadata, routesToOpenAPI } from 'swaggerify';

// 1. Read your route file source
const routeCode = fs.readFileSync('./src/routes/user.route.ts', 'utf-8');

// 2. Extract routes via AST scanning
const routes = scanRoutesFromSource(routeCode);

// 3. Enrich with decorator metadata and resolve schema classes
const enrichedRoutes = enrichRoutesWithControllerMetadata(
    './src/controllers', // directory containing controller classes
    routes,
    './src/schemas', // optional: directory containing schema classes
);

// 4. Generate OpenAPI spec
const openApi = routesToOpenAPI(enrichedRoutes, 'My API', '1.0.0', [{ name: 'Users', description: 'User management' }]);

fs.writeFileSync('openapi.json', JSON.stringify(openApi, null, 2));

Route file conventions

Swaggerify expects route classes structured like this:

import { UserController } from '../controllers/user.controller';
import { UserListRequest, CreateUserRequest } from '../schemas/user.schema';

const validationMiddleware = (schema: any, pos: string) => {};

class UserRoute {
    public path = '/users';
    public router = new Router();
    private userController = new UserController();

    private initializeRoutes() {
        // GET with query schema
        this.router.get(`${this.path}`, validationMiddleware(UserListRequest, 'query'), this.userController.getUsers);

        // POST with body schema
        this.router.post(`${this.path}`, validationMiddleware(CreateUserRequest, 'body'), this.userController.createUser);

        // GET with path params
        this.router.get(`${this.path}/:id`, validationMiddleware(UserIdRequest, 'params'), this.userController.getUserById);

        // @swaggerify-ignore
        this.router.get(`${this.path}/internal-only`, this.userController.internalRoute);
    }
}

// @swaggerify-ignore

Place a // @swaggerify-ignore comment on the line immediately before a route definition to exclude it from the generated spec. No import required — it is a plain comment parsed by the AST scanner.

private initializeRoutes() {
    // Included in the spec:
    this.router.get(`${this.path}`, this.userController.getUsers);

    // @swaggerify-ignore
    this.router.post(`${this.path}/internal`, this.userController.internalAction);

    // Also included:
    this.router.delete(`${this.path}/:id`, this.userController.deleteUser);
}

Decorators

Apply decorators to controller methods to enrich the generated documentation.

@Summary(text: string)

Sets the operation summary (short title).

@Summary('Get list of users')
public getUsers = async (req, res, next) => { ... };

@Description(text: string)

Sets the operation description (longer explanation).

@Description('Returns a paginated list of users filtered by optional query parameters.')
public getUsers = async (req, res, next) => { ... };

@SwaggerResponse(statusCode: number, SchemaClass?: Function, description?: string)

Declares a response. Can be applied multiple times to document multiple status codes.

@SwaggerResponse(200, UserListResponse)
public getUsers = async (req, res, next) => { ... };

For body-less responses (e.g. 204 No Content) omit the schema. The generated operation will not include a content field.

@SwaggerResponse(204)
public deleteUser = async (req, res, next) => { ... };

// With description
@SwaggerResponse(204, 'No Content')
public deleteUser = async (req, res, next) => { ... };

Non-JSON responses (PDF, CSV, binary, …)

Use the options form to declare a non-JSON content type:

// Binary (PDF, octet-stream, image, …) — default schema: { type: 'string', format: 'binary' }
@SwaggerResponse(200, { contentType: 'application/pdf', description: 'PDF report' })
public exportPdf = async (req, res, next) => { ... };

// Text content type (text/csv, text/plain, …) — default schema: { type: 'string' }
@SwaggerResponse(200, { contentType: 'text/csv' })
public exportCsv = async (req, res, next) => { ... };

// Explicit schema with custom contentType
@SwaggerResponse(200, { schema: ReportEnvelope, contentType: 'application/json' })
public getReport = async (req, res, next) => { ... };

Multiple content types per status code

The array form accepts a contentType field. Stacking multiple entries on the same code merges them under one response:

@SwaggerResponse([
    { code: 200, schema: ReportJson, contentType: 'application/json' },
    { code: 200, contentType: 'application/pdf' },
])
public getReport = async (req, res, next) => { ... };

Produces:

'200':
  content:
    application/json: { schema: { ... } }
    application/pdf:  { schema: { type: string, format: binary } }

Stack multiple decorators to document different status codes:

@SwaggerResponse(200, UserListResponse)
@SwaggerResponse(404, ErrorResponse, 'User not found')
@SwaggerResponse(403, ErrorResponse, 'Forbidden')
public getUsers = async (req, res, next) => { ... };

Alternatively, pass an array of response objects in a single decorator. schema is optional inside each entry:

@SwaggerResponse([
    { code: 200, schema: UserListResponse },
    { code: 204, description: 'No Content' },
    { code: 404, schema: ErrorResponse, description: 'User not found' },
])
public getUsers = async (req, res, next) => { ... };

SchemaClass must be a class decorated with class-validator decorators.

@RequestBody(SchemaClass: Function)

Explicitly sets the request body schema. Takes priority over schemas found via route scanning.

@RequestBody(CreateUserRequest)
public createUser = async (req, res, next) => { ... };

@Auth()

Marks the route as requiring Bearer token authentication. Adds security: [{ bearerAuth: [] }] to the operation.

@Auth()
@Summary('Get current user profile')
public getProfile = async (req, res, next) => { ... };

@Tags(...tags: string[])

Overrides the auto-inferred tags for the route (default: derived from the route base path).

@Tags('users', 'admin')
public listAllUsers = async (req, res, next) => { ... };

@Deprecated()

Marks the operation as deprecated in the OpenAPI spec (deprecated: true).

@Deprecated()
@Summary('Old user endpoint — use /v2/users instead')
public legacyGetUsers = async (req, res, next) => { ... };

@Example(value: any) — schema decorator

Adds an example value to a schema class property. Works on any class used as request body, query params, or response schema. The example propagates correctly through class inheritance.

Apply to schema class properties (not controller methods):

import { Example } from 'swaggerify';

class CreateUserRequest {
    @IsString()
    @Example('John')
    firstName: string;

    @IsEmail()
    @Example('[email protected]')
    email: string;

    @IsInt()
    @IsOptional()
    @Example(25)
    age?: number;
}

Produces:

{
    "type": "object",
    "properties": {
        "firstName": { "type": "string", "example": "John" },
        "email": { "type": "string", "format": "email", "example": "[email protected]" },
        "age": { "type": "number", "example": 25 }
    },
    "required": ["firstName", "email"]
}

@Example is also inherited — if a parent class has @Example on a property, child classes get it automatically without redeclaring.


@OpenApiOverride(json: Record<string, any>)

Provides a raw OpenAPI operation object that always takes priority over auto-generation. Use this when the generated spec is insufficient or incorrect.

If auto-generation fails and @OpenApiOverride is present, the override is used as a fallback.

@OpenApiOverride({
    summary: 'Complex multi-format endpoint',
    description: 'Returns data in multiple formats depending on Accept header.',
    parameters: [
        { name: 'format', in: 'query', schema: { type: 'string', enum: ['json', 'csv'] } },
    ],
    responses: {
        200: { description: 'Success' },
        400: { description: 'Invalid format' },
    },
    security: [{ bearerAuth: [] }],
})
public exportData = async (req, res, next) => { ... };

Schema dynamic import

When Swaggerify scans a route file, schema names are stored as strings (e.g. 'UserListRequest'). To convert them to real JSON Schema objects, pass the schemaPath parameter to enrichRoutesWithControllerMetadata():

const enrichedRoutes = enrichRoutesWithControllerMetadata(
    './src/controllers',
    routes,
    './src/schemas', // ← enables dynamic schema import
);

Without schemaPath, schemas remain as unresolved strings — fine for manual mocking but will produce invalid OpenAPI output.

Schema classes use class-validator decorators:

import { IsString, IsEmail, IsOptional, IsInt, IsArray, IsEnum, IsBoolean } from 'class-validator';
import { Type } from 'class-transformer';

export class UserListRequest {
    @IsString()
    @IsOptional()
    firstName?: string;

    @IsEmail()
    @IsOptional()
    email?: string;

    @IsInt()
    @IsOptional()
    page?: number;
}

export class UserListResponse {
    @IsArray()
    @Type(() => UserDetail)
    results: UserDetail[];

    @IsInt()
    total: number;
}

Schema inheritance is supported — validators and @Example from parent classes are automatically included in the child schema:

class BasePagination {
    @IsInt()
    @IsOptional()
    @Example(1)
    page?: number;

    @IsInt()
    @IsOptional()
    @Example(20)
    pageLimit?: number;
}

class UserListRequest extends BasePagination {
    @IsString()
    @IsOptional()
    firstName?: string;
}
// → produces properties: page, pageLimit, firstName (all optional)

Supported validators:

| Decorator | JSON Schema output | | --------------------------------------- | ------------------------------------- | | @IsString() | { type: 'string' } | | @IsInt() / @IsNumber() | { type: 'number' } | | @IsBoolean() | { type: 'boolean' } | | @IsEmail() | { type: 'string', format: 'email' } | | @IsEnum(EnumType) | { type: 'string', enum: [...] } | | @IsIn(any[]) | { type: 'any', enum: [...] } | | @IsArray() + @Type(() => ItemClass) | { type: 'array', items: {...} } | | @IsOptional() | Removes field from required[] |

@IsIn([]) needs another decorator to get the array item types, for example @IsInt()


Configuration

Use swagger.config.ts at project root for reusable settings:

import path from 'path';

export default {
    controllersPath: path.resolve(__dirname, 'src/controllers'),
    schemasPath: path.resolve(__dirname, 'src/schemas'),
    tags: [
        { name: 'Users', description: 'User management' },
        { name: 'Auth', description: 'Authentication' },
    ],
    info: {
        title: 'My API',
        version: '1.0.0',
    },
};

API Reference

scanRoutesFromSource(sourceCode: string): Route[]

Parses a TypeScript route class source string and returns an array of Route objects.

scanControllers(controllersPath: string): Record<string, string>

Returns a map of controller class names to their absolute file paths.

scanSchemas(schemasPath: string): Record<string, string>

Returns a map of schema class names to their absolute file paths.

enrichRoutesWithControllerMetadata(controllerPath, routes, schemaPath?): Route[]

Dynamically imports controllers, reads decorator metadata, and optionally resolves schema strings to JSON Schema objects.

classValidatorToObject(SchemaClass: Function): object

Converts a class-validator-decorated class to a JSON Schema object.

routesToOpenAPI(routes, title, version, tags): object

Generates an OpenAPI 3.0.3 document from enriched routes.


Route interface

interface Route {
    path: string;
    method: HttpMethods;
    body?: object;
    queryParams?: Record<string, object>;
    params?: Record<string, object>;
    auth?: boolean;
    controller?: string;
    function?: string;
    responses?: { code: number; body: object }[];
    summary?: string;
    description?: string;
    tags?: string[];
    deprecated?: boolean;
    openApiOverride?: Record<string, any>;
}

TODOS

This library is still in what we can call beta stage. Here a list of todos:

  • anonymous class support in @SwaggerResponse decorator
  • better logging