Controllers

Controllers are responsible for handling incoming requests and returning responses to the client.

A controller is here to handle a specific request for a given HTTP verb and Route. The routing service is responsible to manage and dispatch request to the right Controller.

In order to create a basic controller, we use classes and decorators. Decorators associate classes with required metadata and enable Ts.ED to create a routing map.

Routing

Usage

In the following example we'll use the decorator which is required to define a basic controller. We'll specify a path for the controller which will be used by the routing mechanism to create your routes.

import {Controller, Get} from "@tsed/common";

@Controller("/calendars")
export class CalendarCtrl {
  @Get()
  findAll(): string {
    return "This action returns all calendars";
  }
}
1
2
3
4
5
6
7
8
9

The decorator before the findAll() method tells Ts.ED to create an endpoint for this particular route path and map every corresponding request to this handler. Since we've declared a prefix for every route (/calendars), Ts.ED will map every GET /calendars request to this method.

Ts.ED provide a decorator for each HTTP verb which can be use to handle a request:

    Configuration

    You can add your controller by adding glob pattern on mount ServerSettings attributes or by importing manually your controller. Here an example:

    import {ServerLoader, ServerSettings} from "@tsed/common";
    import {CalendarCtrl} from "./controllers/CalendarCtrl";
    
    @ServerSettings({
      mount: {
        "/rest": `./controllers/*.ts`, // using componentScan
        // Using manual import
        "/manual": [
          CalendarCtrl
        ]
      }
    })
    export class Server extends ServerLoader {
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    Create multiple version of 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";
    
    @ServerSettings({
      mount: {
        "/rest/v0": "./controllers/v0/**/*.ts",
        "/rest/v1": "./controllers/v1/**/*.ts"
      }
    })
    export class Server extends ServerLoader {
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    Async and Promise

    Ts.ED support Promise 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")
      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

    Multiple endpoint, single method

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

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

    Routes order

    Be aware that routes registration order (methods order in classes) matters. Assume that you have a route that allows getting calendars by his path (/calendars/:id). If you register another endpoint below the mentioned one, which basically returns all calendars at once (calendars), the request will never hit the actual handler because all path parameters are optional.

    See the following example:

    import {Controller, Get, PathParams} from "@tsed/common";
    
    @Controller("/calendars")
    export class CatsController {
      @Get(":id")
      findOne(@PathParams("id") id: string) {
        return `This action returns a #${id} cat`;
      }
    
      @Get()
      findAll() {
        // This endpoint will never get called
        // because the "/calendars" request is going
        // to be captured by the "/calendars/:id" route handler
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    In order to avoid such side-effects, simply move findAll() method above findOne().

    Request

    Input parameters

    decorator provide quick access to an attribute Express.request.body.

    import {BodyParams, Controller, Post} from "@tsed/common";
    import {CalendarModel} from "../models/CalendarModel";
    import {PayloadModel} from "../models/PayloadModel";
    
    
    @Controller("/calendars")
    export class CalendarCtrl {
    
      @Post()
      updatePayload(@BodyParams() payload: PayloadModel): any {
        console.log("payload", payload);
    
        return payload;
      }
    
      @Post()
      updateCalendar(@BodyParams("calendar") calendar: CalendarModel): any {
        console.log("calendar", calendar);
    
        return calendar;
      }
    
      @Post()
      updatePayloads(@BodyParams(PayloadModel) payloads: PayloadModel[]): any {
        console.log("payloads", payloads);
    
        return payloads;
      }
    
      @Post()
      updateCalendars(@BodyParams("calendars", CalendarModel) calendars: CalendarModel[]): any {
        console.log("calendars", calendars);
    
        return calendars;
      }
    }
    
    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

    Same decorator is available to get other params. Use these decorators to get parameters send by the client:

    • : Express.request.body
    • : Express.request.params
    • : Express.request.query
    import {BodyParams, Controller, Post} from "@tsed/common";
    import {CalendarModel} from "../models/CalendarModel";
    import {PayloadModel} from "../models/PayloadModel";
    
    
    @Controller("/calendars")
    export class CalendarCtrl {
    
      @Post()
      updatePayload(@BodyParams() payload: PayloadModel): any {
        console.log("payload", payload);
    
        return payload;
      }
    
      @Post()
      updateCalendar(@BodyParams("calendar") calendar: CalendarModel): any {
        console.log("calendar", calendar);
    
        return calendar;
      }
    
      @Post()
      updatePayloads(@BodyParams(PayloadModel) payloads: PayloadModel[]): any {
        console.log("payloads", payloads);
    
        return payloads;
      }
    
      @Post()
      updateCalendars(@BodyParams("calendars", CalendarModel) calendars: CalendarModel[]): any {
        console.log("calendars", calendars);
    
        return calendars;
      }
    }
    
    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

    Finally, accept to give a object as parameters to change the decorator behavior:

    import {BodyParams, Post} from "@tsed/common";
    
    class MyController {
      @Post()
      async create(@BodyParams({expression: "user", useConverter: false}) body: T): Promise<T> {
        console.log("payload", body);
    
        return body;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    Headers

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

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

    Session/Cookies/Locals

    For the session, cookies or locals data attached on the request, is the same thing seen as previously. Use the following decorators to get the data:

    Response

    Decorators

      Status

      You can change the default response status with the decorator:

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

      Content Type

      You can set the response content type with the decorator:

      import {BodyParams, ContentType, Controller} from "@tsed/common";
      
      @Controller("/calendars")
      export class CalendarCtrl {
        @ContentType(".html")              // => 'text/html'
        @ContentType("html")               // => 'text/html'
        @ContentType("json")               // => 'application/json'
        @ContentType("application/json")   // => 'application/json'
        @ContentType("png")
        getContent(@BodyParams("name") name: string): any {
          return "something";
        }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      You can set the response header with the decorator:

      import {BodyParams, Controller, Header} from "@tsed/common";
      
      @Controller("/calendars")
      export class CalendarCtrl {
        @Header({
          "Content-Type": "text/plain",
          "Content-Length": 123,
          "ETag": {
            "value": "12345",
            "description": "header description"
          }
        })
        create(@BodyParams("name") name: string): string {
          return `Text plain ${name}`;
        }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      Throw exceptions

      You can use ts-httpexceptions or similar module to throw an http exception. All exception will be intercepted by the Global error handler and are sent to the client.

      Here an example:

      import {Controller, Get, PathParams} from "@tsed/common";
      import {BadRequest} from "ts-httpexceptions";
      
      @Controller("/calendars")
      export class CalendarCtrl {
        @Get("/:id")
        get(@PathParams("id") id: number): any {
          if (isNaN(+id)) {
            throw(new BadRequest("Not a number"));
          }
      
          return {id};
        }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      TIP

      This example will produce a response with status code 400 and "Not a number" message. will catch and format the error before sending it to the client.

      Inject request, response and next

      You can use a decorator to inject Express.Request, Express.Response and Express.NextFunction services instead of the classic call provided by Express API.

      Here an example to use these decorators:

      import {Controller, Get, Next, Req, Res} from "@tsed/common";
      import * as Express from "express";
      
      @Controller("/calendars")
      export class CalendarCtrl {
        @Get("/:id")
        get(
          @Req() request: Express.Request,
          @Res() response: Express.Response,
          @Next() next: Express.NextFunction
        ): void {
          setTimeout(() => {
            response
              .status(200)
              .send({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

      Inject router

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

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

      WARNING

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

      Advanced usage

      Templating

      Template feature depending on the engine rendering use by your application. Ts.ED provide decorator to define a view which will be used by the Endpoint to generate the response.

      Here an example of a controller which use the decorator:

      import {Controller, Get, Render} from "@tsed/common";
      
      @Controller("/events")
      export class EventCtrl {
      
        @Get("/:id")
        @Render("eventCard.ejs")
        public get(): any {
          return {startDate: new Date(), name: "MyEvent"};
        }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      And his view:

      <h1><%- name %></h1>
      <div>
          Start: <%- startDate %>
      </div>
      
      1
      2
      3
      4

      TIP

      See our guide to install the engine rendering with Ts.ED.

      Middlewares

      The middleware is a function which is called before the route handler. Middleware functions have access to the request and response objects, and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.

      TIP

      For more details about Middleware declaration see the Middlewares section.

      The following decorators lets you add custom middleware on a method or on controller:

        Example

        import {Controller, Get, PathParams, Use, UseAfter, UseBefore} from "@tsed/common";
        import {CustomBeforeMdlw, CustomMiddleware} from "../middlewares/middlewares";
        
        @Controller("/calendars")
        @UseBefore(CustomBeforeMdlw)
        export class CalendarCtrl {
          @Get("/:id")
          @Use(CustomMiddleware)
          get1(@PathParams("id") id: number): any {
            return {id};
          }
        
          @Get("/:id")
          @UseBefore(CustomMiddleware)
          get2(@PathParams("id") id: number): any {
            return {id};
          }
        
          @Get("/:id")
          @UseAfter(CustomMiddleware)
          get3(@PathParams("id") id: number): any {
            return {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

        Middleware call sequence

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

        TIP

        See middlewares section for more information.

        Child controllers

        A controller can have one or more child controller. This feature allows you to combine your controllers with each other to define your routes. One controller can be added to multiple controllers, so you can easily reuse the same controller.

        import {Controller, Get, RouteService, ServerLoader, ServerSettings} from "@tsed/common";
        
        @Controller("/events")
        export class EventCtrl {
          @Get()
          get() {
          }
        }
        
        @Controller({
          path: "/calendars",
          children: [EventCtrl]
        })
        export class CalendarCtrl {
          @Get()
          get() {
          }
        }
        
        @Controller({
          path: "/rest",
          children: [
            CalendarCtrl,
            EventCtrl
          ]
        })
        export class RestCtrl {
          constructor(private routeService: RouteService) {
          }
        
          @Get()
          get() {
            return this.routeService.printRoutes();
          }
        }
        
        @ServerSettings({
          mount: {
            "/": [
              RestCtrl
            ]
          }
        })
        class Server extends ServerLoader {
        }
        
        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
        37
        38
        39
        40
        41
        42
        43
        44
        45

        This example will produce these following routes:

        Verb Route Method
        GET /rest RestCtrl.get()
        GET /rest/calendars CalendarCtrl.get()
        GET /rest/calendars/events EventCtrl.get()
        GET /rest/events EventCtrl.get()

        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 :

        import {Controller, Get, PathParams} from "@tsed/common";
        
        @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
        12
        13

        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 decorator. See example:

        import {Controller, Get, PathParams, MergeParams} from "@tsed/common";
        
        @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
        13
        14

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

        TIP

        caseSensitive and strict options are also supported with his respective decorators and .

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

        import {Get, QueryParams} from "@tsed/common";
        import {SomeService} from "./SomeService";
        
        export abstract class BaseCtrl {
          constructor(private someService: SomeService) {
          }
        
          @Get("/list")
          async list(@QueryParams("search") search: any) {
            return this.someService.list(search);
          }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        Then, on your children controller:

        import {Controller, Get, PathParams} from "@tsed/common";
        import {BaseCtrl} from "./BaseCtrl";
        
        @Controller("/child")
        export abstract class ChildCtrl extends BaseCtrl {
          @Get("/:id")
          get(@PathParams("id") id: string): any {
            return {id: id};
          }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        Decorators