0.0.6 • Published 2 years ago

express-openapi-decorator v0.0.6

Weekly downloads
-
License
UNLICENSED
Repository
github
Last release
2 years ago

express-openapi-decorator

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

How to use

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));

Router

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));

Decorators

To define the characteristic of the endpoint, we use decorators.

@doc

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 {}

@pathParam

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}`);
    }
}

@queryParam

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}`);
    }
}

@bodyProp

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}`);
    }
}

@errorResponse

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;
    }
}
0.0.6

2 years ago

0.0.5

2 years ago

0.0.4

2 years ago

0.0.3

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago