
Security News
US Government Forces Anthropic to Pull Claude Fable Days After Launch
Anthropic says the directive cited national security concerns over a narrow jailbreak, but offered no specific technical details.
express-openapi-decorators
Advanced tools
Decorator-based Express controllers with OpenAPI specification generation.
Decorator-based Express controllers with OpenAPI specification generation.
This library is experimental.
It targets modern Node.js + TypeScript setups (ESM) and relies on decorator metadata (Symbol.metadata). Some OpenAPI features are not covered yet. If there is interest in the package, I’ll extend it.
@path(), @method(), @middleware()@tag(), @summary(), @description(),
@operationId(), @deprecated(), @requestBody(), @parameter(), @query(), @header(), @cookie(), @response()app or router via metadataopenapi.json) from the same metadatacomponents.schemas using ts-json-schema-generator/:param to OpenAPI /{param} path conversion
/:id(<pattern>) patternsSymbol.metadata)
npm i express-openapi-decorators
Import it once, before loading any decorated classes.
import 'express-openapi-decorators/symbol-metadata-polyfill.mjs';
A method becomes a route handler only if it has at least one method-level @path().
import type express from 'express';
import { controller, path, method, middleware, tag, summary, description, deprecated, requestBody, query, header, cookie, response } from 'express-openapi-decorators';
@controller()
@path('/users')
@tag('users')
@middleware((req, _res, next) => {
req.headers['x-example'] = '1';
next();
})
export class UserController {
@method('GET')
@path('/:id([0-9]+)')
@summary('Get user by id')
@description('Returns a user by id.')
@deprecated()
@query('includePosts', { type: 'boolean' }, 'Include authored posts')
@header('x-request-id', { type: 'string' }, 'Request correlation id')
@cookie('session', { type: 'string' }, 'Session token')
@response(200, 'User')
@response(404)
async getUserById(req: express.Request, res: express.Response) {
res.json({ id: req.params.id });
}
@method('POST')
@path('/')
@summary('Create a new user')
@requestBody('CreateUserRequest')
@response(200, 'CreateUserResponse')
@response(400)
@response(500)
async createUser(req: express.Request, res: express.Response) {
// ...
}
}
import express from 'express';
import 'express-openapi-decorators/symbol-metadata-polyfill.mjs';
import { OpenAPI } from 'express-openapi-decorators';
const app = express();
const router = await new OpenAPI().initialize({
autoscanControllersGlob: 'build/**/*Controller.mjs',
schemaComponentsGlob: 'src/**/http-dto/*.d.mts',
baseOpenAPISchema: {
openapi: '3.0.0',
info: {
title: 'REST API DEMO',
version: '1.0.0',
description: 'REST API documentation example app.',
},
servers: [
{ url: 'http://localhost/api' },
{ url: 'https://test.example.com/api' },
{ url: 'https://example.com/api' },
],
},
});
app.use('/api', router);
app.listen(80, () => {
console.log(`HTTP Server running on port 80`);
});
initialize() registers controllers onto the provided registrar, or creates and returns an internal express.Router() when registrar is omitted.
OpenAPI.initialize() optionsOpenAPI.initialize() accepts these options:
autoscanControllersGlob?: string | string[]
Imports matching controller modules and collects @controller()-decorated classes.autoloadControllers?: boolean
Collects already-loaded @controller() classes without importing modules.controllers?: object[]
Explicit controller instances to register.controllerClasses?: (new (...args: any[]) => any)[]
Explicit controller classes to instantiate and register.controllerFactoryMap?: Map<(new (...args: any[]) => any), (Cls: new (...args: any[]) => any) => any>
Optional factories for custom controller instantiation.schemaComponentsGlob?: string | string[]
Glob(s) for schema component modules used during OpenAPI generation.registrar?: express.Application | express.Router
Express app/router to register routes on.autoregGetOpenApiSpecOp?: boolean
When true (default), also registers GET /openapi.json.baseOpenAPISchema: oas31.OpenAPIObject
Base OpenAPI document to extend.silent?: boolean
Suppresses registration and generation logs.Typical usage patterns:
autoscanControllersGlob when controller modules should be imported automatically.autoloadControllers when modules are already imported elsewhere and you only want to collect decorated classes.controllers, controllerClasses, and controllerFactoryMap when you want explicit control over registration and instantiation.The generator builds an OpenAPI document by walking decorator metadata on controller instances.
You provide a base OpenAPI document (the generator clones it and merges paths and optional components.schemas).
import { getOpenAPISchema } from 'express-openapi-decorators';
import type { oas31 } from 'openapi3-ts';
const baseOpenAPISchema: oas31.OpenAPIObject = {
openapi: '3.1.0',
info: {
title: 'My API',
version: '1.0.0',
},
servers: [
{ url: 'http://localhost:3000' },
],
};
openapi.jsonUsing the high-level OpenAPI.initialize() method, an openapi.json is generated when you start your server with the --generate-openapi command-line argument:
node server.mjs --generate-openapi
When this flag is present, the library generates openapi.json from the configured controllers and base schema, then exits the process. This step is usually needed only once per build/deploy.
openapi.json will be written to the current working directoryautoregGetOpenApiSpecOp is enabled, GET /openapi.json serves that generated file500@path(path: string)@path('/v1')
@path('/v2')
class UserController {
@path('/login')
@path('/auth')
login(req: express.Request, res: express.Response) {}
}
@method(method: 'GET' | 'POST' | ...)@method@middleware(...handlers: express.RequestHandler[])[...classMiddlewares, ...methodMiddlewares]@tag(...tags: string[])Set@summary(text: string)summary@description(text: string)description@operationId(id: string)operationId@deprecated()deprecated: true on generated operations@deprecated()
class LegacyController {
@path('/old-endpoint')
oldEndpoint(req: express.Request, res: express.Response) {}
}
@requestBody(body: RequestBodyObject | string)string shorthand resolves to #/components/schemas/<name>Name[] for array bodies@requestBody('CreateNotebookRequest')
@requestBody('Notebook[]')
@query(name: string, schemaOrRef: SchemaObject | ReferenceObject | string, description?: string, required = false)string shorthand resolves to #/components/schemas/<name>@query('limit', { type: 'integer', minimum: 1, maximum: 100 }, 'Page size')
@query('filter', 'UserFilter', 'Optional filter')
@header(name: string, schemaOrRef: SchemaObject | ReferenceObject | string, description?: string, required = false)in: 'header'@header('x-request-id', { type: 'string' }, 'Request correlation id', true)
@cookie(name: string, schemaOrRef: SchemaObject | ReferenceObject | string, description?: string, required = false)in: 'cookie'@cookie('session', { type: 'string' }, 'Session token')
@parameter(param: ParameterObject)ParameterObject(in, name) pair@parameter({
name: 'traceId',
in: 'header',
required: false,
description: 'Trace identifier',
schema: { type: 'string' },
})
@response(code: number, content?, description?, headers?)Method or class
Method-level responses are combined with class-level defaults
content forms:
string → shorthand for application/json schema refRecord<contentType, schemaName> → shorthand mapContentObject → full OpenAPI contentExamples:
@response(200, 'Notebook')
@response(200, { 'application/json': 'Notebook' })
@response(201, {
'application/json': { schema: { $ref: '#/components/schemas/Notebook' } },
}, 'Created')
@response(204)
@response(404)
Default descriptions/content exist for some common codes (200/204/400/401/403/404/500) when you omit content.
components.schemas)If you provide schemaComponentsGlob, the generator will attempt to build schemas using ts-json-schema-generator.
Convention used by the included implementation:
Example layout:
src/user/http-dto/User.d.mts
src/user/http-dto/CreateUserRequest.d.mts
Then:
getOpenAPISchema(baseOpenAPISchema, controllers, 'src/**/http-dto/*.d.mts');
This will merge into:
openapi.components.schemas.Useropenapi.components.schemas.CreateUserRequestNotes:
const to enum and inlines internal #/definitions/* refs, except self-references which stay as component $refs.A class method is registered as a route handler only if:
@path(...)Resolution rules:
path = <each class @path> + <each method @path>method = <method @method> else <class @method> else GETmiddlewares = [...class @middleware, ...method @middleware]Express route params like:
/:id → /{id}/:name(a|b|c) → enum: ['a','b','c'] (when pattern looks like a pipe-delimited list)/:id([0-9]+) → pattern: '[0-9]+'MIT
FAQs
Decorator-based Express controllers with OpenAPI specification generation.
We found that express-openapi-decorators demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Anthropic says the directive cited national security concerns over a narrow jailbreak, but offered no specific technical details.

Security News
A network of 152 Chrome live wallpaper extensions hid ad tracking and made extension-driven traffic look like Google search clicks.

Company News
Socket’s first CISO brings deep experience securing high-growth SaaS companies as open source supply chain threats accelerate.