# 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 for managing and dispatching 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";
}
}
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.
# Decorators
Ts.ED provides a decorator for each HTTP verb which can be used to handle a request:
Other decorators are provided to describe your route with OpenSpec, adding middlewares, adding some constraints or adding headers:
- Deprecated
- Consumes
- Produces
- Security
- Summary
- AcceptMime
- AuthOptions
-
Authenticated - ContentType
- Header
- Location
-
ReturnType - Returns
- ReturnsArray
- Status
- Use
- UseAfter
- UseAuth
- UseBefore
- UseBeforeEach
- View
# Configuration
You can add your controller by adding glob pattern on mount
ServerSettings attributes or by importing manually your controller.
Here an example:
import {Configuration} from "@tsed/common";
import {CalendarCtrl} from "./controllers/CalendarCtrl";
@Configuration({
mount: {
"/rest": `./controllers/*.ts`, // using componentScan
// Using manual import
"/manual": [
CalendarCtrl
]
}
})
export class Server {
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# Create multiple versions of your API
As you have seen in the previous example, the mount
attribute is an object that let you provide the global endpoint for all your controllers under the controllers
folder.
You can add more configurations to mount different endpoints associated to a folder. Here is another configuration example:
import {Configuration} from "@tsed/common";
@Configuration({
mount: {
"/rest/v0": "./controllers/v0/**/*.ts",
"/rest/v1": "./controllers/v1/**/*.ts"
}
})
export class Server {
}
2
3
4
5
6
7
8
9
10
# Async and Promise
Ts.ED works well with Promise and async
function.
Every async function has to return a Promise
.
This means that you can return a deferred value that Ts.ED will be able to resolve by itself.
Let's see an example of this:
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"
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Observable/Stream/Buffer
Also, Ts.ED support function that return Observable
, Stream
or Buffer
.
import {Controller, Get} from "@tsed/common";
import {createReadStream, ReadStream} from "fs";
import {Observable, of} from "rxjs";
@Controller("/")
export class KindOfResponseCtrl {
@Get("/observable")
observable(): Observable<any[]> {
return of([]);
}
@Get("/stream")
stream(): ReadStream {
return createReadStream(__dirname + "/response.txt");
}
@Get("/stream")
buffer(): Buffer {
return Buffer.from("Hello");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Axios response
5.48.0+Sometime, you just want call another API to proxy a webservice. Axios is an excellent library to call API in Node.js and Ts.ED is able to handle Axios response to wrap it into an Express.js response.
import {Controller, Get, QueryParams, Res} from "@tsed/common";
import Axios from "axios";
import {IncomingMessage} from "http";
@Controller("/proxy")
export class ProxyCtrl {
@Get("/")
proxy(@QueryParams("path") path: string) {
return Axios.get(`https://cerevoice.s3.amazonaws.com/${path}`, {
responseType: "stream"
});
}
// is equivalent to doing that
@Get("/")
async proxy2(@Res() res: Res, @QueryParams("path") path: string): IncomingMessage {
const response = await Axios.get(`https://cerevoice.s3.amazonaws.com/${path}`, {
responseType: "stream"
});
res.set(response.headers);
res.status(response.status);
return response.data;
}
}
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
# Multiple endpoints, single method
Ts.ED lets you define multiple endpoints on the same method, with the 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";
}
}
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 a calendar by its 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 CalendarsController {
@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
}
}
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
# Decorators
- Allow
- Required
- Context
- BodyParams
- CookiesParams
- Cookies
- Err
- HeaderParams
- Locals
- Next
- ParamFn
- PathParams
- RawPathParams
- QueryParams
- RawQueryParams
- Request
- Req
- Response
- Res
- ResponseData
- Session
- UseDeserialization
- UseParam
-
UseFilter - UseParamExpression
- UseParamType
- UsePipe
- UseType
- UseValidation
# Input parameters
Getting parameters from Express Request can be done by using the following decorators:
- :
Express.request.body
- :
Express.request.params
- :
Express.request.params
without transformation and validation, - :
Express.request.query
- :
Express.request.query
without transformation and validation,
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;
}
}
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, accepts to give a object as parameter 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;
}
}
2
3
4
5
6
7
8
9
10
TIP
Since v5.51.0+, decorator accept a model to transform Express.request.query
plain object to a Class.
class QueryParamsModel {
@Required()
@MinLength(1)
name: string;
@Property()
duration: number;
}
@Controller("/")
class QueryController {
@Get("/")
get(@QueryParams() params: QueryParamsModel, @QueryParams("locale") locale: string) {}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# Headers
decorator provides 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;
}
}
2
3
4
5
6
7
8
9
10
11
12
# Session/Cookies/Locals/Context
For the session, cookies, locals or context data attached on the request, it works the same way as seen before. Use the following decorators to get the data:
# Locals
is a request property used by third-party like template engine to render a page by the server. If you attach data on it, you'll expose this data to the template.
If you don't want that, don't use this attribute!
Here is an example:
import {Controller, Get, Locals, Middleware, UseBefore, View} from "@tsed/common";
@Middleware()
class LocalsMiddleware {
use(@Locals() locals: any) {
// set some on locals
locals.user = "user";
}
}
@Controller("/")
@UseBefore(LocalsMiddleware)
class MyCtrl {
@Get("/")
@View("home.ejs") // will use locals and returned data to render the page
get(@Locals("user") user: any) {
console.log("user", user);
return {
description: "Hello world"
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Context
See our dedicated page on PlatformContext for more details.
# Response
# Decorators
- Produces
- AcceptMime
- ContentType
- Header
-
ReturnType - Returns
- ReturnsArray
- Status
- View
- Locals
- Response
- Res
# 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"};
}
}
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";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# Header
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}`;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Throw exceptions
You can use @tsed/exceptions 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 is an example:
import {Controller, Get, PathParams} from "@tsed/common";
import {BadRequest} from "@tsed/exceptions";
@Controller("/calendars")
export class CalendarCtrl {
@Get("/:id")
get(@PathParams("id") id: number): any {
if (isNaN(+id)) {
throw(new BadRequest("Not a number"));
}
return {id};
}
}
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.
TIP
See our guide on HttpExceptions to throw customer HttpExceptions
# 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();
});
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Inject router
Each controller has a which wrap the original router from [Express.Router](http://expressjs.com/en/guide/routing.html You can inject in your controller to add anything related to the Router itself.
import {Controller, PlatformRouter} from "@tsed/common";
@Controller("/calendars")
export class CalendarCtrl {
constructor(router: PlatformRouter) {
router.get("/", this.myMethod);
// GET raw router (Express.Router)
console.log(router.raw);
}
myMethod(req: any, res: any, next: any) {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
WARNING
All of these routes added by this way won't be discovered by Ts.ED to produce Swagger documentation.
# Advanced usage
# Templating
A template engine like EJS or Handlebars can be used to change the response returned by your endpoint. Like Express.js, you need to configure the templating engine so that you can use it later with the decorator.
Here is an example of a controller which uses the decorator:
import {Controller, Get, View} from "@tsed/common";
@Controller("/events")
export class EventCtrl {
@Get("/:id")
@View("eventCard.ejs")
public get(): any {
return {startDate: new Date(), name: "MyEvent"};
}
}
2
3
4
5
6
7
8
9
10
11
And its view:
<h1><%- name %></h1>
<div>
Start: <%- startDate %>
</div>
2
3
4
TIP
To configure a template engine with Ts.ED, 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};
}
}
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 Server, 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 controllers. 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.
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 cases you need to have 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);
}
}
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 this example:
import {Controller, Get, PathParams} from "@tsed/common";
import {MergeParams} from "@tsed/platform-express";
@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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Now, calendarId will have the value given in the context path.
TIP
caseSensitive
and strict
options are also supported with their respective decorators and .
# Inheritance
Ts.ED supports 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);
}
}
2
3
4
5
6
7
8
9
10
11
12
Then, on your child 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};
}
}
2
3
4
5
6
7
8
9
10
# Decorators
- Deprecated
- Consumes
- Produces
- Security
- Summary
- Allow
- Required
- Context
- Controller
- AcceptMime
- AuthOptions
-
Authenticated - ContentType
- Header
- Location
-
ReturnType - Returns
- ReturnsArray
- All
- Get
- Post
- Put
- Delete
- Head
- Patch
- Options
- Status
- Use
- UseAfter
- UseAuth
- UseBefore
- UseBeforeEach
- View
- BodyParams
- CookiesParams
- Cookies
- Err
- HeaderParams
- Locals
- Next
- ParamFn
- PathParams
- RawPathParams
- QueryParams
- RawQueryParams
- Request
- Req
- Response
- Res
- ResponseData
- Session
- UseDeserialization
- UseParam
-
UseFilter - UseParamExpression
- UseParamType
- UsePipe
- UseType
- UseValidation
- Session & cookies
- Passport.js
- TypeORM
- Mongoose
- GraphQL
- Socket.io
- Swagger
- AJV
- Multer
- Serve static files
- Templating
- Throw HTTP Exceptions
- Customize 404
- AWS
- Jest
- Seq
- Controllers
- Providers
- Model
- Converters
- Middlewares
- Pipes
- Interceptors
- Authentication
- Hooks
- Injection scopes
- Custom providers
- Custom endpoint decorator
- Testing