An opiniated library using decorators in order to define openAPI documentation for an expressJs endpoint.
@doc({
summary: 'This endpoint will display Hello world.',
})
class HelloWorld extends Endpoint {
@queryParam({ description: 'The name of person to great.', example: 'Alex' })
name?: string;
@errorResponse()
errorForbidden = new Error(`This name is forbidden.`);
handler() {
if (this.name === 'voldemort') {
throw this.errorForbidden;
}
this.res.send(`Hello ${this.name || 'world'}`);
}
}
This library is not a validator. To validate incoming request use express-openapi-validator
Install peer dependency:
npm install express
Create a class, extending the abstract class Endpoint, for each endpoints.
@doc({
summary: 'This endpoint will display Hello world.',
})
class HelloWorld extends Endpoint {
handler() {
this.res.send('Hello world');
}
}
The OpenApi documentation is part of the endpoint instance:
console.log(new HelloWorld().doc);
Add the endpoint to the Router
:
import { router } from 'express-openapi-decorator';
const router = Router();
router.get('/api/hello', new HelloWorld());
Create api doc:
const doc: OpenAPIV3.Document = {
openapi: '3.0.1',
info: {
description: 'This API serve an example.',
version: '1.0.0',
title: 'Hello world',
},
paths: router.doc,
};
Expose router and doc with express:
const port = 3000;
const app = express();
app.get('/api-docs', (_req, res) => res.json(doc));
app.use(router);
app.listen(port, () => {
logger.info(`Ready to accept connections on port: ${port}`);
});
To visualize the OpenApi documentation use swagger-ui-express: app.use('/ui', swaggerUi.serve, swaggerUi.setup(apiDoc));
const router = Router();
is an extension of express Router, working the same way, but instead to pass request handler to the route, endpoints should be passed.
router.get(path: string, ...endpoints: Endpoint[])
will add the endpoint instances to the given path for the get
method (this work for any method). It allow to merge multiple endpoints definition to one, this can be useful for example to create a security middleware:
@doc({
security: [{ basicAuth: [] }],
})
class SecureEndpoint extends Endpoint {
handler() {
if (this.req.headers['authorization'] !== `Basic ${btoa('user:password')}`) {
throw new Error('Invalid credential');
}
next();
}
}
@doc({
summary: 'This endpoint will display Hello world.',
})
class HelloWorld extends Endpoint {
handler() {
this.res.send('Hello world');
}
}
router.get('/api/hello', new SecureEndpoint(), new HelloWorld());
router
can then be used like a normal express router:
app.use(router);
It is possible to access the endpoints instances with router.endpointInstances
.
router.doc
return the OpenApi doc for each endpoints added to the router according there path and method.
const app = express();
const doc: OpenAPIV3.Document = {
openapi: '3.0.1',
info: {
description: 'This API serve an example.',
version: '1.0.0',
title: 'Hello world',
},
paths: router.doc,
};
app.get('/api-docs', (_req, res) => res.json(doc));
To define the characteristic of the endpoint, we use decorators.
The @doc
decorator get an OpenAPIV3.OperationObject
as first parameter, meaning that it is possible to define the whole endpoint documentation here. However, this definition might get partly overwritten by the following decorators.
@doc({
summary: 'This endpoint will display Hello world.',
})
class HelloWorld extends Endpoint {}
The @pathParam
decorator get an OpenAPIV3.ParameterBaseObject
as first parameter, to define the documentation of path parameter. When express will call the handler, the value of the parameter will be automatcally be populated to the endpoint object.
class HelloWorld extends Endpoint {
@pathParam({ description: 'The name of person to great.', required: true, example: 'Alex' })
name!: string;
handler() {
this.res.send(`Hello ${this.name}`);
}
}
The @queryParam
decorator get an OpenAPIV3.ParameterBaseObject
as first parameter, to define the documentation of a query parameter. When express will call the handler, the value of the parameter will be automatcally be populated to the endpoint object.
class HelloWorld extends Endpoint {
@queryParam({ description: 'The name of person to great.', required: true, example: 'Alex' })
name!: string;
handler() {
this.res.send(`Hello ${this.name}`);
}
}
The @bodyProp
decorator get an OpenAPIV3.BodySchema
as first parameter, to define the documentation of a property from the body sent in the request. When express will call the handler, the value of the property will be automatcally be populated to the endpoint object.
class HelloWorld extends Endpoint {
@bodyProp({ description: 'The name of person to great.', required: true, example: 'Alex' })
name!: string;
handler() {
this.res.send(`Hello ${this.name}`);
}
}
The @errorResponse
decorator get an optional OpenAPIV3.ResponsesObject
as first parameter. The value of error property must be of type Error, where the message will be automatically used as description in the OpenApi documentation.
class HelloWorld extends Endpoint {
@errorResponse()
errorConflict = new Error(`This endpoint is in conflict.`);
async handler() {
throw this.errorConflict;
}
}
Or with status code
class Conflict extends Error {
statusCode = 409;
}
class HelloWorld extends Endpoint {
@errorResponse()
errorConflict = new Conflict(`This endpoint is in conflict.`);
async handler() {
throw this.errorConflict;
}
}
or using http-errors library
import { Conflict } from 'http-errors';
class HelloWorld extends Endpoint {
@errorResponse()
errorConflict = new Conflict(`This endpoint is in conflict.`);
async handler() {
throw this.errorConflict;
}
}