Code Monkey home page Code Monkey logo

routing-controllers's Introduction

routing-controllers

Build Status codecov npm version Dependency Status

English | 中文

Allows to create controller classes with methods as actions that handle requests. You can use routing-controllers with express.js or koa.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 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

  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 in tsconfig.json file of your project:

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

Example of usage

  1. Create a file UserController.ts

    import 'reflect-metadata';
    import { Controller, Param, Body, Get, Post, Put, Delete } from 'routing-controllers';
    
    @Controller()
    export class UserController {
      @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 file app.ts

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

    if you are koa user you just need to use createKoaServer instead of createExpressServer

  3. 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.

More examples

Working with json

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:

import { JsonController, Param, Body, Get, Post, Put, Delete } from 'routing-controllers';

@JsonController()
export class UserController {
  @Get('/users')
  getAll() {
    return userRepository.findAll();
  }

  @Get('/users/:id')
  getOne(@Param('id') id: number) {
    return userRepository.findById(id);
  }

  @Post('/users')
  post(@Body() user: User) {
    return userRepository.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()
export class UserController {
  @Get('/users')
  getAll() {
    return userRepository.findAll();
  }

  @Get('/users/:id')
  getOne(@Param('id') id: number) {
    return userRepository.findById(id);
  }

  @Post('/users')
  post(@Body() user: User) {
    return userRepository.insert(user);
  }

  @Put('/users/:id')
  put(@Param('id') id: number, @Body() user: User) {
    return userRepository.updateById(id, user);
  }

  @Delete('/users/:id')
  remove(@Param('id') id: number) {
    return userRepository.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()
export class UserController {
  @Get('/users')
  getAllUsers(@Req() request: any, @Res() response: any) {
    return response.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 explicitly
    response.redirect('/users');

    return response;
  }
}

@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:

import { Request, Response } from 'express';
import { Controller, Req, Res, Get } from 'routing-controllers';

@Controller()
export class UserController {
  @Get('/users')
  getAll(@Req() request: Request, @Res() response: Response) {
    return response.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 use useExpressServer instead of createExpressServer function:

import { useExpressServer } from 'routing-controllers';

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

koa users must use useKoaServer instead of useExpressServer

Load all controllers from the given directory

You can load all controllers from directories, by specifying array of directories in options of createExpressServer or useExpressServer:

import { createExpressServer } from 'routing-controllers';
import path from 'path';

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

koa users must use createKoaServer instead of createExpressServer

Prefix all controllers routes

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

import { createExpressServer } from 'routing-controllers';
import { UserController } from './controller/UserController';

createExpressServer({
  routePrefix: '/api',
  controllers: [UserController],
}).listen(3000);

koa users must use createKoaServer instead of createExpressServer

Prefix controller with base route

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

@Controller('/users')
export class UserController {
  // ...
}

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 use isArray 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 use isArray option with type 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.

enum Roles {
    Admin = "admin",
    User = "user",
    Guest = "guest",
}

class GetUsersQuery {

    @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 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.

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 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.

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 file
export const fileUploadOptions = () => ({
    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 uses multer to handle file uploads. You can install multer's file definitions via typings, and use files: File[] type instead of any[].

Make parameter required

To make any parameter required, simply pass a required: 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 use class-transformer to create instance of that class type. More info about this feature is available here.

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 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.

@Delete("/users/:id")
@OnUndefined(204)
async remove(@Param("id") id: number): Promise<void> {
    return userRepository.removeById(id);
}

@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.

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

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

import { HttpError } from 'routing-controllers';

export class UserNotFoundError extends HttpError {
  constructor() {
    super(404, 'User not found!');
  }
}
@Get("/users/:id")
@OnUndefined(UserNotFoundError)
saveUser(@Param("id") id: number) {
    return userRepository.findOneById(id);
}

If controller action returns null 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-views, koa-views is the only render middleware that has been tested.

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

    const user = this.userRepository.findOneById(id);
    if (!user)
        throw new NotFoundError(`User was not found.`); // message is optional

    return user;
}

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

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.

class DbError extends HttpError {
  public operationName: string;
  public args: 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';

const app = createExpressServer({
  cors: true,
  controllers: [UserController],
});

app.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:

import { createExpressServer } from 'routing-controllers';
import { UserController } from './UserController';

const app = 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';

const app = createExpressServer({
  defaults: {
    //with this option, null will return 404 by default
    nullResultCode: 404,

    //with this option, void or Promise<void> will return 204 by default
    undefinedResultCode: 204,

    paramOptions: {
      //with this option, argument will be required by default
      required: true,
    },
  },
});

app.listen(3000);

Selectively disable request/response transform

To disable class-transformer on a per-controller or per-route basis, use the transformRequest and transformResponse options on your controller and route decorators:

@Controller("/users", {transformRequest: false, transformResponse: false})
export class UserController {

    @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 use compression middleware:

  1. Install compression middleware: npm install compression

  2. To use middleware per-action:

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

    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.

  3. To use middleware per-controller:

    import { Controller, UseBefore } from 'routing-controllers';
    let compression = require('compression');
    
    @Controller()
    @UseBefore(compression())
    export 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.

  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 required
    let compression = require('compression');
    let app = createExpressServer({
      controllers: [UserController],
    }); // creates express app, registers all controller routes and returns you express app instance
    app.use(compression());
    app.listen(3000); // run express application

    Alternatively, you can create a custom global 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:

    export function loggingMiddleware(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';
    
    export class MyMiddleware implements ExpressMiddlewareInterface {
      // interface implementation is optional
    
      use(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)
    export class UserController {}

    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:

    export function use(context: any, next: (err?: any) => Promise<any>): Promise<any> {
      console.log('do something before execution...');
      return next()
        .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';
    
    export class MyMiddleware implements KoaMiddlewareInterface {
      // interface implementation is optional
    
      use(context: any, next: (err?: any) => Promise<any>): Promise<any> {
        console.log('do something before execution...');
        return next()
          .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)
    export class UserController {}

    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' })
export class LoggingMiddleware implements ExpressMiddlewareInterface {
  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 implement ExpressErrorMiddlewareInterface:

  1. Create a class that implements the ErrorMiddlewareInterface interface:

    import { Middleware, ExpressErrorMiddlewareInterface } from 'routing-controllers';
    
    @Middleware({ type: 'after' })
    export class CustomErrorHandler implements ExpressErrorMiddlewareInterface {
      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 specifying defaultErrorHandler 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';
import path from '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 user
    return content.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';

export class NameCorrectionInterceptor implements InterceptorInterface {
  intercept(action: Action, content: any) {
    return content.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 class and mark it with @Interceptor decorator:

import { Interceptor, InterceptorInterface, Action } from 'routing-controllers';

@Interceptor()
export class NameCorrectionInterceptor implements InterceptorInterface {
  intercept(action: Action, content: any) {
    return content.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 using class-transformer. To use it simply specify a classTransformer: 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 class of that instance with the data sent by a user:

export class User {
  firstName: string;
  lastName: string;

  getName(): string {
    return this.lastName + ' ' + this.firstName;
  }
}

@Controller()
export class UserController {
  post(@Body() user: User) {
    console.log('saving user ' + user.getName());
  }
}

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.

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 inside AbstractBaseController with the possibility to add new and overload methods, the template method pattern.

@Controller(`/product`)
class ProductController extends AbstractControllerTemplate {}
@Controller(`/category`)
class CategoryController extends AbstractControllerTemplate {}
abstract class AbstractControllerTemplate {
  @Post()
  public create() {}

  @Get()
  public read() {}

  @Put()
  public update() {}

  @Delete()
  public delete() {}
}

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 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:

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 setting validate: 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.

export class User {
  @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 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.

@Controller()
export class UserController {
  @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 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.

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:
    const token = action.request.headers['authorization'];

    const user = await getEntityManager().findOneByToken(User, token);
    if (user && !roles.length) return true;
    if (user && roles.find(role => user.roles.indexOf(role) !== -1)) return true;

    return false;
  },
}).listen(3000);

You can use @Authorized on controller actions:

@JsonController()
export class SomeController {
  @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:
    const token = action.request.headers['authorization'];
    return getEntityManager().findOneByToken(User, token);
  },
}).listen(3000);

You can use @CurrentUser on controller actions:

@JsonController()
export class QuestionController {
  @Get('/questions')
  all(@CurrentUser() user?: User, @Body() question: Question) {}

  @Post('/questions')
  save(@CurrentUser({ required: true }) user: User, @Body() post: Post) {}
}

If you mark @CurrentUser as required 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 with typedi:

import { createExpressServer, useContainer } from 'routing-controllers';
import { Container } from 'typedi';
import path from 'path';

// its important to set container before any operation you do with routing-controllers,
// including importing controllers
useContainer(Container);

// create and run server
createExpressServer({
  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()
export class UsersController {
  constructor(private userRepository: UserRepository) {}

  // ... controller actions
}

Note: As [email protected] 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:

// inversify-adapter.ts
import { IocAdapter } from 'routing-controllers';
import { Container } from 'inversify';

class InversifyAdapter implements IocAdapter {
  constructor(private readonly container: Container) {}

  get<T>(someClass: ClassConstructor<T>, action?: Action): T {
    const childContainer = this.container.createChild();
    childContainer.bind(API_SYMBOLS.ClientIp).toConstantValue(action.context.ip);
    return childContainer.resolve<T>(someClass);
  }
}

And then tell Routing Controllers to use it:

// Somewhere in your app startup
import { useContainer } from 'routing-controllers';
import { Container } from 'inversify';
import { InversifyAdapter } from './inversify-adapter.ts';

const container = new Container();
const inversifyAdapter = new InversifyAdapter(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';

export function UserFromSession(options?: { required?: boolean }) {
  return createParamDecorator({
    required: options && options.required ? true : false,
    value: action => {
      const token = action.request.headers['authorization'];
      return database.findUserByToken(token);
    },
  });
}

And use it in your controller:

@JsonController()
export class QuestionController {
  @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

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.

Controller Action Decorators

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)

Method Parameter Decorators

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)

Middleware and Interceptor Decorators

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.

Other Decorators

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.

Samples

Release notes

See information about breaking changes and release notes here.

routing-controllers's People

Contributors

19majkel94 avatar adenhertog avatar alexproca avatar arthurmelin avatar attilaorosz avatar benjd90 avatar chaowlert avatar d0ck avatar dependabot[bot] avatar diluka avatar efk3 avatar fabiob avatar felipesabino avatar ivanpadavan avatar ivanproskuryakov avatar joshualawson avatar jotamorais avatar kyle-seongwoo-jun avatar lucasltv avatar michallytek avatar mingyang91 avatar nolazybits avatar nonameprovided avatar petermetz avatar phikes avatar pleerock avatar sh3d2 avatar tonypythoneer avatar twittwer avatar yazshel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

routing-controllers's Issues

Cannot get any data into any Post method

I have a very simple controller method

@Post("/register") register(@Req() request: any, @Res() response: any) { console.log(JSON.stringify(request.body)); return ""; }

and I'm trying to pass in some post data using the curl CLI iunder windows:

curl -X POST -H "Content-Type: application/json" -d "{ \"email\": \"[email protected]\", \"name\": \"john\", \"password\": \"1\" }" localhost:8080/api/auth/register

Somehow I cannot for the life of me get any of the data to come through i.e. the body is a l w a y s empty.
I have absolutely no idea where the problem is as the @get methods work no problem.

Please advise.
Thansk you so much.

Provide an example how to write unit tests.

Hello, first I want to say "thank you" for your library! Very useful.
Could you please provide an example, how to write unit / functional test for an application, written with you library?

ErrorHandler decorator

The documentation mentions using the @ErrorHandler() decorator, but that decorator doesn't exist.

Flow in returning a promise

What happens in the following code,

@Post("/session")
@NullResultCode(401)
login( @Body({ required: true }) credentials: Object): Promise<Object> {
    return AuthService.getToken(credentials["username"], credentials["password"]);
}

when,

  1. Promise rejects
  2. Promise resolves

Does 401 occur only if I return null?
Is there a way to specify error message for a 401 using promises?

Global middleware usage fails

Hello,

I am intresting on this project, and I was downloaded your example project (https://github.com/pleerock/routing-controllers-express-demo)

I would like to use Helmet ( https://helmetjs.github.io/ ) with global middleware declaration, but it not works, only works in middleware per-controller, or middleware per-action!

Please check my code:
Working code with middleware per-controller:
let helmet = require("helmet"); @UseBefore(helmet()) export class PostController {....}
but in main app.ts I would like to use:
let helmet = require("helmet"); expressApp.use(helmet());

And after I called some URL, then I got this Error:

Error: Can't set headers after they are sent.
at ServerResponse.setHeader (_http_outgoing.js:367:11)
at dnsPrefetchControl (E:\workspace\adv\node\routing-controllers-express-demo\node_modules\dns-prefetch-control\index.js:9:11)
at call (E:\workspace\adv\node\routing-controllers-express-demo\node_modules\connect\index.js:239:7)
at next (E:\workspace\adv\node\routing-controllers-express-demo\node_modules\connect\index.js:183:5)
at Function.handle (E:\workspace\adv\node\routing-controllers-express-demo\node_modules\connect\index.js:186:3)
at app (E:\workspace\adv\node\routing-controllers-express-demo\node_modules\connect\index.js:51:37)
at Layer.handle [as handle_request] (E:\workspace\adv\node\routing-controllers-express-demo\node_modules\express\lib\router\layer.js:95:5)
at trim_prefix (E:\workspace\adv\node\routing-controllers-express-demo\node_modules\express\lib\router\index.js:312:13)
at E:\workspace\adv\node\routing-controllers-express-demo\node_modules\express\lib\router\index.js:280:7
at Function.process_params (E:\workspace\adv\node\routing-controllers-express-demo\node_modules\express\lib\router\index.js:330:12)

Used versions is the lates one in every dependency!

Can you help me what is the problem?

Please support @Authorized

No an issue , rather feature request:

It would be really nice if you could add some kind of @authenticated() decorator to controller and actions that will call some midleware to decide if user is authorized to call the controller action.

Thanks.

Only `Get` works with Koa

Hi,

I was very happy to come across this project, and tried to switch my Koa 2.0 app to use it. Turns out that any endpoint that is not a GET just hangs and times out.

@JsonController("/api/analytics")
@UseBefore(myAuthMiddleware())
export default class AnalyticsAPIController {
    @Post("/")
    async noworks(){
        return {yes: "hi"};
    }

    @Get("/")
    async works(){
        return {yes: "hi"};
    }
}

Here is an example of a controller that works:

@JsonController("/api/clients")
export default class ClientsAPIController {
    @Get("/")
    @UseBefore(myAuthMiddlware())
    async getAll(ctx) : Promise<any[]> {
        let res = await clients.logic.getAll();
        return res;
    }

    @Get("/:clientId")
    async get(@Param("clientId") clientId: number) : Promise<any> {
        let client = await clients.logic.get(clientId);
        return client;
    }
}

I'd love to help contribute to this project as well; if you could try to point me in the direction of where you think the error is in the source code, I could take a look/stab.

Avoid global state

As for me, having global state like this:

import "./SomeController" // import result affects as a global variable
let app = createExpressServer() // express server implicitly use "SomeController"

is not a good idea, because is handles like global variables. For example, this requries to clear metadata storage before each functional test. I think it could be better, if you made this opt-in, and add ability to explicit pass controllers, interceptors and other citizens to createExpressServer:

import SomeController from "./SomeController"
import SomeInterceptor from "./SomeInterceptor"
let app = createExpressServer(
  SomeController,
  SomeInterceptor
)

or somehow else. What do you think about it?

Duplicated info sections in readme

There is small bug in readme - there are two sections about the same feature - creating instances of classes from action params:

The first one tells you that it's enabled by default and you can disable if you want (which is true) but the second tells about explicit set useClassTransformer: true and later explains that it's enabled by default.

I think that there should be one place for merged info about this feature and links to this place in other places.

Piping Stream to Response Yields Empty Result

Consider the following example of piping a stream (any stream; but my example uses PDF) to the result object:

@Get("/pdf")
public pdf(@Req() req: Request, @Res() res: Response) {
    let wkhtmltopdf = require("wkhtmltopdf");

    res.setHeader("Content-type", "application/pdf");
    wkhtmltopdf("http://localhost/my/app").pipe(res);
}

Unfortunately, the route simply returns Cannot GET /pdf.

The same code, however, works when conventionally registering the route as below.
I wonder if I'm doing something wrong, or if the piping of streams is not yet supported.

app.get("/test", (req: Request, res: Response) => {
    let wkhtmltopdf = require("wkhtmltopdf");

    res.setHeader("Content-type", "application/pdf");
    wkhtmltopdf("http://localhost/projects/proposely").pipe(res);
});

Gzip compression causes empty responses

Hi,
I want to use compression module for Gzip compression, here is my server setup:

    // use gzip compression
    this.app.use(compression());
    // create expressjs application
    useExpressServer(this.app, {
      routePrefix: '/api/v1',
      controllers: [__dirname + '/controllers/*.js'],
      middlewares: [__dirname + '/middlewares/*.js']
    });

My problem is that sometimes i'm getting empty response without the actual data although my controller always returning the data.
Here is my controller code:

  @Get()
  public async getAllUsers(@QueryParam("page") page: number = -1 , @QueryParam("count") count: number = -1, @QueryParam("term") term: string = ''): Promise<Page<User>> {
      let result = await this.getUsers(page, count, term);
      return result.data;
  }

It's only happens when using with compression module, any thoughts?

Error with `@Res` decorator

When I try to run the following file:

import {Request, Response} from 'express';
import {Controller, Req, Res, Get} from 'routing-controllers';

@Controller()
class WelcomeController {

    @Get('/')
    index(@Req() request: Request, @Res() response: Response) {
        response.send('Hello world!');
    }

}

export default WelcomeController;

I get the following error:

$ node build/index.js
/Users/lorefnon/Workspace/server/node_modules/routing-controllers/decorator/params.js:30
        var reflectedType = Reflect.getMetadata("design:paramtypes", object, methodName)[index];
                                                                                        ^

TypeError: Cannot read property '1' of undefined
    at /Users/lorefnon/Workspace/server/node_modules/routing-controllers/decorator/params.js:30:89
    at /Users/lorefnon/Workspace/server/build/controllers/WelcomeController.js:9:37
    at DecorateProperty (/Users/lorefnon/Workspace/server/node_modules/reflect-metadata/Reflect.js:533:29)
    at Object.decorate (/Users/lorefnon/Workspace/server/node_modules/reflect-metadata/Reflect.js:98:20)
    at __decorate (/Users/lorefnon/Workspace/server/build/controllers/WelcomeController.js:4:92)
    at Object.<anonymous> (/Users/lorefnon/Workspace/server/build/controllers/WelcomeController.js:20:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)

The following however works as expected:

@Controller()
class WelcomeController {

    @Get('/')
    index() {
        return 'Hello world';
    }

}

Other relevant files:

index.ts:

import 'reflect-metadata';
import { createExpressServer } from 'routing-controllers';
import WelcomeController from './controllers/WelcomeController';

createExpressServer({
    controllers: [`${__dirname}/controllers/*`]
}).listen(9000);

tsconfig.json:

{
    "compilerOptions": {
	      "target": "es5",
	      "outDir": "dist",
	      "module": "commonjs",
	      "declaration": true,
	      "noImplicitAny": true,
	      "strictNullChecks": true,
	      "removeComments": true,
	      "moduleResolution": "node",
	      "sourceMap": true,
	      "inlineSources": true,
        "experimentalDecorators": true
    },
    "include": [
	      "src/**/*"
    ],
    "exclude": [
	      "node_modules",
	      "**/*.spec.ts"
    ]
}

Versions:

KOA Support

I'd like to propose to abstract this to support multiple frameworks and provide out of the box support for koa.

Primary Drivers

  • Express has a big community, but the project hasn't moved in over a year. No major releases, no activity, etc.
  • KOA is faster and lighter.
  • Building on point 1, Express doesn't implement any new ES* features. Generators have been native in node since 6.0 ... KOA has had those for over a year now. KOA.
  • Most of the new new projects I've seen are using KOA which continues to build on point 1. Main reason is lack of support of new features.

Reference

Similar Projects to yours
There are some projects that are similar to your but not near as full feature, here is a list:

Decorators for middleware ?

Hi

I was wondering if its possible to pass in custom middleware on a route or is it planned ?

or even better, protecting routes using security decorators ? separate library ?

Crashed when use some of parameter decorators

When I use @Body(), @Param(), @QueryParam() and etc, program will crash with this error

        var format = Reflect.getMetadata("design:paramtypes", object, methodName)[index];
                                                                                 ^

TypeError: Cannot read property '0' of undefined

routing-controllers 0.6.2
reflect-metadata 0.1.3
node 6.3.1

custom error middleware always execute after default

I have custom error middleware to handle errors and decide different way to response. But neither Infinity nor -Infinity I set to the priority option, the default error middleware send the response with status 500 before my error middleware. My error middleware seems to be useless.

how can I solve this?

Action does not handle bluebird promise

I'm trying to return Bluebird promise frin action, and it passes to response without waiting untill it resolves.
This happens because you test instanceof Promise to check that return object is a promise. I think that it is better to test that return object is Thenable or PromiseLike - check that then method exists on returned object.

Parameter didn't end with backslash "/"

Given that i have controller like this:

@Get("/charities/:charityId([0-9a-fA-F]{24})/suggests/random")
getRandomSuggest(@Param("charityId", {required: true}) charityId: string): Promise<SomeClass> {
    // doing some awesome stuf
}

If our url is /charities/56b4c81cb88c513400940d5e/suggests/random

Then it will interpreted like this:
current: :charityId is 56b4c81cb88c513400940d5e/suggests/random
the expected 56b4c81cb88c513400940d5e

Promise<void> returns 404

Hi,

Thank you for the library. I found one case where behavior might not be correct. For example

@Post('/test')    
test() {
    return Promise.resolve();
}

Above will return 404 (even above code was run), even I add EmptyResultCode also doesn't help.
My expectation is it should return 204.

chai-http and load controllers from folder

Hi,
I'm trying to create a TDD friendly environment for a new project.
I'll use chai-http for testing endpoints.

It seems like the test runner calls the app before the router is defined. not sure.
Does it happen because I'm using the controllers option?

    useExpressServer(this.app, {
      controllers: [__dirname + "/controllers/*.js"],
      middlewares: [__dirname + "/middlewares/global/*.js"],
      useClassTransformer: true,
      defaultErrorHandler: false
    });

If yes, what to do?
If no, what to do?

Related: chaijs/chai-http#143

Including *.ts files in util.DirectoryExportedClassesLoader.importClassesFromDirectories() breaks import

Hi,

util.DirectoryExportedClassesLoader.importClassesFromDirectories() includes *ts files. This causes a problem when *.ts files are compiled to *.js files in the same directory. The importClassesFromDirectories() function does not know which file to use so it ignores.

Changing formats = [".js", ".ts"] to formats = [".js"] fixes any issues so may be an idea to slightly refactor this.

Cheers

JT

ParameterRequiredError is not working

when the required key is true on a @BodyParam the error is sent as clear text (ie: not put in double quotes) and the http code is not set correctly so it becomes 500 even if that's not a server error

Signature Mismatch in `MiddlewareInterface`

Consider the following code (note the Request/Response type hints):

export class MyMiddleware implements MiddlewareInterface {
    use(request: Request, response: Response, next?: Function): any {

Compiling throws the following error:

error TS2420: Class 'MyMiddleware' incorrectly implements interface 'MiddlewareInterface'.
  Types of property 'use' are incompatible.
    Type '(request: Request, response: Response, next?: Function) => any' is not assignable to type '{ (request: any, response: any, next?: (err?: any) => any): any; (context: any, next: (err?: any)...'.
      Types of parameters 'response' and 'next' are incompatible.
        Type '(err?: any) => Promise<any>' is not assignable to type 'Response'.
          Property 'status' is missing in type '(err?: any) => Promise<any>'.

This, however, works:

export class MyMiddleware implements MiddlewareInterface {
    use(request: any, response: any, next?: Function): any {

I tracked the issue to the second (Koa) signature in MiddlewareInterface (removing it fixes the above but, obviously, won't make Koa happy). It looks like my code makes the TypeScript compiler think I'm trying to implement the second signature.

While I tried changing/adding signatures in MiddlewareInterface, I couldn't get it to compile.

Enabling CORS

I'm having issues enabling CORS. I've tried setting the headers using the @Header decorator

@Get('/')
@Render('home')
@Header("Access-Control-Allow-Origin", "*");
@Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
Index()
{
 //...
}

I've tried using the @UseBefore decorator

@UseBefore(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
})

I've tried setting the middleware in the server.ts file

expressServer.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});

Despite trying these 3 methods, I continuously get the error:

esponse to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

I'm using localhost:80. I've tried on Google Chrome, Firefox, and Microsoft Edge.

Is there something I'm missing?

why will method be executed when the previous method return a result?

when I visit: http://localhost:3000/api/user/all?id=123
the result is '{"id":"all","flag":"getOne"}',but there is a Error: Can't set headers after they are sent.
why will Method:getAll be executed when Method:getOne return a result?

import {Controller, JsonResponse, Param, Get, QueryParam} from "routing-controllers";

@Controller('/user')
export class UserController {

    @Get("/:id")
    @JsonResponse()
    getOne(@Param("id") id:string) {
        console.log('getOne');
        return {id:id,flag:'getOne'};
    }

    @Get("/all")
    getAll(@QueryParam("id") id:number) {
        console.log('getAll');
        return {id:id,flag:'getAll'};
    }
}

Why not declare public with HttpError parameters?

Hi @pleerock, I have a question about this.

export class HttpError extends Error {
    httpCode: number;
    message: string;

    constructor(httpCode: number, message?: string) {
        super();
        if (httpCode)
            this.httpCode = httpCode;
        if (message)
            this.message = message;

        this.stack = new Error().stack;
    }
}

Why not do this?

export class HttpError extends Error {

    constructor(public httpCode: number, public message?: string) {
        super();
        this.stack = new Error().stack;
    }
}

When it declare public with httpCode and message, it will auto-pass as instance properties.
Perhaps you have special consideration.

Support DI in controllers

Hi, this a great library, I had actually rolled my own similar implementation using decorators but this is much more complete solution so I'm looking at migrating to routing-controllers.

My first thought is how canI I inject dependencies into controllers, would you be open to using a DI framework like https://github.com/inversify/InversifyJS/ ??

Dashes in get param value is not accepted

@Get('/:uuid/stuff') public getSomething(@Param('uuid') uuid) { }
When i use the above, and sent a /1234-234234-24324234/stuff (as example)
I get a 'Error' @ HttpError.js:18:22

Cannot find name 'Promise'

Hi,

it's amazing library, which can help me in my project. Unfortunately, I've problem with correct compilation by tsc.
I receive this error:
node_modules/routing-controllers/middleware/MiddlewareInterface.d.ts(14,60): error TS2304: Cannot find name 'Promise'.

Is it problem with this lib or it's my problem with incorrect configuration ?

container option in options doesn't work

The container option in useExpressErver function does not work. See below.

import { useExpressServer, useContainer } from 'routing-controllers';
import { Container } from 'typedi';

function setup(app)
    const controllersPath = path.resolve('dist', 'controllers');

    useExpressServer(app, {
      controllerDirs: [ controllersPath ],
      options: { container: Container } // < Does not work
    });

    useContainer(Container); // Works
}

Session Error - Parameter user_id is required for request

Here's my code:

@Controller()
export class TestRoutes {
    @Get("/test")
    getTest(@Req() req:Request,@Res() res:Response,@Session("user_id")userId:number) {
        res.send("user:"+userId)
    }
}

Is there any way to prevent the request completely dies when "user_id" from the session is undefined?
I'm getting this error

Parameter user_id is required for request on GET /test

when an anonymous visitor visits the page because "user_id" is not defined in the req.session obj.

I'm not sure if it's related but I have tried custom error handle in your sample and defaultErrorHandler: false in useExpressServer, but nothing works.

Sending raw data is impossible.

Hi!
Thanks for an awesome project! It make writing services in TS a real pleasure.
One bug I came across - even for actions that are marked with a different @ContentType and not inside @jsoncontroller - the result cannot be binary, for example, I cannot send image file.
The reason for it is the following code in ExpressDriver:

 } else {
                    response.send(String(result));
                }

Basically, even if a Buffer is returned - it's converted to string. It's not a correct behavior. At least some sort of escape hatch is needed.

What would you recommend?

Routes from Directory not working

Hi,

When running the following two classes the Controller does not register. The server starts up and I can receive requests but I am unable to access my routes.

server.ts

/// <reference path='../typings/index.d.ts' />
import 'reflect-metadata';
import 'es6-shim';
import * as express from 'express';
import {useExpressServer} from 'routing-controllers';
import {createConnection, ConnectionOptions} from 'typeorm';
let bodyParser = require('body-parser');
let cookieParser = require('cookie-parser');
import session = require('express-session');

/**
 * The server.
 *
 * @class Server
 */
class Server {

    app: express.Application;

    /**
     * Bootstrap the application.
     *
     * @class Server
     * @method bootstrap
     * @static
     */
    public static bootstrap(): Server {
        return new Server();
    }

    /**
     * Constructor.
     *
     * @class Server
     * @constructor
     */
    constructor() {
        this.app = express();
        this.createServer(this.app);
    }

    /**
     * Configure application
     *
     * @class Server
     * @method config
     * @return void
     */
    private createServer(app: express.Application): void {
        /**************************************
         * set up the server
         **************************************/
        // base directory. we use it because file in "required" in another module
        const baseDir = __dirname;
        useExpressServer(app, {
            controllerDirs: [baseDir + "/server/controllers"],
            // controllerDirs: [baseDir + "/server/controllers/**/*.js"],
            // controllerDirs: [baseDir + "/server/controllers/**/*.controller.js"]
        });
        app.listen(process.env.PORT || 4200, function () {
            console.log('&&&&&&& Listening on 4200 &&&&&&');
        });
    }
}

let server = Server.bootstrap();
module.exports = server.app;

This compiles OK and prints out

&&&&&&& Listening on 4200 &&&&&&

test.controller.ts

import {Response, Request, Router} from 'express';
import {Controller, Get, Req, Res, Post} from 'routing-controllers';

@Controller()
export class TestController {

    constructor() {
        console.log("@@@@@@@@@@@@@@@@@@ TestController @@@@@@@@@@@@@@@@@@")
    }

    @Get('/')
    entry(@Req() request: Request, @Res() response: Response) {
        console.log("*************** entry()")

    }
}

Next, if I add require("./server/contollers/test.controller"); to the top of my server.ts the compiled code generates the following:

__decorate([
        routing_controllers_1.Get('/'),
        __param(0, routing_controllers_1.Req()),
        __param(1, routing_controllers_1.Res()), 
        __metadata('design:type', Function), 
        __metadata('design:paramtypes', [express_1.Request, express_1.Response]), 
        __metadata('design:returntype', void 0)

This code gives:

/Users/ts/Documents/workspace/testapp/src/server/contollers/test.controller.js:207
        __metadata('design:paramtypes', [express_1.Request, express_1.Response]), 
                                         ^

ReferenceError: express_1 is not defined
    at /Users/jt/Documents/workspace/testapp/src/server/contollers/test.controller.js:207:42
    at Object.<anonymous> (/Users/jt/Documents/workspace/testapp/src/server/contollers/test.controller.js:319:2)
    at Module._compile (module.js:556:32)
    at Object.Module._extensions..js (module.js:565:10)
    at Module.load (module.js:473:32)
    at tryModuleLoad (module.js:432:12)
    at Function.Module._load (module.js:424:3)
    at Module.require (module.js:483:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/jt/Documents/workspace/testapp/src/server.js:12:1)

tsconfig.json

{
  "compilerOptions": {
    "lib": ["es5", "es6", "dom"],
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": true,
    "pretty": true,
    "suppressImplicitAnyIndexErrors": true,
    "typeRoots": [
      "node_modules/@types"
    ],
    "types": [
      "typed-graphql"
    ]
  },
  "files": [
    "./src/server.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

It largely follows the examples so I am stuck as to why it doesn't work. Is this an issue?

Cheers

JT

Build Table of Contents for this README

This is issue opened for me.

This module is awesome and this document is clear to me.
I want to add Table of Contents for this README. It's convenience for interested developer to read.

Can I extends controller class?

I want to put same common code into abstract controller like a template. And a concrete controller can extend a abstract controller.

I don't want to copy codes, is there a way to avoid that?

Koa render

Currently in the KoaDriver in the handleSuccess method this.koa.render is called but is undefinded.

I can't find anywhere where koa does rendering, it seems this has been copied from the ExpressDriver because Express handles its rendering, koa uses rendering middlewares that then create the render method on the context.

I am using koa-views as my renderer but getting the following error:

{"name":"TypeError","message":"this.koa.render is not a function","stack":"TypeError: this.koa.render is not a function\n    at KoaDriver.handleSuccess 

I have had a quick play around, and found that changing the call from this.koa.render to options.context.render does cause something to try and load, but timesout without any errors. I haven't looked completely through the routing-controller source to completely understand whats going on.

If anyone could give me some guidance before I dig in I would be more than happy to get this working!

@UseBefore called before all middlewares

Hi @pleerock,

Thanks for your nice lib. I'm trying to run JSON Schema validation for request body using @UseBefore decorator. But once my middleware called, I can't retrive request.body parameter. Is it an expected behavior?

EmailsController.ts

@JsonController("/emails")
export class EmailController {
   
    @Post()
    @UseBefore((request: any, response: any, next: (err?: Error) => void): void => {
        Joi.validate(request.body, schema, (err) => {
            if (err) {
                next(err);
            } else {
                next();
            }
        });
    })
    saveEmail(@Body() email: Email): Email {
        return email;
    }
}

index.ts

import "es6-shim";
import "reflect-metadata";
import {createExpressServer} from "routing-controllers";

let app = createExpressServer({
    routePrefix: "/api",
    controllers: [__dirname + "/controllers/v1/*.js"]
});
const morgan = require('morgan');
const bodyParser = require('body-parser');
const compress = require('compression');

app
    .use(morgan('dev'))
    .use(compress({}))
    .use(bodyParser.json())
    .use(bodyParser.urlencoded({ extended: true }));
app.listen(3000, () => console.log("started"));

If I change @UseBefore to @UseAfter, request will have a body parameter. How can I get an access to body before saveEmail action is called?

One more question: should @UseAfter fail whole request if it returns an error? If I call next(new Error(...)) there it will log an error but still return a result.

Server Caching

Is it possible to cache action results on the server side?

In ASP Web API it can be achieved the way below (though through external lib):
[CacheOutput(ClientTimeSpan = 100, ServerTimeSpan = 100)] public string Get() { return "s"; }

Request Interceptor

Is it possible to create request interceptor for controller and their actions? My ultimate goal is to add security handler that will check access token.

always return before getting the result.

Hi,
I am trying to fetch users from db using mongoose. But while making a GET request(http://localhost:3000/api/users) the call not wait until the callback comes from db. My code look likes below,

@JsonController("/api/users")
    export class UserController {    
    @Inject() private userService: UserService;

    @Get("/")
    @JsonResponse()
    getAll(@BodyParam("name") userName: string, @Req() request: Request, @Res() response: Response){
        this.userService.fetchAll((err, res) => {
            response.send(res);
        });
    }
}

How to fix this issue. I have tried fibers but its not working for me.

Cannot find module 'routing-controllers'.

Hey there. I'm trying to use routing-controllers in my project; however, after following the instructions in README.md, I get a error TS2307: Cannot find module 'routing-controllers'. wherever I try to use it, e.g. `import { Controller, Param, Body, Get, Post, Put, Delete, Req, Res } from "routing-controllers";``

I'm on node v7 and TypeScript 2.0.10. Not sure what I am doing wrong though... routing-controllers folder is correctly in my node_modules folder.

undefined in a HTTP POST body

I wrote a handler like this. When I use form post or Postman's post, it gets an empty object.
But I use supertest to test it, it seems OK, and get the right body object.

request.body is always undefined.

    @Post("/")
    async saveOne(@Body() data) { //data is {} if not in test
        return await this.todoService.save(data);
    }

test code: PASS

"use strict";
describe("Todo测试", ()=> {
    const expect = require("chai").expect;
    const supertest = require("supertest");
    const request = supertest.agent(`http://localhost:${process.env.LEANCLOUD_APP_PORT}`);

    it("写入对象", (done)=> {
        request.post("/todos").send({"content": `测试数据 - ${new Date()}`}).expect(200).end((e, res)=> {
            expect(res.body).to.be.an("object", "返回对象");
            done();
        });
    });
});

Chrome's POST form submit: FAILED

curl 'http://localhost:3000/todos' -H 'Origin: http://localhost:3000' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: zh-CN,zh;q=0.8' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Cache-Control: max-age=0' -H 'Referer: http://localhost:3000/' -H 'Connection: keep-alive' -H 'DNT: 1' --data 'content=test' --compressed

Postman's curl: FAILED

curl -X POST -H "Cache-Control: no-cache" -H "Postman-Token: e06e03e8-6009-28f6-59cc-0cc72ac6dfbc" -H "Content-Type: application/x-www-form-urlencoded" -d 'content=value to be save&ignore=value to be ignore' "http://localhost:3000/todos"

p.s. just ignore those Chinese characters,there're nothing to do with this issue, consider them just strings

middleware cant not handle exception

built by Ts2.1 ;
Expception throw in use function of middleware cant be handled correctly, this.handleError was undefined. 'this' scope is not work when call this.handleError.
Generated Code in ExpressDriver.js
ExpressDriver.prototype.registerMiddleware = function (middleware) {
if (!middleware.instance.use)
return;
this.express.use(function (request, response, next) {
var _this = this;
}
actually , I modified the js, the code can work.
ExpressDriver.prototype.registerMiddleware = function (middleware) {
if (!middleware.instance.use)
return;
var _this = this;
this.express.use(function (request, response, next) {

   }

Bluebird Warning: a promise was created in a handler but was not returned from it

Full text: Warning: a promise was created in a handler at node_modules/routing-controllers/RoutingControllerExecutor.ts:92:35 but was not returned from it, see http://goo.gl/rRqMUw

I know that this is not an issue because promise gets handled by another function. But let's make dat bluebird happy. In my opinion proper promise chaining with return statements it's not a code-style adoption.

As a temporary workaround I've had to disable the forgotten return warning:

Promise.config({
  warnings: {
    wForgottenReturn: false
  }
})

call to router controller direct from client

hi,
tanks for the awesome module.

i wonder if its possible to call the controller direct from client side project.

end the controller recognize the caller , and replace the code with "http.get...".
it is possible with decorators?

i think it woud be grate for projects that shared the client side with server side

tanks

Session Method Parameter Decorator

Is the @Session decorator avaible?
I attach to req.session an user object after sucessfull authentication and use it in controler method to check if user is an owner of demanded object from database.
It would be nice to have :

@Get("/posts")
getAll(@Session("user") user: User) {
   return postsRepository.getAllByUser(user);
}

And don't have to parse request object by myself 😉

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.