Runtime OpenAPI v3 schema generation for routing-controllers.
Runtime OpenAPI v3 schema generation for routing-controllers.
yarn add routing-controllers-openapi
import { getMetadataArgsStorage } from 'routing-controllers' import { routingControllersToSpec } from 'routing-controllers-openapi'// Define your controllers as usual:
@JsonController('/users') class UsersController { @Get('/:userId') getUser(@Param('userId') userId: string) { // ... }
@HttpCode(201) @Post('/') createUser(@Body() body: CreateUserBody) { // ... } }
// Generate a schema:
const storage = getMetadataArgsStorage() const spec = routingControllersToSpec(storage) console.log(spec)
prints out the following specification:
{ "components": { "schemas": {} }, "info": { "title": "", "version": "1.0.0" }, "openapi": "3.0.0", "paths": { "/users/{userId}": { "get": { "operationId": "UsersController.getUser", "parameters": [ { "in": "path", "name": "userId", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "content": { "application/json": {} }, "description": "Successful response" } }, "summary": "List users", "tags": ["Users"] } }, "/users/": { "post": { "operationId": "UsersController.createUser", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateUserBody" } } }, "description": "CreateUserBody", "required": false }, "responses": { "201": { "content": { "application/json": {} }, "description": "Successful response" } }, "summary": "Create user", "tags": ["Users"] } } } }
/samplefor a complete sample application.
routingControllersToSpechas the following type signature:
export function routingControllersToSpec( storage: MetadataArgsStorage, routingControllerOptions: RoutingControllersOptions = {}, additionalProperties: Partial = {} ): OpenAPIObject
routingControllerOptionsrefers to the options object used to configure routing-controllers. Pass in the same options here to have your
routePrefixand
defaultsoptions reflected in the resulting OpenAPI spec.
additionalPropertiesis a partial OpenAPI object that gets merged into the result spec. You can for example set your own
infoor
componentskeywords here.
Use class-validator-jsonschema to convert your validation classes into OpenAPI-compatible schemas:
import { validationMetadatasToSchemas } from 'class-validator-jsonschema'// ...
const schemas = validationMetadatasToSchemas({ refPointerPrefix: '#/components/schemas/', })
const spec = routingControllersToSpec(storage, routingControllerOptions, { components: { schemas }, info: { title: 'My app', version: '1.2.0' }, })
Use the
@OpenAPIdecorator to supply your actions with additional keywords:
import { OpenAPI } from 'routing-controllers-openapi'@JsonController('/users') export class UsersController { @Get('/') @OpenAPI({ description: 'List all available users', responses: { '400': { description: 'Bad request', }, }, }) listUsers() { // ... } }
The parameter object consists of any number of properties from the Operation object. These properties are then merged into the spec, overwriting any existing values.
Alternatively you can call
@OpenAPIwith a function of type
(source: OperationObject, route: IRoute) => OperationObject, i.e. a function receiving the existing spec as well as the target route, spitting out an updated spec. This function parameter can be used to implement for example your own merging logic or custom decorators.
@OpenAPIdecorators
A single handler can be decorated with multiple
@OpenAPIs. Note though that since decorators are applied top-down, any possible duplicate keys are overwritten by subsequent decorators:
@OpenAPI({ summary: 'This value will be overwritten!', description: 'This value will remain' }) @OpenAPI({ summary: 'This value will remain' }) listUsers() { // ... }
Multiple
@OpenAPIs are merged together with
lodash/mergewhich has a few interesting properties to keep in mind when it comes to arrays. Use the function parameter described above when strict control over merging logic is required.
@OpenAPIdecorator
Using
@OpenAPIon the controller class effectively applies given spec to each class method. Method-level
@OpenAPIs are merged into class specs, with the former having precedence:
@OpenAPI({ security: [{ basicAuth: [] }], // Applied to each method }) @JsonController('/users') export class UsersController { // ... }
Extracting response types automatically in runtime isn't currently allowed by Typescript's reflection system. Specifically the problem is that
routing-controllers-openapican't unwrap generic types like Promise or Array: see e.g. here for discussion. As a workaround you can use the
@ResponseSchemadecorator to supply the response body schema:
import { ResponseSchema } from 'routing-controllers-openapi'@JsonController('/users') export class UsersController { @Get('/:id') @ResponseSchema(User) getUser() { // ... } }
@ResponseSchematakes as an argument either a class-validator class or a plain string schema name. You can also supply an optional secondary
optionsargument:
@Post('/') @ResponseSchema(User, { contentType: 'text/csv', description: 'A list of created user objects', isArray: true statusCode: '201'}) createUsers() { // ... }
contentTypeand
statusCodedefault to routing-controller's
@ContentTypeand
@HttpCodevalues. To specify a response schema of an array, set
options.isArrayas
true. You can also annotate a single handler with multiple
ResponseSchemas to specify responses with different status codes.
Note that when using
@ResponseSchematogether with
@JSONSchema, the outer decorator will overwrite keys of inner decorators. So in the following example, information from
@ResponseSchemawould be overwritten by
@JSONSchema:
@JSONSchema({responses: { '200': { 'content': { 'application/json': { schema: { '$ref': '#/components/schemas/Pet' } } ] } }}) @ResponseSchema(SomeResponseObject) handler() { ... }
Multiple ResponseSchemas with different status codes are supported as follows.
@ResponseSchema(Response1) @ResponseSchema(Response2, {statusCode: '400'})
In case of multiple ResponseSchemas being registered with the same status code, we resolve them using the oneOf operator.
@ResponseSchema(Response1) @ResponseSchema(Response2)
will generate
"200": { "content": { "application/json":{ "schema": { "oneOf": [ {$ref: "#/components/schemas/Response1"}, {$ref: "#/components/schemas/Response2"} ] } } } }
@Controller/
@JsonControllerbase route and default content-type
options.routePrefix
@Get,
@Postand other action decorators
@Paramdecorator
/users/:id(\d+)/:type?) are also supported
@QueryParamand
@QueryParams
@HeaderParamand
@HeaderParams
@Bodyand
@BodyParam
@HttpCodeand
@ContentTypevalues
options.defaults.paramOptions.requiredoption and local override with
{required: true}in decorator params
summary,
operationIdand
tagskeywords from controller/method names
Feel free to submit a PR!