Learn Nest 1: Controller

Nest is a framework for building efficient and scalable Nodejs server-side applications, It supports Typescript, and combines with OOP, FP and FRP paradigm. Nest is based on low-level HTTP Server framework (Express or Fastify), but provides a higher level of abstraction and many interesting features like Dependency Injection, module (for better responsibility division), controller definement (based on decorator) and etc for better biz coding.

There are several concept in Nest—Controller, Provider, Module, Middleware, Exception Filter, Pipe, Custom Decorator, Guard, Interceptor—To learn these concept (and relationships between them) is to learn Nest.


Controllers are responsible for handling incoming requests and returning response to the client, just like RESTful Controller in Spring MVC. Routing mechanism will controls which controller handle which requests. Frequently each controller has multiple routes and different routes can perform different actions.

routes

@Controller() decorator is required to define a basic controller, it can accept a optional string as prefix to group a set of related routes. We can use decorators like @Get(), @Post()(named HTTP request method decorator) to define endpoint for HTTP request, which corresponds HTTP request method and route paths, such a route path is determined by concatenating controller’s prefix and path specified in method’s decorator. method decorated by such HTTP request method decorator is called request handler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Controller, Get, Post } from '@nestjs/common';

@Controller('cats')
export class CatsController {
@Get() // mapping GET /cats
findAll(): string {
return 'This action returns all cats';
}

@Post('add') // mapping POST /cats/add
addCat(): string {
return 'This action add new cat';
}
}

two ways to manipulating response

When a request is made to this endpoint(such like ‘/cats’), Nest will route the request to the user-defined method findAll(). Method name is completely arbitrary.

This method will return 200 status code and the plain response 'This action returns all cats', It may seems strange from the viewpoint of low-level HTTP framework like Express. It’s because that Nest employs two different option to manipulating response: Standard (recommended) or Library-specific.

Standard approach make use of request handler’s return value. when it returns a javascript object or array, it will be automatically serialized to JSON. when it returns primitive types like string, number, boolean, Nest will send just the value. status code is always 200 and POST requests will use 201, but this behavior can be modified by @HttpCode(...) decorator at handler-level (i.e. method level, @Controller is class-level).

If request handler use either @Res() or @Next(), Library-specific option will be chosen. Library-specific option use library’s native response object to handle response, which can be injected using @Res() decorator (e.g. findAll(@Res() response: Response)). With this approach, you have the ability to use native response handling methods exposed by the object, like response.status(200).send().

Return value also can be either Promise or Observable, It means that Nest supports and works well with Javascript asynchronous programming model.

request object

It’s often to access request details like query param and request body, we can access the request directly by instructing Nest to inject request object using @Req() decorator.

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

import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('cats')
export class CatsController {
@Get()
findAll(@Req() request: Request): object {
return {
query: request.query,
body: request.body,
};
}
}

There are several dedicating decorators to grab specific properties from request object, It should be mentioned that request body is deserialized as JSON by default, and header name is case insensitive:

Decorator corresponding object/field type
@Request(),@Req() req Request
@Response(), @Res() res Response
@Nest() next Function
@Session() req.session
@Param() req.params Record<string, string>
@Param(key: string) req.params[key] undefined | string
@Body() req.body object
@Body(key: string) req.body[key] unknown
@Query() req.query Record<string, string | string[]>
@Query(key: string) req.query[key] undefined | string | string[]
@Headers() req.headers Record<string, string>
@Headers(key: string) req.headers undefined | string
@Ip() req.ip string
@HostParam() req.hosts object

There is a tiny example below, It should be warned that type signature of parameters of request handler makes nonsense at runtime, thus type constraint should be guaranteed by programmers themselves (But we can use ValidationPipe to do this declaratively):

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { Body, Controller, Get, Param, Query, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';
import { z } from 'zod';
import { AppService } from './app.service';
import { Numeric, ZodValidator } from './configuration/ZodValidator';

type SomeBody = {
name: string;
age: number;
};

@Controller('/learn')
export class AppController {
// constructor injection
// can use @Inject to indicate Token
constructor(private readonly appService: AppService) {}

// Get, Post, Delete...
// return primitive value
@Get()
getHello() {
return 'hello';
}

@Get('/hello') // define subroute
anotherHello(): any[] {
// array and object return as JSON
return [1, 2, 3, 'hello'];
}
@Get('/req')
// use Req, Request to inject Request Object, use Res, Response to inject Response Object
getReq(@Req() req: Request): object {
return { params: req.query };
}
@Get('/res')
getRes(@Res() res: Response): string {
res.send('send data by hand');
return 'ss'; // will be ignore cause it's library-specific option now
}
// other Decorator: Next, Session, Param, Body, Query, Headers, Ip...
@Get('/param/:name')
// typesafe should be maintained by hand
param(
@Param('name') param: string,
@Query() allQueryParam: Record<string, string | string[]>, // get All Query Param
@Query('param') name?: string,
): object {
return { param, name, allQueryParam };
}

// NO TYPE VALIDATION! It will accept any JSON object
@Get('/body')
body(@Body() body: SomeBody) {
return body;
}

static BodySchema = z.object({
name: z.string(),
age: Numeric(),
});
// use pipe as Validation
@Get('/validBody')
validBody(
@Body(ZodValidator(AppController.BodySchema))
body: z.infer<typeof AppController.BodySchema>,
) {
return body;
}

@Get('/id/:id')
// it will accept param like '123abc'
paramType(@Param('id') id: number) {
return id + 1;
}

// promise and rxjs is supported
@Get('/async')
async asyncFn() {
await new Promise((resolve) => setTimeout(resolve, 2000));
return 'rua';
}
}

With the above controller fully defined, Nest still doesn’t know that CatsController exists and as a result won’t create an instance of this class. There is no such a @ComponentScan in Nest :).

In Nest, Controllers always belong to a module, which is included in controllers array within @Module() decorator and we should include CatsController in any Modules as below.

1
2
3
4
5
6
7
8
9
10
11
12
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { LoggerMiddleware } from './LoggerMiddleware';

@Module({
controllers: [CatsController],
})
export class CatsModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('/');
}
}

总结

使用英语写笔记是一个尝试,最开始的时候肯定是大范围地照抄文档,但也要不断学习更多表述,并不断尝试复述,先把量堆上去。

  • is responsible for, 负责

Controller is responsible for handling incoming request and returning responses to the client.

  • sth’s purpose is to do sth, …的用途是…

A controller’s purpose is to receive specific requests for the application.

  • The main idea of … is that …,……的用途/意义/特点是……

The main idea of Provider is that it can be injected as a dependency.

  • sth is required to do sth, …对…是必需的

@Controller decorator is required to define a basic controller.

  • perform action, 执行操作
  • corresponds to/with, 同…相一致
  • corresponding,对应的
  • is determined by, 由什么决定/得出
  • sth specified in, 在…里给定的,同 given from/by?

Route path is determined by concatenating controller’s prefix and path specified in method’s decorator.

  • employ, 采用(观点),对应 adopt(采用方法)?

Nest will employ two different option to manipulating response.

  • behavior,行为;feature,特性;mechanism,机制;function,功能
  • with this approach, 使用这种方式/在这种情况下……
  • with …,随着,之后

with the above controller fully defined, …

  • as a result …,因此,thus
  • have the ability to,有能力做某事
  • instruct,命令,要求

reference