Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage in Express / Koa using TypeScript and Routing Controllers Framework.

License

NotificationsYou must be signed in to change notification settings

typestack/routing-controllers

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Statuscodecovnpm versionDependency Status

English |中文

Allows to create controller classes with methods as actions that handle requests.You can use routing-controllers withexpress.js orkoa.js.

Table of Contents

Installation

  1. Install module:

    npm install routing-controllers

  2. reflect-metadata shim is required:

    npm install reflect-metadata

    and make sure to import it before you use routing-controllers:

import'reflect-metadata';
  1. Install framework:

    a. If you want to use routing-controllers withexpress.js, then install it and all required dependencies:

    npm install express body-parser multer

    Optionally you can also install their typings:

    npm install -D @types/express @types/body-parser @types/multer

    b. If you want to use routing-controllers withkoa 2, then install it and all required dependencies:

    npm install koa @koa/router koa-bodyparser @koa/multer

    Optionally you can also install their typings:

    npm install -D @types/koa @types/koa-bodyparser

  2. Install peer dependencies:

npm install class-transformer class-validator

In prior versions, these were direct dependencies, but now they are peer dependencies so you can choose when to upgrade and accept breaking changes.

  1. Its important to set these options intsconfig.json file of your project:

    {"emitDecoratorMetadata":true,"experimentalDecorators":true}

Example of usage

  1. Create a fileUserController.ts

    import'reflect-metadata';import{Controller,Param,Body,Get,Post,Put,Delete}from'routing-controllers';@Controller()exportclassUserController{  @Get('/users')getAll(){return'This action returns all users';}  @Get('/users/:id')getOne(@Param('id')id:number){return'This action returns user #'+id;}  @Post('/users')post(@Body()user:any){return'Saving user...';}  @Put('/users/:id')put(@Param('id')id:number, @Body()user:any){return'Updating a user...';}  @Delete('/users/:id')remove(@Param('id')id:number){return'Removing user...';}}

    This class will register routes specified in method decorators in your server framework (express.js or koa).

  2. Create a fileapp.ts

    // this shim is requiredimport{createExpressServer}from'routing-controllers';import{UserController}from'./UserController';// creates express app, registers all controller routes and returns you express app instanceconstapp=createExpressServer({controllers:[UserController],// we specify controllers we want to use});// run express application on port 3000app.listen(3000);

    if you are koa user you just need to usecreateKoaServer instead ofcreateExpressServer

  3. Open in browserhttp://localhost:3000/users. You will seeThis action returns all users in your browser.If you openhttp://localhost:3000/users/1 you will seeThis action returns user #1.

More examples

Working with json

If you are designing a REST API where your endpoints always receive and return JSON thenyou can use@JsonController decorator instead of@Controller.This will guarantee you that data returned by your controller actions always be transformed to JSONandContent-Type header will be always set toapplication/json.It will also guaranteeapplication/json header is understood from the requests and the body parsed as JSON:

import{JsonController,Param,Body,Get,Post,Put,Delete}from'routing-controllers';@JsonController()exportclassUserController{  @Get('/users')getAll(){returnuserRepository.findAll();}  @Get('/users/:id')getOne(@Param('id')id:number){returnuserRepository.findById(id);}  @Post('/users')post(@Body()user:User){returnuserRepository.insert(user);}}

Return promises

You can return a promise in the controller, and it will wait until promise resolved and return promise result in a response body.

import{JsonController,Param,Body,Get,Post,Put,Delete}from'routing-controllers';@JsonController()exportclassUserController{  @Get('/users')getAll(){returnuserRepository.findAll();}  @Get('/users/:id')getOne(@Param('id')id:number){returnuserRepository.findById(id);}  @Post('/users')post(@Body()user:User){returnuserRepository.insert(user);}  @Put('/users/:id')put(@Param('id')id:number, @Body()user:User){returnuserRepository.updateById(id,user);}  @Delete('/users/:id')remove(@Param('id')id:number){returnuserRepository.removeById(id);}}

Using Request and Response objects

You can use framework's request and response objects directly. If you want to handle the response by yourself,just make sure you return the response object itself from the action.

import{Controller,Req,Res,Get}from'routing-controllers';@Controller()exportclassUserController{  @Get('/users')getAllUsers(@Req()request:any, @Res()response:any){returnresponse.send('Hello response!');}  @Get('/posts')getAllPosts(@Req()request:any, @Res()response:any){// some response functions don't return the response object,// so it needs to be returned explicitlyresponse.redirect('/users');returnresponse;}}

@Req() decorator injects you aRequest object, and@Res() decorator injects you aResponse object.If you have installed typings, you can use their types:

import{Request,Response}from'express';import{Controller,Req,Res,Get}from'routing-controllers';@Controller()exportclassUserController{  @Get('/users')getAll(@Req()request:Request, @Res()response:Response){returnresponse.send('Hello response!');}}

note: koa users can also use@Ctx() context to inject koa's Context object.

Pre-configure express/koa

If you have, or if you want to create and configure express app separately,you can useuseExpressServer instead ofcreateExpressServer function:

import{useExpressServer}from'routing-controllers';letexpress=require('express');// or you can import it if you have installed typingsletapp=express();// your created express server// app.use() // you can configure it the way you wantuseExpressServer(app,{// register created express server in routing-controllerscontrollers:[UserController],// and configure it the way you need (controllers, validation, etc.)});app.listen(3000);// run your express server

koa users must useuseKoaServer instead ofuseExpressServer

Load all controllers from the given directory

You can load all controllers from directories, by specifying array of directories in options ofcreateExpressServer oruseExpressServer:

import{createExpressServer}from'routing-controllers';importpathfrom'path';createExpressServer({controllers:[path.join(__dirname+'/controllers/*.js')],}).listen(3000);// register controllers routes in our express application

koa users must usecreateKoaServer instead ofcreateExpressServer

Prefix all controllers routes

If you want to prefix all your routes, e.g./api you can useroutePrefix option:

import{createExpressServer}from'routing-controllers';import{UserController}from'./controller/UserController';createExpressServer({routePrefix:'/api',controllers:[UserController],}).listen(3000);

koa users must usecreateKoaServer instead ofcreateExpressServer

Prefix controller with base route

You can prefix all specific controller's actions with base route:

@Controller('/users')exportclassUserController{// ...}

Inject routing parameters

You can use@Param decorator to inject parameters in your controller actions:

@Get("/users/:id")getOne(@Param("id")id:number){// id will be automatically casted to "number" because it has type number}

If you want to inject all parameters use@Params() decorator.

Inject query parameters

To inject query parameters, use@QueryParam decorator:

@Get("/users")getUsers(@QueryParam("limit")limit:number){}

You can useisArray option to get a query param array. This will cast the query param :

@Get("/users/by-multiple-ids")getUsers(@QueryParam("ids",{isArray:true})ids: string[]){}

GET /users/by-multiple-ids?ids=aids = ['a']GET /users/by-multiple-ids?ids=a&ids=bids = ['a', 'b']

You can combine useisArray option withtype option to get a query param array of one type. This will cast the query param :

@Get("/users/by-multiple-ids")getUsers(@QueryParam("ids",{isArray:true,type:Number})ids: number[]){}

GET /users/by-multiple-ids?ids=1ids = [1]GET /users/by-multiple-ids?ids=1&ids=3.5ids = [1, 3.5]

If you want to inject all query parameters use@QueryParams() decorator.The biggest benefit of this approach is that you can perform validation of the params.

enumRoles{Admin="admin",User="user",Guest="guest",}classGetUsersQuery{    @IsPositive()limit:number;    @IsAlpha()city:string;    @IsEnum(Roles)role:Roles;    @IsBoolean()isActive:boolean;    @IsArray()    @IsNumber(undefined,{each:true})    @Type(()=>Number)ids:number[];}@Get("/users")getUsers(@QueryParams()query:GetUsersQuery){// here you can access query.role, query.limit// and others valid query parameters// query.ids will be an array, of numbers, even with one element}

Inject request body

To inject request body, use@Body decorator:

@Post("/users")saveUser(@Body()user:User){}

If you specify a class type to parameter that is decorated with@Body(),routing-controllers will useclass-transformer to create instance of the given class type from the data received in request body.To disable this behaviour you need to specify a{ classTransformer: false } in RoutingControllerOptions when creating a server.

Inject request body parameters

To inject request body parameter, use@BodyParam decorator:

@Post("/users")saveUser(@BodyParam("name")userName:string){}

Inject request header parameters

To inject request header parameter, use@HeaderParam decorator:

@Post("/users")saveUser(@HeaderParam("authorization")token:string){}

If you want to inject all header parameters use@HeaderParams() decorator.

Inject cookie parameters

To get a cookie parameter, use@CookieParam decorator:

@Get("/users")getUsers(@CookieParam("username")username:string){}

If you want to inject all header parameters use@CookieParams() decorator.

Inject session object

To inject a session value, use@SessionParam decorator:

@Get("/login")savePost(@SessionParam("user")user:User, @Body()post:Post){}

If you want to inject the main session object, use@Session() without any parameters.

@Get("/login")savePost(@Session()session:any, @Body()post:Post){}

The parameter marked with@Session decorator is required by default. If your action param is optional, you have to mark it as not required:

action(@Session("user",{required:false})user:User){}

Express usesexpress-session / Koa useskoa-session orkoa-generic-session to handle session, so firstly you have to install it manually to use@Session decorator.

Inject state object

To inject a state parameter use@State decorator:

@Get("/login")savePost(@State("user")user:User, @Body()post:Post){}

If you want to inject the whole state object use@State() without any parameters.This feature is only supported by Koa.

Inject uploaded file

To inject uploaded file, use@UploadedFile decorator:

@Post("/files")saveFile(@UploadedFile("fileName")file:any){}

You can also specify uploading options to multer this way:

// to keep code clean better to extract this function into separate fileexportconstfileUploadOptions=()=>({storage:multer.diskStorage({destination:(req:any,file:any,cb:any)=>{ ...},filename:(req:any,file:any,cb:any)=>{ ...}}),fileFilter:(req:any,file:any,cb:any)=>{ ...},limits:{fieldNameSize:255,fileSize:1024*1024*2}});// use options this way:@Post("/files")saveFile(@UploadedFile("fileName",{options:fileUploadOptions})file:any){}

To inject all uploaded files use@UploadedFiles decorator instead.Routing-controllers usesmulter to handle file uploads.You can install multer's file definitions via typings, and usefiles: File[] type instead ofany[].

Make parameter required

To make any parameter required, simply pass arequired: true flag in its options:

@Post("/users")save(@Body({required:true})user:any){// your method will not be executed if user is not sent in a request}

Same you can do with all other parameters@QueryParam,@BodyParam and others.If user request does not contain required parameter routing-controllers will throw an error.

Convert parameters to objects

If you specify a class type to parameter that is decorated with parameter decorator,routing-controllers will useclass-transformer to create instance of that class type.More info about this feature is availablehere.

Set custom ContentType

You can specify a custom ContentType header:

@Get("/users")@ContentType("text/csv")getUsers(){// ...}

Set Location

You can set a Location header for any action:

@Get("/users")@Location("http://github.com")getUsers(){// ...}

Set Redirect

You can set a Redirect header for any action:

@Get("/users")@Redirect("http://github.com")getUsers(){// ...}

You can override the Redirect header by returning a string value:

@Get("/users")@Redirect("http://github.com")getUsers(){return"https://www.google.com";}

You can use template to generate the Redirect header:

@Get("/users")@Redirect("http://github.com/:owner/:repo")getUsers(){return{owner:"typestack",repo:"routing-controllers"};}

Set custom HTTP code

You can explicitly set a returned HTTP code for any action:

@HttpCode(201)@Post("/users")saveUser(@Body()user:User){// ...}

Controlling empty responses

If your controller returnsvoid orPromise<void> orundefined it will throw you 404 error.To prevent this if you need to specify what status code you want to return using@OnUndefined decorator.

@Delete("/users/:id")@OnUndefined(204)asyncremove(@Param("id")id: number):Promise<void>{returnuserRepository.removeById(id);}

@OnUndefined is also useful when you return some object which can or cannot be undefined.In this examplefindOneById returns undefined in the case if user with given id was not found.This action will return 404 in the case if user was not found, and regular 200 in the case if it was found.

@Get("/users/:id")@OnUndefined(404)getOne(@Param("id")id: number){returnuserRepository.findOneById(id);}

You can also specify error class you want to use if it returned undefined:

import{HttpError}from'routing-controllers';exportclassUserNotFoundErrorextendsHttpError{constructor(){super(404,'User not found!');}}
@Get("/users/:id")@OnUndefined(UserNotFoundError)saveUser(@Param("id")id: number){returnuserRepository.findOneById(id);}

If controller action returnsnull you can use@OnNull decorator instead.

Set custom headers

You can set any custom header in a response:

@Get("/users/:id")@Header("Cache-Control","none")getOne(@Param("id")id: number){// ...}

Render templates

If you are using server-side rendering you can render any template:

@Get("/users/:id")@Render("index.html")getOne(){return{param1:"these params are used",param2:"in templating engine"};}

To use rendering ability make sure to configure express / koa properly.To use rendering ability with Koa you will need to use a rendering 3rd party such as@koa/ejs,@koa/ejs is the only render middleware that has been tested.

Seethe koa render test file as an example.

Throw HTTP errors

If you want to return errors with specific error codes, there is an easy way:

@Get("/users/:id")getOne(@Param("id")id:number){constuser=this.userRepository.findOneById(id);if(!user)thrownewNotFoundError(`User was not found.`);// message is optionalreturnuser;}

Now, when user won't be found with requested id, response will be with http status code 404 and following content:

{"name":"NotFoundError","message":"User was not found."}

There are set of prepared errors you can use:

  • HttpError
  • BadRequestError
  • ForbiddenError
  • InternalServerError
  • MethodNotAllowedError
  • NotAcceptableError
  • NotFoundError
  • UnauthorizedError
  • UnprocessableEntityError

You can also create and use your own errors by extendingHttpError class.To define the data returned to the client, you could define a toJSON method in your error.

classDbErrorextendsHttpError{publicoperationName:string;publicargs:any[];constructor(operationName:string,args:any[]=[]){super(500);Object.setPrototypeOf(this,DbError.prototype);this.operationName=operationName;this.args=args;// can be used for internal logging}toJSON(){return{status:this.httpCode,failedOperation:this.operationName,};}}

Enable CORS

Since CORS is a feature that is used almost in any web-api application,you can enable it in routing-controllers options.

import{createExpressServer}from'routing-controllers';import{UserController}from'./UserController';constapp=createExpressServer({cors:true,controllers:[UserController],});app.listen(3000);

To use cors you need to install its module.For express itsnpm i cors, for koa itsnpm i @koa/cors.You can pass cors options as well:

import{createExpressServer}from'routing-controllers';import{UserController}from'./UserController';constapp=createExpressServer({cors:{// options from cors documentation},controllers:[UserController],});app.listen(3000);

Default settings

You can override default status code in routing-controllers options.

import{createExpressServer}from'routing-controllers';import{UserController}from'./UserController';constapp=createExpressServer({defaults:{//with this option, null will return 404 by defaultnullResultCode:404,//with this option, void or Promise<void> will return 204 by defaultundefinedResultCode:204,paramOptions:{//with this option, argument will be required by defaultrequired:true,},},});app.listen(3000);

Selectively disable request/response transform

To disableclass-transformer on a per-controller or per-route basis, use thetransformRequest andtransformResponse options on your controller and route decorators:

@Controller("/users",{transformRequest:false,transformResponse:false})exportclassUserController{    @Get("/",{transformResponse:true}){// route option overrides controller option}}

Using middlewares

You can use any existing express / koa middleware, or create your own.To create your middlewares there is a@Middleware decorator,and to use already exist middlewares there are@UseBefore and@UseAfter decorators.

Use existing middleware

There are multiple ways to use middleware.For example, lets try to usecompression middleware:

  1. Install compression middleware:npm install compression

  2. To use middleware per-action:

    import{Controller,Get,UseBefore}from"routing-controllers";letcompression=require("compression");// ...@Get("/users/:id")@UseBefore(compression())getOne(@Param("id")id: number){// ...}

    This way compression middleware will be applied only forgetOne controller action,and will be executedbefore action execution.To execute middlewareafter action use@UseAfter decorator instead.

  3. To use middleware per-controller:

    import{Controller,UseBefore}from'routing-controllers';letcompression=require('compression');@Controller()@UseBefore(compression())exportclassUserController{}

    This way compression middleware will be applied for all actions of theUserController controller,and will be executedbefore its action execution. Same way you can use@UseAfter decorator here.

  4. If you want to use compression module globally for all controllers you can simply register it during bootstrap:

    import{createExpressServer}from'routing-controllers';import{UserController}from'./UserController';// we need to "load" our controller before call createExpressServer. this is requiredletcompression=require('compression');letapp=createExpressServer({controllers:[UserController],});// creates express app, registers all controller routes and returns you express app instanceapp.use(compression());app.listen(3000);// run express application

    Alternatively, you can create a customglobal middleware and simply delegate its execution to the compression module.

Creating your own express middleware

Here is example of creating middleware for express.js:

  1. There are two ways of creating middleware:

    First, you can create a simple middleware function:

    exportfunctionloggingMiddleware(request:any,response:any,next?:(err?:any)=>any):any{console.log('do something...');next();}

    Second you can create a class:

    import{ExpressMiddlewareInterface}from'routing-controllers';exportclassMyMiddlewareimplementsExpressMiddlewareInterface{// interface implementation is optionaluse(request:any,response:any,next?:(err?:any)=>any):any{console.log('do something...');next();}}
  2. Then you can use them this way:

    import{Controller,UseBefore}from'routing-controllers';import{MyMiddleware}from'./MyMiddleware';import{loggingMiddleware}from'./loggingMiddleware';@Controller()@UseBefore(MyMiddleware)@UseAfter(loggingMiddleware)exportclassUserController{}

    or per-action:

    @Get("/users/:id")@UseBefore(MyMiddleware)@UseAfter(loggingMiddleware)getOne(@Param("id")id: number){// ...}

    @UseBefore executes middleware before controller action.@UseAfter executes middleware after each controller action.

Creating your own koa middleware

Here is example of creating middleware for koa.js:

  1. There are two ways of creating middleware:

    First, you can create a simple middleware function:

    exportfunctionuse(context:any,next:(err?:any)=>Promise<any>):Promise<any>{console.log('do something before execution...');returnnext().then(()=>{console.log('do something after execution');}).catch(error=>{console.log('error handling is also here');});}

    Second you can create a class:

    import{KoaMiddlewareInterface}from'routing-controllers';exportclassMyMiddlewareimplementsKoaMiddlewareInterface{// interface implementation is optionaluse(context:any,next:(err?:any)=>Promise<any>):Promise<any>{console.log('do something before execution...');returnnext().then(()=>{console.log('do something after execution');}).catch(error=>{console.log('error handling is also here');});}}
  2. Then you can them this way:

    import{Controller,UseBefore}from'routing-controllers';import{MyMiddleware}from'./MyMiddleware';import{loggingMiddleware}from'./loggingMiddleware';@Controller()@UseBefore(MyMiddleware)@UseAfter(loggingMiddleware)exportclassUserController{}

    or per-action:

    @Get("/users/:id")@UseBefore(MyMiddleware)@UseAfter(loggingMiddleware)getOne(@Param("id")id: number){// ...}

    @UseBefore executes middleware before controller action.@UseAfter executes middleware after each controller action.

Global middlewares

Global middlewares run before each request, always.To make your middleware global mark it with@Middleware decorator and specify if it runs after or before controllers actions.

import{Middleware,ExpressMiddlewareInterface}from'routing-controllers';@Middleware({type:'before'})exportclassLoggingMiddlewareimplementsExpressMiddlewareInterface{use(request:any,response:any,next:(err:any)=>any):void{console.log('do something...');next();}}

To enable this middleware, specify it during routing-controllers initialization:

import{createExpressServer}from'routing-controllers';import{UserController}from'./UserController';import{LoggingMiddleware}from'./LoggingMiddleware';createExpressServer({controllers:[UserController],middlewares:[LoggingMiddleware],}).listen(3000);

Error handlers

Error handlers are specific only to express.Error handlers work same way as middlewares, but implementExpressErrorMiddlewareInterface:

  1. Create a class that implements theErrorMiddlewareInterface interface:

    import{Middleware,ExpressErrorMiddlewareInterface}from'routing-controllers';@Middleware({type:'after'})exportclassCustomErrorHandlerimplementsExpressErrorMiddlewareInterface{error(error:any,request:any,response:any,next:(err:any)=>any){console.log('do something...');next();}}

Custom error handlers are invoked after the default error handler, so you won't be able to change response code or headers.To prevent this, you have to disable default error handler by specifyingdefaultErrorHandler option in createExpressServer or useExpressServer:

createExpressServer({defaultErrorHandler:false,// disable default error handler, only if you have your own error handler}).listen(3000);

Loading middlewares, interceptors and controllers from directories

Also you can load middlewares from directories. Also you can use glob patterns:

import{createExpressServer}from'routing-controllers';importpathfrom'path';createExpressServer({controllers:[path.join(__dirname,'/controllers/**/*.js')],middlewares:[path.join(__dirname,'/middlewares/**/*.js')],interceptors:[path.join(__dirname,'/interceptors/**/*.js')],}).listen(3000);

Using interceptors

Interceptors are used to change or replace the data returned to the client.You can create your own interceptor class or function and use to all or specific controller or controller action.It works pretty much the same as middlewares.

Interceptor function

The easiest way is to use functions directly passed to@UseInterceptor of the action.

import{Get,Param,UseInterceptor}from"routing-controllers";// ...@Get("/users")@UseInterceptor(function(action:Action,content:any){// here you have content returned by this action. you can replace something// in it and return a replaced result. replaced result will be returned to the userreturncontent.replace(/Mike/gi,"Michael");})getOne(@Param("id")id: number){return"Hello, I am Mike!";// client will get a "Hello, I am Michael!" response.}

You can use@UseInterceptor per-action, or per-controller.If its used per-controller then interceptor will apply to all controller actions.

Interceptor classes

You can also create a class and use it with@UseInterceptor decorator:

import{Interceptor,InterceptorInterface,Action}from'routing-controllers';exportclassNameCorrectionInterceptorimplementsInterceptorInterface{intercept(action:Action,content:any){returncontent.replace(/Mike/gi,'Michael');}}

And use it in your controllers this way:

import{Get,Param,UseInterceptor}from"routing-controllers";import{NameCorrectionInterceptor}from"./NameCorrectionInterceptor";// ...@Get("/users")@UseInterceptor(NameCorrectionInterceptor)getOne(@Param("id")id: number){return"Hello, I am Mike!";// client will get a "Hello, I am Michael!" response.}

Global interceptors

You can create interceptors that will affect all controllers in your project by creating interceptor classand mark it with@Interceptor decorator:

import{Interceptor,InterceptorInterface,Action}from'routing-controllers';@Interceptor()exportclassNameCorrectionInterceptorimplementsInterceptorInterface{intercept(action:Action,content:any){returncontent.replace(/Mike/gi,'Michael');}}

Creating instances of classes from action params

When user sends a json object and you are parsing it, sometimes you want to parse it into object of some class, instead of parsing it into simple literal object.You have ability to do this usingclass-transformer.To use it simply specify aclassTransformer: true option on application bootstrap:

import{createExpressServer}from'routing-controllers';createExpressServer({classTransformer:true,}).listen(3000);

Now, when you parse your action params, if you have specified a class, routing-controllers will create you a classof that instance with the data sent by a user:

exportclassUser{firstName:string;lastName:string;getName():string{returnthis.lastName+' '+this.firstName;}}@Controller()exportclassUserController{post(@Body()user:User){console.log('saving user '+user.getName());}}

IfUser is an interface - then simple literal object will be created.If its a class - then instance of this class will be created.

This technique works with@Body,@Param,@QueryParam,@BodyParam, and other decorators.Learn more about class-transformer and how to handle more complex object constructionshere.This behaviour is enabled by default.If you want to disable it simply passclassTransformer: false to createExpressServer method. Alternatively you can disable transforming forindividual controllers or routes.

Controller Inheritance

Often your application may need to have an option to inherit controller from another to reuse code and avoid duplication.A good example of the use is the CRUD operations which can be hidden insideAbstractBaseController with the possibility to add new and overload methods, the template method pattern.

@Controller(`/product`)classProductControllerextendsAbstractControllerTemplate{}@Controller(`/category`)classCategoryControllerextendsAbstractControllerTemplate{}abstractclassAbstractControllerTemplate{  @Post()publiccreate(){}  @Get()publicread(){}  @Put()publicupdate(){}  @Delete()publicdelete(){}}

https://en.wikipedia.org/wiki/Template_method_pattern

Auto validating action params

Sometimes parsing a json object into instance of some class is not enough.E.g.class-transformer doesn't check whether the property's types are correct, so you can get runtime error if you rely on TypeScript type safe. Also you may want to validate the object to check e.g. whether the password string is long enough or entered e-mail is correct.

It can be done easily thanks to integration withclass-validator. This behaviour isenabled by default. If you want to disable it, you need to do it explicitly e.g. by passingvalidation: false option on application bootstrap:

import{createExpressServer}from'routing-controllers';createExpressServer({validation:false,}).listen(3000);

If you want to turn on the validation only for some params, not globally for every parameter, you can do this locally by settingvalidate: true option in parameter decorator options object:

@Post("/login")login(@Body({validate:true})user:User){}

Now you need to define the class which type will be used as type of controller's method param.Decorate the properties with appropriate validation decorators.

exportclassUser{  @IsEmail()email:string;  @MinLength(6)password:string;}

If you haven't used class-validator yet, you can learn how to use the decorators and handle more complex object validationhere.

Now, if you have specified a class type, your action params will be not only an instance of that class (with the data sent by a user) but they will be validated too, so you don't have to worry about eg. incorrect e-mail or too short password and manual checks every property in controller method body.

@Controller()exportclassUserController{  @Post('/login')login(@Body()user:User){console.log(`${user.email} is for 100% sure a valid e-mail address!`);console.log(`${user.password.length} is for 100% sure 6 chars or more!`);}}

If the param doesn't satisfy the requirements defined by class-validator decorators,an error will be thrown and captured by routing-controller, so the client will receive 400 Bad Request and JSON with nice detailedValidation errors array.

If you need special options for validation (groups, skipping missing properties, etc.) or transforming (groups, excluding prefixes, versions, etc.), you can pass them as global config asvalidation in createExpressServer method or as a localvalidate setting for method parameter -@Body({ validate: localOptions }).

This technique works not only with@Body but also with@Param,@QueryParam,@BodyParam and other decorators.

Using authorization features

Routing-controllers comes with two decorators helping you to organize authorization in your application.

@Authorized decorator

To make@Authorized decorator to work you need to setup special routing-controllers options:

import{createExpressServer,Action}from'routing-controllers';createExpressServer({authorizationChecker:async(action:Action,roles:string[])=>{// here you can use request/response objects from action// also if decorator defines roles it needs to access the action// you can use them to provide granular access check// checker must return either boolean (true or false)// either promise that resolves a boolean value// demo code:consttoken=action.request.headers['authorization'];constuser=awaitgetEntityManager().findOneByToken(User,token);if(user&&!roles.length)returntrue;if(user&&roles.find(role=>user.roles.indexOf(role)!==-1))returntrue;returnfalse;},}).listen(3000);

You can use@Authorized on controller actions:

@JsonController()exportclassSomeController{  @Authorized()  @Post('/questions')save(@Body()question:Question){}  @Authorized('POST_MODERATOR')// you can specify roles or array of roles  @Post('/posts')save(@Body()post:Post){}}

@CurrentUser decorator

To make@CurrentUser decorator to work you need to setup special routing-controllers options:

import{createExpressServer,Action}from'routing-controllers';createExpressServer({currentUserChecker:async(action:Action)=>{// here you can use request/response objects from action// you need to provide a user object that will be injected in controller actions// demo code:consttoken=action.request.headers['authorization'];returngetEntityManager().findOneByToken(User,token);},}).listen(3000);

You can use@CurrentUser on controller actions:

@JsonController()exportclassQuestionController{  @Get('/questions')all(@CurrentUser()user?:User, @Body()question:Question){}  @Post('/questions')save(@CurrentUser({required:true})user:User, @Body()post:Post){}}

If you mark@CurrentUser asrequired and currentUserChecker logic will return empty result,then routing-controllers will throw authorization required error.

Using DI container

routing-controllers supports a DI container out of the box. You can inject your services into your controllers,middlewares and error handlers. Container must be setup during application bootstrap.Here is example how to integrate routing-controllers withtypedi:

import{createExpressServer,useContainer}from'routing-controllers';import{Container}from'typedi';importpathfrom'path';// its important to set container before any operation you do with routing-controllers,// including importing controllersuseContainer(Container);// create and run servercreateExpressServer({controllers:[path.join(__dirname,'/controllers/*.js')],middlewares:[path.join(__dirname,'/middlewares/*.js')],interceptors:[path.join(__dirname,'/interceptors/*.js')],}).listen(3000);

That's it, now you can inject your services into your controllers:

@Controller()@Service()exportclassUsersController{constructor(privateuserRepository:UserRepository){}// ... controller actions}

Note: As TypeDI@0.9.0 won't create instances for unknown classes, you have to decorate your Controller as aService() as well. See#642

For other IoC providers that don't expose aget(xxx) function, you can create an IoC adapter usingIocAdapter like so:

// inversify-adapter.tsimport{IocAdapter}from'routing-controllers';import{Container}from'inversify';classInversifyAdapterimplementsIocAdapter{constructor(privatereadonlycontainer:Container){}get<T>(someClass:ClassConstructor<T>,action?:Action):T{constchildContainer=this.container.createChild();childContainer.bind(API_SYMBOLS.ClientIp).toConstantValue(action.context.ip);returnchildContainer.resolve<T>(someClass);}}

And then tell Routing Controllers to use it:

// Somewhere in your app startupimport{useContainer}from'routing-controllers';import{Container}from'inversify';import{InversifyAdapter}from'./inversify-adapter.ts';constcontainer=newContainer();constinversifyAdapter=newInversifyAdapter(container);useContainer(inversifyAdapter);

Custom parameter decorators

You can create your own parameter decorators.Here is simple example how "session user" can be implemented using custom decorators:

import{createParamDecorator}from'routing-controllers';exportfunctionUserFromSession(options?:{required?:boolean}){returncreateParamDecorator({required:options&&options.required ?true :false,value:action=>{consttoken=action.request.headers['authorization'];returndatabase.findUserByToken(token);},});}

And use it in your controller:

@JsonController()exportclassQuestionController{  @Post()save(@Body()question:Question, @UserFromSession({required:true})user:User){// here you'll have user authorized and you can safely save your question// in the case if user returned your undefined from the database and "required"// parameter was set, routing-controllers will throw you ParameterRequired error}}

Decorators Reference

Controller Decorators

SignatureExampleDescription
@Controller(baseRoute: string)@Controller("/users") class SomeControllerClass that is marked with this decorator is registered as controller and its annotated methods are registered as actions. Base route is used to concatenate it to all controller action routes.
@JsonController(baseRoute: string)@JsonController("/users") class SomeJsonControllerClass that is marked with this decorator is registered as controller and its annotated methods are registered as actions. Difference between @JsonController and @Controller is that @JsonController automatically converts results returned by controller to json objects (using JSON.parse) and response being sent to a client is sent with application/json content-type. Base route is used to concatenate it to all controller action routes.

Controller Action Decorators

SignatureExampleDescriptionexpress.js analogue
@Get(route: string|RegExp)@Get("/users") all()Methods marked with this decorator will register a request made with GET HTTP Method to a given route. In action options you can specify if action should response json or regular text response.app.get("/users", all)
@Post(route: string|RegExp)@Post("/users") save()Methods marked with this decorator will register a request made with POST HTTP Method to a given route. In action options you can specify if action should response json or regular text response.app.post("/users", save)
@Put(route: string|RegExp)@Put("/users/:id") update()Methods marked with this decorator will register a request made with PUT HTTP Method to a given route. In action options you can specify if action should response json or regular text response.app.put("/users/:id", update)
@Patch(route: string|RegExp)@Patch("/users/:id") patch()Methods marked with this decorator will register a request made with PATCH HTTP Method to a given route. In action options you can specify if action should response json or regular text response.app.patch("/users/:id", patch)
@Delete(route: string|RegExp)@Delete("/users/:id") delete()Methods marked with this decorator will register a request made with DELETE HTTP Method to a given route. In action options you can specify if action should response json or regular text response.app.delete("/users/:id", delete)
@Head(route: string|RegExp)@Head("/users/:id") head()Methods marked with this decorator will register a request made with HEAD HTTP Method to a given route. In action options you can specify if action should response json or regular text response.app.head("/users/:id", head)
@All(route: string|RegExp)@All("/users/me") rewrite()Methods marked with this decorator will register a request made with any HTTP Method to a given route. In action options you can specify if action should response json or regular text response.app.all("/users/me", rewrite)
@Method(methodName: string, route: string|RegExp)@Method("move", "/users/:id") move()Methods marked with this decorator will register a request made with givenmethodName HTTP Method to a given route. In action options you can specify if action should response json or regular text response.app.move("/users/:id", move)

Method Parameter Decorators

SignatureExampleDescriptionexpress.js analogue
@Req()getAll(@Req() request: Request)Injects a Request object.function (request, response)
@Res()getAll(@Res() response: Response)Injects a Response object.function (request, response)
@Ctx()getAll(@Ctx() context: Context)Injects a Context object (koa-specific)function (ctx) (koa-analogue)
@Param(name: string, options?: ParamOptions)get(@Param("id") id: number)Injects a router parameter.request.params.id
@Params()get(@Params() params: any)Injects all router parameters.request.params
@QueryParam(name: string, options?: ParamOptions)get(@QueryParam("id") id: number)Injects a query string parameter.request.query.id
@QueryParams()get(@QueryParams() params: any)Injects all query parameters.request.query
@HeaderParam(name: string, options?: ParamOptions)get(@HeaderParam("token") token: string)Injects a specific request headers.request.headers.token
@HeaderParams()get(@HeaderParams() params: any)Injects all request headers.request.headers
@CookieParam(name: string, options?: ParamOptions)get(@CookieParam("username") username: string)Injects a cookie parameter.request.cookie("username")
@CookieParams()get(@CookieParams() params: any)Injects all cookies.request.cookies
@Session()get(@Session() session: any)Injects the whole session object.request.session
@SessionParam(name: string)get(@SessionParam("user") user: User)Injects an object from session property.request.session.user
@State(name?: string)get(@State() session: StateType)Injects an object from the state (or the whole state).ctx.state (koa-analogue)
@Body(options?: BodyOptions)post(@Body() body: any)Injects a body. In parameter options you can specify body parser middleware options.request.body
@BodyParam(name: string, options?: ParamOptions)post(@BodyParam("name") name: string)Injects a body parameter.request.body.name
@UploadedFile(name: string, options?: UploadOptions)post(@UploadedFile("filename") file: any)Injects uploaded file from the response. In parameter options you can specify underlying uploader middleware options.request.file.file (using multer)
@UploadedFiles(name: string, options?: UploadOptions)post(@UploadedFiles("filename") files: any[])Injects all uploaded files from the response. In parameter options you can specify underlying uploader middleware options.request.files (using multer)

Middleware and Interceptor Decorators

SignatureExampleDescription
@Middleware({ type: "before"|"after" })@Middleware({ type: "before" }) class SomeMiddlewareRegisters a global middleware.
@UseBefore()@UseBefore(CompressionMiddleware)Uses given middleware before action is being executed.
@UseAfter()@UseAfter(CompressionMiddleware)Uses given middleware after action is being executed.
@Interceptor()@Interceptor() class SomeInterceptorRegisters a global interceptor.
@UseInterceptor()@UseInterceptor(BadWordsInterceptor)Intercepts result of the given controller/action and replaces some values of it.

Other Decorators

SignatureExampleDescription
@Authorized(roles?: string|string[])@Authorized("SUPER_ADMIN") get()Checks if user is authorized and has given roles on a given route.authorizationChecker should be defined in routing-controllers options.
@CurrentUser(options?: { required?: boolean })get(@CurrentUser({ required: true }) user: User)Injects currently authorized user.currentUserChecker should be defined in routing-controllers options.
@Header(headerName: string, headerValue: string)@Header("Cache-Control", "private") get()Allows to explicitly set any HTTP header returned in the response.
@ContentType(contentType: string)@ContentType("text/csv") get()Allows to explicitly set HTTP Content-Type returned in the response.
@Location(url: string)@Location("http://github.com") get()Allows to explicitly set HTTP Location header returned in the response.
@Redirect(url: string)@Redirect("http://github.com") get()Allows to explicitly set HTTP Redirect header returned in the response.
@HttpCode(code: number)@HttpCode(201) post()Allows to explicitly set HTTP code to be returned in the response.
@OnNull(codeOrError: number|Error)@OnNull(201) post()Sets a given HTTP code when controller action returned null.
@OnUndefined(codeOrError: number|Error)@OnUndefined(201) post()Sets a given HTTP code when controller action returned undefined.
@ResponseClassTransformOptions(options: ClassTransformOptions)@ResponseClassTransformOptions({/*...*/}) get()Sets options to be passed to class-transformer when it used for classToPlain a response result.
@Render(template: string)@Render("user-list.html") get()Renders a given html template. Data returned by a controller serve as template variables.

Samples

Release notes

See information about breaking changes and release noteshere.

About

Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage in Express / Koa using TypeScript and Routing Controllers Framework.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors76

Languages


[8]ページ先頭

©2009-2025 Movatter.jp