UNPKG

decorator-koa-router

Version:

Package to declare koa controllers via decorators

302 lines (259 loc) 10.5 kB
# Decorator koa router ### Usage This library streamlines the declaration of routes, allowing an agile development of the APIs. The integration has been done under the official package of the Koa team: https://www.npmjs.com/package/@koa/router To use this library it is necessary to work with a dependency container. Examples have been made with the InversifyJS and node-dependency-injection packages. The examples are in this repository: <a href="https://github.com/arnold095/test-koa-router">test-koa-router</a> You can integrate any other dependency injector as long as it complies with the "IocAdapter" interface ### Example with InversifyJS Server.ts ```ts import 'reflect-metadata'; import Koa from 'koa'; import { join } from 'path'; import { InversifyIocAdapter } from './InversifyIocAdapter'; import { RouterBuilder } from 'decorator-koa-router'; export class Server { public static async load(): Promise<void> { const port = 3000; const koaServer = new Koa(); const controllersPath = join(__dirname, './Controllers/*.ts');// The path where controllers are const iocAdapter = new InversifyIocAdapter(); const router = await RouterBuilder('/api', iocAdapter, controllersPath); koaServer.use(router); koaServer.listen(port); console.info(`Server running at port ${port}`); } } Server.load(); ``` InversifyIocAdapter ```ts import { IocAdapter } from '../../IocAdapter'; import { Container } from 'inversify'; import { AdapterTypes } from './AdapterTypes'; import { ProductContainer } from './Containers/ProductContainer'; import { UserContainer } from './Containers/UserContainer'; export class InversifyIocAdapter implements IocAdapter { private _container: Container = new Container(); constructor() { this.inject(ProductContainer.container()); this.inject(UserContainer.container()); } public get<T>(className: string): T { return this.container().get<T>(className); } private container(): Container { return this._container; } private inject(dependencies: AdapterTypes) { const { controllers, services } = dependencies; if (controllers) this.bindControllers(controllers); if (services) this.bindServices(services); } private bindControllers(controllers: AdapterTypes['controllers']) { if (controllers) { for (const controller of controllers) { this.container().bind(controller.name).to(controller).inRequestScope(); } } } private bindServices(services: AdapterTypes['services']) { if (services) { for (const service of services) { this.container().bind(service.name).to(service).inRequestScope(); } } } } ``` ProductContainer ```ts import { AdapterTypes } from '../AdapterTypes'; import { ProductController } from '../Controllers/ProductController'; import { ProductFinder } from '../Services/Product/ProductFinder'; import { ProductCreator } from '../Services/Product/ProductCreator'; import { ProductModifier } from '../Services/Product/ProductModifier'; import { ProductRemover } from '../Services/Product/ProductRemover'; export class ProductContainer { public static container(): AdapterTypes { return { controllers: [ProductController], services: [ProductFinder, ProductCreator, ProductModifier, ProductRemover], }; } } ``` ProductController ```ts import { Context, Response } from 'koa'; import { ProductFinder } from '../Services/Product/ProductFinder'; import { controller, httpDelete, httpGet, httpPost, httpPut } from 'decorator-koa-router'; import { inject, injectable } from 'inversify'; import { ProductCreator } from '../Services/Product/ProductCreator'; import { ProductModifier } from '../Services/Product/ProductModifier'; import { ProductRemover } from '../Services/Product/ProductRemover'; @controller('/product') @injectable() export class ProductController { constructor( @inject('ProductFinder') private readonly productFinder: ProductFinder, @inject('ProductCreator') private readonly productCreator: ProductCreator, @inject('ProductModifier') private readonly productModifier: ProductModifier, @inject('ProductRemover') private readonly productRemover: ProductRemover ) {} @httpGet('/') public async listAll({ response }: Context): Promise<Response> { const product = await this.productFinder.run(); response.status = 200; response.body = product; return response; } @httpPost('/') public async create({ response }: Context): Promise<Response> { await this.productCreator.run(); response.status = 201; return response; } @httpPut('/:productId') public async modify({ response }: Context): Promise<Response> { await this.productModifier.run(); response.status = 204; return response; } @httpDelete('/:productId') public async remove({ response }: Context): Promise<Response> { await this.productRemover.run(); response.status = 200; return response; } } ``` ProductFinder.ts (service) ```ts import { injectable } from 'inversify'; @injectable() export class ProductFinder { public async run(): Promise<Record<string, string | number>> { return { id: 1, name: 'test', }; } } ``` ### Example with node-dependency-injection server.ts ```ts import 'reflect-metadata'; import Koa from 'koa'; import { join } from 'path'; import { RouterBuilder } from '../../RouterBuilder'; import { RouterBuilder } from 'decorator-koa-router'; export class Server { public static async load(): Promise<void> { const port = 3000; const koaServer = new Koa(); const controllersPath = join(__dirname, './Controllers/*.ts'); const iocAdapter = new NodeDependencyInjectionIocAdapter(); const router = await RouterBuilder('/api', iocAdapter, controllersPath); koaServer.use(router); koaServer.listen(port); console.info(`Server running at port ${port}`); } } Server.load(); ``` NodeDependencyInjectionIocAdapter.ts ```ts import { IocAdapter } from '../../IocAdapter'; import { join } from 'path'; import { ContainerBuilder, YamlFileLoader } from 'node-dependency-injection'; export class NodeDependencyInjectionIocAdapter implements IocAdapter { private readonly container: ContainerBuilder; constructor() { this.container = new ContainerBuilder(); const loader = new YamlFileLoader(this.container); loader.load(join(__dirname, './Containers/Container.yaml')); } get<T>(className: string): T { return this.container.get<T>(className); } } ``` ProductController.ts ```ts import { Context, Next, Response } from 'koa'; import { ProductFinder } from '../Services/Product/ProductFinder'; import { controller, httpDelete, httpGet, httpPost, httpPut } from 'decorator-koa-router'; import { ProductCreator } from '../Services/Product/ProductCreator'; import { ProductModifier } from '../Services/Product/ProductModifier'; import { ProductRemover } from '../Services/Product/ProductRemover'; @controller('/product', (ctx: Context, next: Next) => { console.info('Middleware 1'); next(); }, (ctx: Context, next: Next) => { console.info('Middleware 2...'); next(); }) export class ProductController { constructor( private readonly productFinder: ProductFinder, private readonly productCreator: ProductCreator, private readonly productModifier: ProductModifier, private readonly productRemover: ProductRemover ) { } @httpGet('/', (ctx: Context, next: Next) => { console.info('Middleware 3'); next(); }, (ctx: Context, next: Next) => { console.info('Middleware 4...'); next(); }) public async listAll({response}: Context): Promise<Response> { const product = await this.productFinder.run(); response.status = 200; response.body = product; return response; } @httpPost('/') public async create({response}: Context): Promise<Response> { await this.productCreator.run(); response.status = 201; return response; } @httpPut('/:productId') public async modify({response}: Context): Promise<Response> { await this.productModifier.run(); response.status = 204; return response; } @httpDelete('/:productId') public async remove({response}: Context): Promise<Response> { await this.productRemover.run(); response.status = 200; return response; } } ``` ProductFinder.ts (service) ```ts export class ProductFinder { public async run(): Promise<Record<string, string | number>> { return { id: 1, name: 'test', }; } } ``` ### Decorators reference | Signature | Example | Description | | ------------------------------------ | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `@controller(controllerRoute: string, ...middlewares)` | `@controller("/product") class ProductController` | class defined as controller, the paths of the methods that contain this class will be concatenated. | `@httpGet(methodRoute: string, ...middlewares)` | `@httpGet("/") async getProduct()` | This decorator defines a route of type GET | `@httpPost(methodRoute: string, ...middlewares)` | `@httpPost("/") async createProduct()` | This decorator defines a route of type POST | `@httpPut(methodRoute: string, ...middlewares)` | `@httpPut("/:productId") async updateProduct()` | This decorator defines a route of type PUT | `@httpDelete(methodRoute: string, ...middlewares)` | `@httpDelete("/:productId") async removeProduct()` | This decorator defines a route of type DELETE