| name | openapi-generator |
| description | Generates OpenAPI 3.0/3.1 specifications from Express, Next.js, Fastify, Hono, or NestJS routes. Creates complete specs with schemas, examples, and documentation that can be imported into Postman, Insomnia, or used with Swagger UI. Use when users request "generate openapi", "create swagger spec", "openapi documentation", or "api specification". |
OpenAPI Generator
Generate OpenAPI 3.0/3.1 specifications from your API codebase automatically.
Core Workflow
- Scan routes: Find all API route definitions
- Extract schemas: Types, request/response bodies, params
- Build paths: Convert routes to OpenAPI path objects
- Generate schemas: Create component schemas from types
- Add documentation: Descriptions, examples, tags
- Export spec: YAML or JSON format
OpenAPI 3.1 Base Template
openapi: 3.1.0
info:
title: API Title
version: 1.0.0
description: API description
contact:
email: api@example.com
license:
name: MIT
url: https://opensource.org/licenses/MIT
servers:
- url: http://localhost:3000/api
description: Development
- url: https://api.example.com
description: Production
tags:
- name: Users
description: User management endpoints
- name: Products
description: Product catalog endpoints
paths: {}
components:
schemas: {}
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
apiKey:
type: apiKey
in: header
name: X-API-Key
security:
- bearerAuth: []
TypeScript to OpenAPI Schema Converter
import * as ts from "typescript";
interface OpenAPISchema {
type?: string;
properties?: Record<string, OpenAPISchema>;
required?: string[];
items?: OpenAPISchema;
$ref?: string;
enum?: string[];
format?: string;
description?: string;
example?: unknown;
}
function typeToOpenAPISchema(
checker: ts.TypeChecker,
type: ts.Type
): OpenAPISchema {
if (type.flags & ts.TypeFlags.String) {
return { type: "string" };
}
if (type.flags & ts.TypeFlags.Number) {
return { type: "number" };
}
if (type.flags & ts..) {
{ : };
}
(checker.()) {
elementType = ( ts.).?.[];
{
: ,
: elementType ? (checker, elementType) : {},
};
}
(. & ts..) {
: <, > = {};
: [] = [];
.().( {
propType = checker.(
prop,
prop.!
);
properties[prop.] = (checker, propType);
(!(prop. & ts..)) {
required.(prop.);
}
});
{
: ,
properties,
: required. > ? required : ,
};
}
(.()) {
enumValues = .
.( t.())
.( (t ts.).);
(enumValues. > ) {
{ : , : enumValues };
}
}
{};
}
Express Route Scanner with JSDoc
import * as fs from "fs";
import * as path from "path";
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
interface RouteMetadata {
method: string;
path: string;
summary?: string;
description?: string;
tags?: string[];
requestBody?: object;
responses?: Record<string, object>;
parameters?: object[];
security?: object[];
}
function extractJSDocMetadata(comments: string): Partial<RouteMetadata> {
const metadata: Partial<RouteMetadata> = {};
const summaryMatch = comments.match(/@summary\s+(.+)/);
if (summaryMatch) metadata. = summaryMatch[].();
descMatch = comments.();
(descMatch) metadata. = descMatch[].();
tagsMatch = comments.();
(tagsMatch) metadata. = tagsMatch[].().( t.());
metadata;
}
(): [] {
: [] = [];
routes;
}
OpenAPI Path Generator
import * as yaml from "js-yaml";
interface OpenAPISpec {
openapi: string;
info: object;
servers: object[];
paths: Record<string, object>;
components: {
schemas: Record<string, object>;
securitySchemes?: object;
};
tags?: object[];
security?: object[];
}
function generateOpenAPISpec(
routes: RouteMetadata[],
options: {
title: string;
version: string;
description?: string;
servers: { url: string; description: string }[];
}
): OpenAPISpec {
const spec: OpenAPISpec = {
openapi: "3.1.0",
info: {
title: options.title,
version: options.version,
description: options.description,
},
: options.,
: {},
: {
: {},
: {
: {
: ,
: ,
: ,
},
},
},
: [],
};
tagSet = <>();
( route routes) {
openAPIPath = route..(, );
(!spec.[openAPIPath]) {
spec.[openAPIPath] = {};
}
spec.[openAPIPath][route..()] = {
: route. || ,
: route.,
: route. || [(route.)],
: (route),
: route.,
: route. || (route.),
: route.,
};
(route. || [(route.)]).(
tagSet.(t)
);
}
spec. = .(tagSet).( ({ name }));
spec;
}
(): [] {
: [] = [];
pathParamRegex = ;
match;
((match = pathParamRegex.(route.)) !== ) {
params.({
: match[],
: ,
: ,
: { : },
: ,
});
}
params;
}
(): {
: <, > = {
: {
: ,
: {
: {
: { : },
},
},
},
: {
: ,
: {
: {
: { : },
},
},
},
: {
: ,
},
: {
: ,
},
: {
: ,
},
};
(method === ) {
responses[] = {
: ,
: {
: {
: { : },
},
},
};
}
(method === ) {
responses[] = {
: ,
};
}
responses;
}
(): {
parts = path.().();
parts[] || ;
}
Common Schema Components
components:
schemas:
Error:
type: object
required:
- code
- message
properties:
code:
type: string
example: "VALIDATION_ERROR"
message:
type: string
example: "Invalid request data"
details:
type: object
additionalProperties:
type: array
items:
type: string
Pagination:
type: object
properties:
page:
type: integer
minimum: 1
example: 1
limit:
type: integer
minimum: 1
maximum: 100
example: 10
total:
type: integer
example:
{}
Fastify Integration
import Fastify from "fastify";
import swagger from "@fastify/swagger";
import swaggerUi from "@fastify/swagger-ui";
const fastify = Fastify({ logger: true });
await fastify.register(swagger, {
openapi: {
info: {
title: "My API",
version: "1.0.0",
},
servers: [{ url: "http://localhost:3000" }],
},
});
await fastify.register(swaggerUi, {
routePrefix: "/docs",
});
fastify.get(
"/users/:id",
{
schema: {
params: {
type: "object",
properties: {
id: { type: "string", format: "uuid" },
},
required: ["id"],
},
response: {
200: {
type: "object",
properties: {
id: { : },
: { : },
: { : },
},
},
},
},
},
(request, reply) => {
}
);
NestJS Integration
import { Controller, Get, Post, Body, Param } from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiBody } from "@nestjs/swagger";
@ApiTags("users")
@Controller("users")
export class UsersController {
@Get()
@ApiOperation({ summary: "Get all users" })
@ApiResponse({ status: 200, description: "List of users", type: [UserDto] })
findAll() {
}
@Get(":id")
@ApiOperation({ summary: "Get user by ID" })
@ApiResponse({ status: 200, description: "User found", type: UserDto })
@ApiResponse({ status: 404, description: })
() {
}
()
({ : })
({ : })
({ : , : , : })
() {
}
}
CLI Script
#!/usr/bin/env node
import * as fs from "fs";
import * as yaml from "js-yaml";
import { program } from "commander";
program
.name("openapi-gen")
.description("Generate OpenAPI specification from API routes")
.option("-f, --framework <type>", "Framework (express|nextjs|fastify)", "express")
.option("-s, --source <path>", "Source directory", "./src")
.option("-o, --output <path>", "Output file", "./openapi.yaml")
.option("-t, --title <name>", "API title", "My API")
.option("-v, --version <version>", "API version", "1.0.0")
.option("--json", "Output as JSON instead of YAML")
.parse();
const options = program.opts();
async function main() {
const routes = await scanRoutes(options., options.);
spec = (routes, {
: options.,
: options.,
: [
{ : , : },
],
});
output = options.
? .(spec, , )
: yaml.(spec, { : - });
fs.(options., output);
.();
}
();
Validation Script
import SwaggerParser from "@apidevtools/swagger-parser";
async function validateSpec(specPath: string): Promise<void> {
try {
const api = await SwaggerParser.validate(specPath);
console.log(`API name: ${api.info.title}, Version: ${api.info.version}`);
console.log("OpenAPI specification is valid!");
} catch (err) {
console.error("Validation failed:", err.message);
process.exit(1);
}
}
Best Practices
- Use $ref: Reference shared schemas to avoid duplication
- Add examples: Include realistic examples for all schemas
- Document errors: Define all possible error responses
- Use tags: Organize endpoints by resource/feature
- Version control: Commit spec to repository
- Validate: Run validation before publishing
- Generate SDKs: Use openapi-generator for client SDKs
- Serve UI: Host Swagger UI or Redoc for documentation
Output Checklist