Gathering detailed insights and metrics for routing-controllers
Gathering detailed insights and metrics for routing-controllers
Gathering detailed insights and metrics for routing-controllers
Gathering detailed insights and metrics for routing-controllers
Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage in Express / Koa using TypeScript and Routing Controllers Framework.
npm install routing-controllers
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
4,415 Stars
1,008 Commits
394 Forks
52 Watching
10 Branches
75 Contributors
Updated on 27 Nov 2024
TypeScript (99.2%)
JavaScript (0.77%)
HTML (0.03%)
Cumulative downloads
Total Downloads
Last day
-7%
10,314
Compared to previous day
Last week
9.2%
57,134
Compared to previous week
Last month
11.6%
233,805
Compared to previous month
Last year
-3.1%
2,611,532
Compared to previous year
English | ä¸æ–‡
Allows to create controller classes with methods as actions that handle requests. You can use routing-controllers with express.js or koa.js.
Install module:
npm install routing-controllers
reflect-metadata
shim is required:
npm install reflect-metadata
and make sure to import it before you use routing-controllers:
1import 'reflect-metadata';
Install framework:
a. If you want to use routing-controllers with express.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 with koa 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
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.
Its important to set these options in tsconfig.json
file of your project:
1{ 2 "emitDecoratorMetadata": true, 3 "experimentalDecorators": true 4}
Create a file UserController.ts
1import 'reflect-metadata'; 2import { Controller, Param, Body, Get, Post, Put, Delete } from 'routing-controllers'; 3 4@Controller() 5export class UserController { 6 @Get('/users') 7 getAll() { 8 return 'This action returns all users'; 9 } 10 11 @Get('/users/:id') 12 getOne(@Param('id') id: number) { 13 return 'This action returns user #' + id; 14 } 15 16 @Post('/users') 17 post(@Body() user: any) { 18 return 'Saving user...'; 19 } 20 21 @Put('/users/:id') 22 put(@Param('id') id: number, @Body() user: any) { 23 return 'Updating a user...'; 24 } 25 26 @Delete('/users/:id') 27 remove(@Param('id') id: number) { 28 return 'Removing user...'; 29 } 30}
This class will register routes specified in method decorators in your server framework (express.js or koa).
Create a file app.ts
1// this shim is required 2import { createExpressServer } from 'routing-controllers'; 3import { UserController } from './UserController'; 4 5// creates express app, registers all controller routes and returns you express app instance 6const app = createExpressServer({ 7 controllers: [UserController], // we specify controllers we want to use 8}); 9 10// run express application on port 3000 11app.listen(3000);
if you are koa user you just need to use
createKoaServer
instead ofcreateExpressServer
Open in browser http://localhost:3000/users
. You will see This action returns all users
in your browser.
If you open http://localhost:3000/users/1
you will see This action returns user #1
.
If you are designing a REST API where your endpoints always receive and return JSON then
you can use @JsonController
decorator instead of @Controller
.
This will guarantee you that data returned by your controller actions always be transformed to JSON
and Content-Type
header will be always set to application/json
.
It will also guarantee application/json
header is understood from the requests and the body parsed as JSON:
1import { JsonController, Param, Body, Get, Post, Put, Delete } from 'routing-controllers'; 2 3@JsonController() 4export class UserController { 5 @Get('/users') 6 getAll() { 7 return userRepository.findAll(); 8 } 9 10 @Get('/users/:id') 11 getOne(@Param('id') id: number) { 12 return userRepository.findById(id); 13 } 14 15 @Post('/users') 16 post(@Body() user: User) { 17 return userRepository.insert(user); 18 } 19}
You can return a promise in the controller, and it will wait until promise resolved and return promise result in a response body.
1import { JsonController, Param, Body, Get, Post, Put, Delete } from 'routing-controllers'; 2 3@JsonController() 4export class UserController { 5 @Get('/users') 6 getAll() { 7 return userRepository.findAll(); 8 } 9 10 @Get('/users/:id') 11 getOne(@Param('id') id: number) { 12 return userRepository.findById(id); 13 } 14 15 @Post('/users') 16 post(@Body() user: User) { 17 return userRepository.insert(user); 18 } 19 20 @Put('/users/:id') 21 put(@Param('id') id: number, @Body() user: User) { 22 return userRepository.updateById(id, user); 23 } 24 25 @Delete('/users/:id') 26 remove(@Param('id') id: number) { 27 return userRepository.removeById(id); 28 } 29}
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.
1import { Controller, Req, Res, Get } from 'routing-controllers'; 2 3@Controller() 4export class UserController { 5 @Get('/users') 6 getAllUsers(@Req() request: any, @Res() response: any) { 7 return response.send('Hello response!'); 8 } 9 10 @Get('/posts') 11 getAllPosts(@Req() request: any, @Res() response: any) { 12 // some response functions don't return the response object, 13 // so it needs to be returned explicitly 14 response.redirect('/users'); 15 16 return response; 17 } 18}
@Req()
decorator injects you a Request
object, and @Res()
decorator injects you a Response
object.
If you have installed typings, you can use their types:
1import { Request, Response } from 'express'; 2import { Controller, Req, Res, Get } from 'routing-controllers'; 3 4@Controller() 5export class UserController { 6 @Get('/users') 7 getAll(@Req() request: Request, @Res() response: Response) { 8 return response.send('Hello response!'); 9 } 10}
note: koa users can also use
@Ctx() context
to inject koa's Context object.
If you have, or if you want to create and configure express app separately,
you can use useExpressServer
instead of createExpressServer
function:
1import { useExpressServer } from 'routing-controllers'; 2 3let express = require('express'); // or you can import it if you have installed typings 4let app = express(); // your created express server 5// app.use() // you can configure it the way you want 6useExpressServer(app, { 7 // register created express server in routing-controllers 8 controllers: [UserController], // and configure it the way you need (controllers, validation, etc.) 9}); 10app.listen(3000); // run your express server
koa users must use
useKoaServer
instead ofuseExpressServer
You can load all controllers from directories, by specifying array of directories in options of
createExpressServer
or useExpressServer
:
1import { createExpressServer } from 'routing-controllers'; 2import path from 'path'; 3 4createExpressServer({ 5 controllers: [path.join(__dirname + '/controllers/*.js')], 6}).listen(3000); // register controllers routes in our express application
koa users must use
createKoaServer
instead ofcreateExpressServer
If you want to prefix all your routes, e.g. /api
you can use routePrefix
option:
1import { createExpressServer } from 'routing-controllers'; 2import { UserController } from './controller/UserController'; 3 4createExpressServer({ 5 routePrefix: '/api', 6 controllers: [UserController], 7}).listen(3000);
koa users must use
createKoaServer
instead ofcreateExpressServer
You can prefix all specific controller's actions with base route:
1@Controller('/users') 2export class UserController { 3 // ... 4}
You can use @Param
decorator to inject parameters in your controller actions:
1@Get("/users/:id") 2getOne(@Param("id") id: number) { // id will be automatically casted to "number" because it has type number 3}
If you want to inject all parameters use @Params()
decorator.
To inject query parameters, use @QueryParam
decorator:
1@Get("/users") 2getUsers(@QueryParam("limit") limit: number) { 3}
You can use isArray
option to get a query param array. This will cast the query param :
1@Get("/users/by-multiple-ids") 2getUsers(@QueryParam("ids", { isArray: true}) ids: string[]) { 3}
GET /users/by-multiple-ids?ids=a
→ ids = ['a']
GET /users/by-multiple-ids?ids=a&ids=b
→ ids = ['a', 'b']
You can combine use isArray
option with type
option to get a query param array of one type. This will cast the query param :
1@Get("/users/by-multiple-ids") 2getUsers(@QueryParam("ids", { isArray: true, type: Number}) ids: number[]) { 3}
GET /users/by-multiple-ids?ids=1
→ ids = [1]
GET /users/by-multiple-ids?ids=1&ids=3.5
→ ids = [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.
1enum Roles { 2 Admin = "admin", 3 User = "user", 4 Guest = "guest", 5} 6 7class GetUsersQuery { 8 9 @IsPositive() 10 limit: number; 11 12 @IsAlpha() 13 city: string; 14 15 @IsEnum(Roles) 16 role: Roles; 17 18 @IsBoolean() 19 isActive: boolean; 20 21 @IsArray() 22 @IsNumber(undefined, { each: true }) 23 @Type(() => Number) 24 ids: number[]; 25} 26 27@Get("/users") 28getUsers(@QueryParams() query: GetUsersQuery) { 29 // here you can access query.role, query.limit 30 // and others valid query parameters 31 // query.ids will be an array, of numbers, even with one element 32}
To inject request body, use @Body
decorator:
1@Post("/users") 2saveUser(@Body() user: User) { 3}
If you specify a class type to parameter that is decorated with @Body()
,
routing-controllers will use class-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.
To inject request body parameter, use @BodyParam
decorator:
1@Post("/users") 2saveUser(@BodyParam("name") userName: string) { 3}
To inject request header parameter, use @HeaderParam
decorator:
1@Post("/users") 2saveUser(@HeaderParam("authorization") token: string) { 3}
If you want to inject all header parameters use @HeaderParams()
decorator.
To get a cookie parameter, use @CookieParam
decorator:
1@Get("/users") 2getUsers(@CookieParam("username") username: string) { 3}
If you want to inject all header parameters use @CookieParams()
decorator.
To inject a session value, use @SessionParam
decorator:
1@Get("/login") 2savePost(@SessionParam("user") user: User, @Body() post: Post) {}
If you want to inject the main session object, use @Session()
without any parameters.
1@Get("/login") 2savePost(@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:
1action(@Session("user", { required: false }) user: User) {}
Express uses express-session / Koa uses koa-session or koa-generic-session to handle session, so firstly you have to install it manually to use @Session
decorator.
To inject a state parameter use @State
decorator:
1@Get("/login") 2savePost(@State("user") user: User, @Body() post: Post) { 3}
If you want to inject the whole state object use @State()
without any parameters.
This feature is only supported by Koa.
To inject uploaded file, use @UploadedFile
decorator:
1@Post("/files") 2saveFile(@UploadedFile("fileName") file: any) { 3}
You can also specify uploading options to multer this way:
1// to keep code clean better to extract this function into separate file 2export const fileUploadOptions = () => ({ 3 storage: multer.diskStorage({ 4 destination: (req: any, file: any, cb: any) => { ... 5 }, 6 filename: (req: any, file: any, cb: any) => { ... 7 } 8 }), 9 fileFilter: (req: any, file: any, cb: any) => { ... 10 }, 11 limits: { 12 fieldNameSize: 255, 13 fileSize: 1024 * 1024 * 2 14 } 15}); 16 17// use options this way: 18@Post("/files") 19saveFile(@UploadedFile("fileName", { options: fileUploadOptions }) file: any) { 20}
To inject all uploaded files use @UploadedFiles
decorator instead.
Routing-controllers uses multer to handle file uploads.
You can install multer's file definitions via typings, and use files: File[]
type instead of any[]
.
To make any parameter required, simply pass a required: true
flag in its options:
1@Post("/users") 2save(@Body({ required: true }) user: any) { 3 // your method will not be executed if user is not sent in a request 4}
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.
If you specify a class type to parameter that is decorated with parameter decorator, routing-controllers will use class-transformer to create instance of that class type. More info about this feature is available here.
You can specify a custom ContentType header:
1@Get("/users") 2@ContentType("text/csv") 3getUsers() { 4 // ... 5}
You can set a Location header for any action:
1@Get("/users") 2@Location("http://github.com") 3getUsers() { 4 // ... 5}
You can set a Redirect header for any action:
1@Get("/users") 2@Redirect("http://github.com") 3getUsers() { 4 // ... 5}
You can override the Redirect header by returning a string value:
1@Get("/users") 2@Redirect("http://github.com") 3getUsers() { 4 return "https://www.google.com"; 5}
You can use template to generate the Redirect header:
1@Get("/users") 2@Redirect("http://github.com/:owner/:repo") 3getUsers() { 4 return { 5 owner: "typestack", 6 repo: "routing-controllers" 7 }; 8}
You can explicitly set a returned HTTP code for any action:
1@HttpCode(201) 2@Post("/users") 3saveUser(@Body() user: User) { 4 // ... 5}
If your controller returns void
or Promise<void>
or undefined
it will throw you 404 error.
To prevent this if you need to specify what status code you want to return using @OnUndefined
decorator.
1@Delete("/users/:id") 2@OnUndefined(204) 3async remove(@Param("id") id: number): Promise<void> { 4 return userRepository.removeById(id); 5}
@OnUndefined
is also useful when you return some object which can or cannot be undefined.
In this example findOneById
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.
1@Get("/users/:id") 2@OnUndefined(404) 3getOne(@Param("id") id: number) { 4 return userRepository.findOneById(id); 5}
You can also specify error class you want to use if it returned undefined:
1import { HttpError } from 'routing-controllers'; 2 3export class UserNotFoundError extends HttpError { 4 constructor() { 5 super(404, 'User not found!'); 6 } 7}
1@Get("/users/:id") 2@OnUndefined(UserNotFoundError) 3saveUser(@Param("id") id: number) { 4 return userRepository.findOneById(id); 5}
If controller action returns null
you can use @OnNull
decorator instead.
You can set any custom header in a response:
1@Get("/users/:id") 2@Header("Cache-Control", "none") 3getOne(@Param("id") id: number) { 4 // ... 5}
If you are using server-side rendering you can render any template:
1@Get("/users/:id") 2@Render("index.html") 3getOne() { 4 return { 5 param1: "these params are used", 6 param2: "in templating engine" 7 }; 8}
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-views, koa-views is the only render middleware that has been tested.
If you want to return errors with specific error codes, there is an easy way:
1@Get("/users/:id") 2getOne(@Param("id") id: number) { 3 4 const user = this.userRepository.findOneById(id); 5 if (!user) 6 throw new NotFoundError(`User was not found.`); // message is optional 7 8 return user; 9}
Now, when user won't be found with requested id, response will be with http status code 404 and following content:
1{ 2 "name": "NotFoundError", 3 "message": "User was not found." 4}
There are set of prepared errors you can use:
You can also create and use your own errors by extending HttpError
class.
To define the data returned to the client, you could define a toJSON method in your error.
1class DbError extends HttpError { 2 public operationName: string; 3 public args: any[]; 4 5 constructor(operationName: string, args: any[] = []) { 6 super(500); 7 Object.setPrototypeOf(this, DbError.prototype); 8 this.operationName = operationName; 9 this.args = args; // can be used for internal logging 10 } 11 12 toJSON() { 13 return { 14 status: this.httpCode, 15 failedOperation: this.operationName, 16 }; 17 } 18}
Since CORS is a feature that is used almost in any web-api application, you can enable it in routing-controllers options.
1import { createExpressServer } from 'routing-controllers'; 2import { UserController } from './UserController'; 3 4const app = createExpressServer({ 5 cors: true, 6 controllers: [UserController], 7}); 8 9app.listen(3000);
To use cors you need to install its module.
For express its npm i cors
, for koa its npm i @koa/cors
.
You can pass cors options as well:
1import { createExpressServer } from 'routing-controllers'; 2import { UserController } from './UserController'; 3 4const app = createExpressServer({ 5 cors: { 6 // options from cors documentation 7 }, 8 controllers: [UserController], 9}); 10 11app.listen(3000);
You can override default status code in routing-controllers options.
1import { createExpressServer } from 'routing-controllers'; 2import { UserController } from './UserController'; 3 4const app = createExpressServer({ 5 defaults: { 6 //with this option, null will return 404 by default 7 nullResultCode: 404, 8 9 //with this option, void or Promise<void> will return 204 by default 10 undefinedResultCode: 204, 11 12 paramOptions: { 13 //with this option, argument will be required by default 14 required: true, 15 }, 16 }, 17}); 18 19app.listen(3000);
To disable class-transformer
on a per-controller or per-route basis, use the transformRequest
and transformResponse
options on your controller and route decorators:
1@Controller("/users", {transformRequest: false, transformResponse: false}) 2export class UserController { 3 4 @Get("/", {transformResponse: true}) { 5 // route option overrides controller option 6 } 7}
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.
There are multiple ways to use middleware. For example, lets try to use compression middleware:
Install compression middleware: npm install compression
To use middleware per-action:
1import { Controller, Get, UseBefore } from "routing-controllers"; 2let compression = require("compression"); 3 4// ... 5 6@Get("/users/:id") 7@UseBefore(compression()) 8getOne(@Param("id") id: number) { 9 // ... 10}
This way compression middleware will be applied only for getOne
controller action,
and will be executed before action execution.
To execute middleware after action use @UseAfter
decorator instead.
To use middleware per-controller:
1import { Controller, UseBefore } from 'routing-controllers'; 2let compression = require('compression'); 3 4@Controller() 5@UseBefore(compression()) 6export class UserController {}
This way compression middleware will be applied for all actions of the UserController
controller,
and will be executed before its action execution. Same way you can use @UseAfter
decorator here.
If you want to use compression module globally for all controllers you can simply register it during bootstrap:
1import { createExpressServer } from 'routing-controllers'; 2import { UserController } from './UserController'; // we need to "load" our controller before call createExpressServer. this is required 3let compression = require('compression'); 4let app = createExpressServer({ 5 controllers: [UserController], 6}); // creates express app, registers all controller routes and returns you express app instance 7app.use(compression()); 8app.listen(3000); // run express application
Alternatively, you can create a custom global middleware and simply delegate its execution to the compression module.
Here is example of creating middleware for express.js:
There are two ways of creating middleware:
First, you can create a simple middleware function:
1export function loggingMiddleware(request: any, response: any, next?: (err?: any) => any): any { 2 console.log('do something...'); 3 next(); 4}
Second you can create a class:
1import { ExpressMiddlewareInterface } from 'routing-controllers'; 2 3export class MyMiddleware implements ExpressMiddlewareInterface { 4 // interface implementation is optional 5 6 use(request: any, response: any, next?: (err?: any) => any): any { 7 console.log('do something...'); 8 next(); 9 } 10}
Then you can use them this way:
1import { Controller, UseBefore } from 'routing-controllers'; 2import { MyMiddleware } from './MyMiddleware'; 3import { loggingMiddleware } from './loggingMiddleware'; 4 5@Controller() 6@UseBefore(MyMiddleware) 7@UseAfter(loggingMiddleware) 8export class UserController {}
or per-action:
1@Get("/users/:id") 2@UseBefore(MyMiddleware) 3@UseAfter(loggingMiddleware) 4getOne(@Param("id") id: number) { 5 // ... 6}
@UseBefore
executes middleware before controller action.
@UseAfter
executes middleware after each controller action.
Here is example of creating middleware for koa.js:
There are two ways of creating middleware:
First, you can create a simple middleware function:
1export function use(context: any, next: (err?: any) => Promise<any>): Promise<any> { 2 console.log('do something before execution...'); 3 return next() 4 .then(() => { 5 console.log('do something after execution'); 6 }) 7 .catch(error => { 8 console.log('error handling is also here'); 9 }); 10}
Second you can create a class:
1import { KoaMiddlewareInterface } from 'routing-controllers'; 2 3export class MyMiddleware implements KoaMiddlewareInterface { 4 // interface implementation is optional 5 6 use(context: any, next: (err?: any) => Promise<any>): Promise<any> { 7 console.log('do something before execution...'); 8 return next() 9 .then(() => { 10 console.log('do something after execution'); 11 }) 12 .catch(error => { 13 console.log('error handling is also here'); 14 }); 15 } 16}
Then you can them this way:
1import { Controller, UseBefore } from 'routing-controllers'; 2import { MyMiddleware } from './MyMiddleware'; 3import { loggingMiddleware } from './loggingMiddleware'; 4 5@Controller() 6@UseBefore(MyMiddleware) 7@UseAfter(loggingMiddleware) 8export class UserController {}
or per-action:
1@Get("/users/:id") 2@UseBefore(MyMiddleware) 3@UseAfter(loggingMiddleware) 4getOne(@Param("id") id: number) { 5 // ... 6}
@UseBefore
executes middleware before controller action.
@UseAfter
executes middleware after each controller action.
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.
1import { Middleware, ExpressMiddlewareInterface } from 'routing-controllers'; 2 3@Middleware({ type: 'before' }) 4export class LoggingMiddleware implements ExpressMiddlewareInterface { 5 use(request: any, response: any, next: (err: any) => any): void { 6 console.log('do something...'); 7 next(); 8 } 9}
To enable this middleware, specify it during routing-controllers initialization:
1import { createExpressServer } from 'routing-controllers'; 2import { UserController } from './UserController'; 3import { LoggingMiddleware } from './LoggingMiddleware'; 4 5createExpressServer({ 6 controllers: [UserController], 7 middlewares: [LoggingMiddleware], 8}).listen(3000);
Error handlers are specific only to express.
Error handlers work same way as middlewares, but implement ExpressErrorMiddlewareInterface
:
Create a class that implements the ErrorMiddlewareInterface
interface:
1import { Middleware, ExpressErrorMiddlewareInterface } from 'routing-controllers'; 2 3@Middleware({ type: 'after' }) 4export class CustomErrorHandler implements ExpressErrorMiddlewareInterface { 5 error(error: any, request: any, response: any, next: (err: any) => any) { 6 console.log('do something...'); 7 next(); 8 } 9}
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 specifying defaultErrorHandler
option in createExpressServer or useExpressServer:
1createExpressServer({ 2 defaultErrorHandler: false, // disable default error handler, only if you have your own error handler 3}).listen(3000);
Also you can load middlewares from directories. Also you can use glob patterns:
1import { createExpressServer } from 'routing-controllers'; 2import path from 'path'; 3 4createExpressServer({ 5 controllers: [path.join(__dirname, '/controllers/**/*.js')], 6 middlewares: [path.join(__dirname, '/middlewares/**/*.js')], 7 interceptors: [path.join(__dirname, '/interceptors/**/*.js')], 8}).listen(3000);
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.
The easiest way is to use functions directly passed to @UseInterceptor
of the action.
1import { Get, Param, UseInterceptor } from "routing-controllers"; 2 3// ... 4 5@Get("/users") 6@UseInterceptor(function(action: Action, content: any) { 7 // here you have content returned by this action. you can replace something 8 // in it and return a replaced result. replaced result will be returned to the user 9 return content.replace(/Mike/gi, "Michael"); 10}) 11getOne(@Param("id") id: number) { 12 return "Hello, I am Mike!"; // client will get a "Hello, I am Michael!" response. 13}
You can use @UseInterceptor
per-action, or per-controller.
If its used per-controller then interceptor will apply to all controller actions.
You can also create a class and use it with @UseInterceptor
decorator:
1import { Interceptor, InterceptorInterface, Action } from 'routing-controllers'; 2 3export class NameCorrectionInterceptor implements InterceptorInterface { 4 intercept(action: Action, content: any) { 5 return content.replace(/Mike/gi, 'Michael'); 6 } 7}
And use it in your controllers this way:
1import { Get, Param, UseInterceptor } from "routing-controllers"; 2import { NameCorrectionInterceptor } from "./NameCorrectionInterceptor"; 3 4// ... 5 6@Get("/users") 7@UseInterceptor(NameCorrectionInterceptor) 8getOne(@Param("id") id: number) { 9 return "Hello, I am Mike!"; // client will get a "Hello, I am Michael!" response. 10}
You can create interceptors that will affect all controllers in your project by creating interceptor class
and mark it with @Interceptor
decorator:
1import { Interceptor, InterceptorInterface, Action } from 'routing-controllers'; 2 3@Interceptor() 4export class NameCorrectionInterceptor implements InterceptorInterface { 5 intercept(action: Action, content: any) { 6 return content.replace(/Mike/gi, 'Michael'); 7 } 8}
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 using class-transformer.
To use it simply specify a classTransformer: true
option on application bootstrap:
1import { createExpressServer } from 'routing-controllers'; 2 3createExpressServer({ 4 classTransformer: true, 5}).listen(3000);
Now, when you parse your action params, if you have specified a class, routing-controllers will create you a class of that instance with the data sent by a user:
1export class User { 2 firstName: string; 3 lastName: string; 4 5 getName(): string { 6 return this.lastName + ' ' + this.firstName; 7 } 8} 9 10@Controller() 11export class UserController { 12 post(@Body() user: User) { 13 console.log('saving user ' + user.getName()); 14 } 15}
If User
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 constructions here.
This behaviour is enabled by default.
If you want to disable it simply pass classTransformer: false
to createExpressServer method. Alternatively you can disable transforming for individual controllers or routes.
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 inside AbstractBaseController
with the possibility to add new and overload methods, the template method pattern.
1@Controller(`/product`) 2class ProductController extends AbstractControllerTemplate {} 3@Controller(`/category`) 4class CategoryController extends AbstractControllerTemplate {} 5abstract class AbstractControllerTemplate { 6 @Post() 7 public create() {} 8 9 @Get() 10 public read() {} 11 12 @Put() 13 public update() {} 14 15 @Delete() 16 public delete() {} 17}
https://en.wikipedia.org/wiki/Template_method_pattern
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 with class-validator. This behaviour is enabled by default. If you want to disable it, you need to do it explicitly e.g. by passing validation: false
option on application bootstrap:
1import { createExpressServer } from 'routing-controllers'; 2 3createExpressServer({ 4 validation: false, 5}).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 setting validate: true
option in parameter decorator options object:
1@Post("/login") 2login(@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.
1export class User { 2 @IsEmail() 3 email: string; 4 5 @MinLength(6) 6 password: string; 7}
If you haven't used class-validator yet, you can learn how to use the decorators and handle more complex object validation here.
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.
1@Controller() 2export class UserController { 3 @Post('/login') 4 login(@Body() user: User) { 5 console.log(`${user.email} is for 100% sure a valid e-mail address!`); 6 console.log(`${user.password.length} is for 100% sure 6 chars or more!`); 7 } 8}
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 detailed Validation 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 as validation
in createExpressServer method or as a local validate
setting for method parameter - @Body({ validate: localOptions })
.
This technique works not only with @Body
but also with @Param
, @QueryParam
, @BodyParam
and other decorators.
Routing-controllers comes with two decorators helping you to organize authorization in your application.
@Authorized
decoratorTo make @Authorized
decorator to work you need to setup special routing-controllers options:
1import { createExpressServer, Action } from 'routing-controllers'; 2 3createExpressServer({ 4 authorizationChecker: async (action: Action, roles: string[]) => { 5 // here you can use request/response objects from action 6 // also if decorator defines roles it needs to access the action 7 // you can use them to provide granular access check 8 // checker must return either boolean (true or false) 9 // either promise that resolves a boolean value 10 // demo code: 11 const token = action.request.headers['authorization']; 12 13 const user = await getEntityManager().findOneByToken(User, token); 14 if (user && !roles.length) return true; 15 if (user && roles.find(role => user.roles.indexOf(role) !== -1)) return true; 16 17 return false; 18 }, 19}).listen(3000);
You can use @Authorized
on controller actions:
1@JsonController() 2export class SomeController { 3 @Authorized() 4 @Post('/questions') 5 save(@Body() question: Question) {} 6 7 @Authorized('POST_MODERATOR') // you can specify roles or array of roles 8 @Post('/posts') 9 save(@Body() post: Post) {} 10}
@CurrentUser
decoratorTo make @CurrentUser
decorator to work you need to setup special routing-controllers options:
1import { createExpressServer, Action } from 'routing-controllers'; 2 3createExpressServer({ 4 currentUserChecker: async (action: Action) => { 5 // here you can use request/response objects from action 6 // you need to provide a user object that will be injected in controller actions 7 // demo code: 8 const token = action.request.headers['authorization']; 9 return getEntityManager().findOneByToken(User, token); 10 }, 11}).listen(3000);
You can use @CurrentUser
on controller actions:
1@JsonController() 2export class QuestionController { 3 @Get('/questions') 4 all(@CurrentUser() user?: User, @Body() question: Question) {} 5 6 @Post('/questions') 7 save(@CurrentUser({ required: true }) user: User, @Body() post: Post) {} 8}
If you mark @CurrentUser
as required
and currentUserChecker logic will return empty result,
then routing-controllers will throw authorization required error.
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 with typedi:
1import { createExpressServer, useContainer } from 'routing-controllers'; 2import { Container } from 'typedi'; 3import path from 'path'; 4 5// its important to set container before any operation you do with routing-controllers, 6// including importing controllers 7useContainer(Container); 8 9// create and run server 10createExpressServer({ 11 controllers: [path.join(__dirname, '/controllers/*.js')], 12 middlewares: [path.join(__dirname, '/middlewares/*.js')], 13 interceptors: [path.join(__dirname, '/interceptors/*.js')], 14}).listen(3000);
That's it, now you can inject your services into your controllers:
1@Controller() 2@Service() 3export class UsersController { 4 constructor(private userRepository: UserRepository) {} 5 6 // ... controller actions 7}
Note: As TypeDI@0.9.0 won't create instances for unknown classes, you have to decorate your Controller as a Service()
as well. See #642
For other IoC providers that don't expose a get(xxx)
function, you can create an IoC adapter using IocAdapter
like so:
1// inversify-adapter.ts 2import { IocAdapter } from 'routing-controllers'; 3import { Container } from 'inversify'; 4 5class InversifyAdapter implements IocAdapter { 6 constructor(private readonly container: Container) {} 7 8 get<T>(someClass: ClassConstructor<T>, action?: Action): T { 9 const childContainer = this.container.createChild(); 10 childContainer.bind(API_SYMBOLS.ClientIp).toConstantValue(action.context.ip); 11 return childContainer.resolve<T>(someClass); 12 } 13}
And then tell Routing Controllers to use it:
1// Somewhere in your app startup 2import { useContainer } from 'routing-controllers'; 3import { Container } from 'inversify'; 4import { InversifyAdapter } from './inversify-adapter.ts'; 5 6const container = new Container(); 7const inversifyAdapter = new InversifyAdapter(container); 8useContainer(inversifyAdapter);
You can create your own parameter decorators. Here is simple example how "session user" can be implemented using custom decorators:
1import { createParamDecorator } from 'routing-controllers'; 2 3export function UserFromSession(options?: { required?: boolean }) { 4 return createParamDecorator({ 5 required: options && options.required ? true : false, 6 value: action => { 7 const token = action.request.headers['authorization']; 8 return database.findUserByToken(token); 9 }, 10 }); 11}
And use it in your controller:
1@JsonController() 2export class QuestionController { 3 @Post() 4 save(@Body() question: Question, @UserFromSession({ required: true }) user: User) { 5 // here you'll have user authorized and you can safely save your question 6 // in the case if user returned your undefined from the database and "required" 7 // parameter was set, routing-controllers will throw you ParameterRequired error 8 } 9}
Signature | Example | Description |
---|---|---|
@Controller(baseRoute: string) | @Controller("/users") class SomeController | Class 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 SomeJsonController | Class 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. |
Signature | Example | Description | express.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 given methodName 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) |
Signature | Example | Description | express.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) |
Signature | Example | Description |
---|---|---|
@Middleware({ type: "before"|"after" }) | @Middleware({ type: "before" }) class SomeMiddleware | Registers 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 SomeInterceptor | Registers a global interceptor. |
@UseInterceptor() | @UseInterceptor(BadWordsInterceptor) | Intercepts result of the given controller/action and replaces some values of it. |
Signature | Example | Description |
---|---|---|
@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. |
See information about breaking changes and release notes here.
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
Found 5/6 approved changesets -- score normalized to 8
Reason
7 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 7
Reason
4 existing vulnerabilities detected
Details
Reason
dependency not pinned by hash detected -- score normalized to 4
Details
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
project is not fuzzed
Details
Reason
security policy file not detected
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More