Controllers

Installation

You can add the controllers folder on mount attribute in your server settings as follow :

import {ServerLoader, ServerSettings} from "@tsed/common";
import Path = require("path");
const rootDir = Path.resolve(__dirname);

@ServerSettings({
   rootDir,
   mount: {
      '/rest': `${rootDir}/controllers/*.js`
   }
})
export class Server extends ServerLoader {
    $onMountingMiddlewares(): void|Promise<any> {

        const cookieParser = require('cookie-parser'),
            bodyParser = require('body-parser'),
            compress = require('compression'),
            methodOverride = require('method-override');


        this
            .use(ServerLoader.AcceptMime("application/json"))

            .use(cookieParser())
            .use(compress({}))
            .use(methodOverride())
            .use(bodyParser.json())
            .use(bodyParser.urlencoded({
                extended: true
            }));

        return null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

Versioning your API

As you have seen in the previous example, the mount attribute is an object that let you to provide the global endpoint for your all controllers under the controllers folder.

You can add more configuration to mount different endpoint associated to a folder. Here is another configuration example:

import {ServerLoader, ServerSettings} from "@tsed/common";
import Path = require("path");

@ServerSettings({
   rootDir: Path.resolve(__dirname),
   mount: {
     "/rest": "${rootDir}/controllers/current/**/*.js",
     "/rest/v1": "${rootDir}/controllers/v1/**/*.js"
   }
})
export class Server extends ServerLoader {

}
1
2
3
4
5
6
7
8
9
10
11
12
13

Decorators

    Async and Promise

    Ts.ED support Promise API and async instruction to send a response. Just return a promise in your method and the controller will be waiting for your promised response before sending a response to the client.

    import {Controller, Get, PathParams} from "@tsed/common";
    
    interface Calendar{
        id: string;
        name: string;
    }
    
    @Controller("/calendars")
    export class CalendarCtrl {
    
        @Get("/:id")
        public get(
            @PathParams("id") id: string
        ): Promise<Calendar> {
    
            return new Promise<Calendar>((resolve: Function, reject: Function) => {
                resolve({
                    id,
                    name: "test"
                });
            });
    
        }
        // or
        @Get("/:id")
        async get(
            @PathParams("id") id: string
    
        ): Promise<Calendar> {
    
            return {
                id,
                name: "test"
            };
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

    Multiple routes (Alias)

    Ts.ED let you define multiple routes on the same method controller, with same verb like GET or POST, or with another verb like this:

    import {Controller, Get, Post, PathParams} from "@tsed/common";
    
    @Controller("/calendars")
    export class CalendarCtrl {
    
        @Get("/:id")
        @Get("/alias/:id")
        @Post("/:id/complexAlias")
        async get(
            @PathParams("id") id: string
        ): Promise<any> {
            return {};
        }    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    Input parameters

    @PathParams decorator provide quick access to an attribute Express.request.params.

    import {Controller, Get, PathParams} from "@tsed/common";
    
    @Controller("/calendars")
    export class CalendarCtrl {
    
        @Get("/:id")
        async get(
            @PathParams("id") id: number
        ): void {
    
            return {id: id, name: "test"}
    
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    Same decorator is available to get other params. Use BodyParams (with the right HTTP verb @Post, @Put, etc...), QueryParams or CookiesParams to get parameters send by the client.

    HeaderParams

    @HeaderParams decorator provide you a quick access to the Express.request.get()

    import {Controller, Get, HeaderParams, PathParams} from "@tsed/common";
    
    @Controller("/calendars")
    export class CalendarCtrl {
    
        @Get("/:id")
        async get(
            @HeaderParams("x-token") token: string,
            @PathParams("id") id: number
        ): any {
            console.log("token", token);
            return {id: id};
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    Status

    You can change the status of the response with the @Status() decorator.

    import {Controller, Get, BodyParams, Status} from "@tsed/common";
    
    interface Calendar{
        id: string;
        name: string;
    }
    
    @Controller("/calendars")
    export class CalendarCtrl {
        @Put("/")
        @Status(201)
        async create(
            @BodyParams("name") id: string
        ): Promise<Calendar> {
            return {
                id: 2,
                name: "test"
            };
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    Response and Request

    You can use a decorator to inject Express.Request, Express.Response and Express.NextFunction services instead of the classic call provided by Express API. Just use decorator Request (or Req), Response (or Res) and Next on your method parameters like this :

    import {Controller, Get, Res, Req, Next} from "@tsed/common";
    import * as Express from "express";
    
    interface ICalendar{
        id: string;
        name: string;
    }
    
    @Controller("/calendars")
    export class CalendarCtrl {
    
        @Get("/:id")
        get(
            @Req() request: Express.Request,
            @Res() response: Express.Response,
            @Next() next: Express.NextFunction
        ): void {
    
            setTimeout(() => {
                response.send(200, {id: request.params.id, name: "test"});
                next();
            });
    
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    Router

    Each controller has an Express.Router instance associated with it. The ExpressRouter decorator is here to inject this instance to your controller.

    import {Controller, Get, ExpressRouter} from "@tsed/common";
    
    @Controller("/calendars")
    export class CalendarCtrl {
        
        constructor(@ExpressRouter router: ExpressRouter) {
            router.get('/', this.myMethod)
        }
        
        myMethod(req, res, next){
            
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    In this case, injection on the method isn't available.

    Custom middleware

    @Use(), @UseBefore(), @UseAfter() decorators lets you add custom middleware on a method or on controller.

    Example

    import {Controller, Get, PathParams, Use, UseBefore, UseAfter} from "@tsed/common";
    import {BadRequest} from "httpexceptions";
    import {CustomMiddleware, CustomBeforeMdlw} from "../middlewares/middlewares"
    
    @Controller("/calendars")
    @UseBefore(CustomBeforeMdlw)
    export class CalendarCtrl {
    
        @Get("/:id")
        @Use(CustomMiddleware)
        async get(
            @PathParams("id") id: number
        ): any {
           return {id: id};
        }
    
        @Get("/:id")
        @UseBefore(CustomMiddleware)
        async get(
            @PathParams("id") id: number
        ): any {
           return {id: id};
        }
    
        @Get("/:id")
        @UseAfter(CustomMiddleware)
        async get(
            @PathParams("id") id: number
        ): any {
           return {id: id};
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    For more information about the CustomMiddleware see the Middlewares section.

    Middleware call sequence

    When a request is sent to the server all middlewares added in the ServerLoader, Controller or Endpoint with decorators will be called while a response isn't sent by one of the middleware in the stack.

    See middleware call sequence for more information.

    Dependencies

    A controller can depend to an other controllers. Dependencies lets you manage each Controller as Express Router module.

    @Controller("/events")
    export class EventCtrl {
     ...
    }
    
    @Controller("/calendars", EventCtrl)
    export class CalendarCtrl {
     ...
    }
    
    @Controller("/rest")
    export class RestCtrl{
        constructor(
            routeService: RouteService
        ){
            console.log(routeService.printRoutes());
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    In this example, CalendarCtrl have EventCtrl as dependencies. When all Controllers are built, the recorded routes will be as follows :

    • /rest
    • /rest/calendars
    • /rest/calendars/events

    Merge Params

    In some case you need to have a complex routes like this rest/calendars/:calendarId/events/:eventId. This route can be written with Ts.ED like this :

    @Controller("/:calendarId/events")
    class EventCtrl {
        @Get("/:eventId")
        async get(
            @PathParams("calendarId") calendarId: string,
            @PathParams("eventId") eventId: string
        ) {
            console.log("calendarId =>", calendarId);
            console.log("eventId =>", eventId);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    In this case, the calendarId will be undefined because Express.Router didn't merge params by default from the parent Router (see Express documentation).

    To solve it you can use the @MergeParams() decorator. See example:

    @Controller("/:calendarId/events")
    @MergeParams()
    class EventCtrl {
        @Get("/:eventId")
        async get(
            @PathParams("calendarId") calendarId: string,
            @PathParams("eventId") eventId: string
        ) {
            console.log("calendarId =>", calendarId);
            console.log("eventId =>", eventId);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    Now, calendarId will have the value given in the context path.

    CaseSensitive and Strict options are also supported.

    Inheritance

    Ts.ED support the ES6 inheritance class. So you can declare a controller that implement some generic method and use it on a children class.

    To do that just declare a parent controller without the @Controller decorator.

    export abstract class BaseController {
        constructor(private someService: SomeService) {}
    
        @Get('/list')
        async list(@QueryParams("search") search: any) {
            return someService.list(search)
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    Then, on your children controller:

    @Controller('/children')
    export abstract class ChildrenCtrl extends BaseController {
        @Get('/:id')
        async get(@PathParams("id") id: string): Promise<any> {
          return {id: id}
        }
    }
    
    1
    2
    3
    4
    5
    6
    7