内置装饰器
@Module
@Module 声明模块
ts1@Module({ 2 imports: [], 3 controllers: [AppController], 4 providers: [AppService], 5})
@Controller
声明 Controller
typescript1@Controller() 2export class AppController { 3 constructor(private readonly appService: AppService) {} 4}
@Controller() 装饰器内还可以传入一些属性,比如指定 path
typescript1@Controller({ path: 'admin' })
这样就相当于在原有路由的基础上额外增加 admin 路由。
举个例子,当前的路由访问http://127.0.0.1:3000/hello可以获取到 Hello World:
typescript1@Controller() 2export class AppController { 3 constructor(@Inject('AppService') private readonly appService: AppService) {} 4 5 @Get('/hello') 6 getHello(): string { 7 return this.appService.getHello() 8 } 9}
当加入 path 后:
diff1- @Controller() 2+ @Controller({ path: 'admin' }) 3export class AppController { 4 constructor(@Inject('AppService') private readonly appService: AppService) {} 5 6 @Get('/hello') 7 getHello(): string { 8 return this.appService.getHello(); 9 } 10}
就需要访问http://127.0.0.1:3000/admin/hello才能获取到了。
@Injectable
声明 provider
typescript1@Injectable() 2export class AppService { 3 getHello(): string { 4 return 'Hello World!' 5 } 6}
只要是 class,都可以用 @Injectable 表示该 class 是可注入到其他模块的。
@Inject
注入时可以使用@Inject
typescript1export class AppController { 2 @Inject(AppService) 3 private readonly appService: AppService 4}
效果相当于使用构造器
typescript1@Controller() 2export class AppController { 3 constructor(private readonly appService: AppService) {} 4 5 @Get() 6 getHello(): string { 7 return this.appService.getHello() 8 } 9}
如果 token 是字符串的话,也可以直接传入 token 来注入:
token:
typescript1@Module({ 2 imports: [], 3 controllers: [AppController], 4 providers: [{ provide: 'AppService', useClass: AppService }], 5})
注入 token:
typescript1export class AppController { 2 @Inject('AppService') 3 private readonly appService: AppService 4 5 @Get() 6 getHello(): string { 7 return this.appService.getHello() 8 } 9}
@Optional
如果传入不正确的 token,创建AppController时会报错,但如果它是可选的,就可以使用@Optional 声明一下,这样没有对应的 provider 也能正常创建这个对象。
typescript1@Controller() 2export class AppController { 3 @Optional() 4 @Inject('service') 5 private readonly service: Record<string, any> 6 7 constructor(@Optional() @Inject('AppService') private readonly appService: AppService) {} 8 9 @Get() 10 getHello(): string { 11 return this.appService.getHello() 12 } 13}
@Global
用@Global 来声明全局模块,这样它 exports 的 provider 就可以直接注入无需 imports 了
typescript1@Global() 2@Module({ 3 imports: [], 4 controllers: [AppController], 5 providers: [AppService], 6 exports: [AppService], 7})
@Catch
filter 是处理抛出的未捕获异常的,通过 @Catch 来指定处理的异常:
typescript1@Catch(HttpException) 2export class HttpExceptionFilter implements ExceptionFilter { 3 catch(exception: HttpException, host: ArgumentsHost) { 4 const ctx = host.switchToHttp() 5 const response = ctx.getResponse() 6 const request = ctx.getRequest() 7 const status = exception.getStatus() 8 response.status(status).json({ 9 statusCode: status, 10 path: request.url, 11 message: exception.message, 12 }) 13 } 14}
@UseFilters
filter 通过 @UseFilters 应用到 handler 上:
typescript1 @Get() 2 @UseFilters(HttpExceptionFilter) 3 getHello(): string { 4 throw new HttpException('Error', HttpStatus.BAD_REQUEST); 5 return this.appService.getHello(); 6 }
@UseGuards
路由守卫
typescript1@Controller() 2export class AppController { 3 @Get() 4 @UseGuards(RolesGuard) 5 getHello(): string { 6 return 'Hello World!' 7 } 8}
@UseInterceptors
拦截器
typescript1@Controller() 2export class AppController { 3 @Get() 4 @UseInterceptors(new RouteInterceptor()) 5 getHello(): string { 6 return 'Hello World!' 7 } 8}
@UsePipes
Pipe
typescript1 @Post() 2 @UsePipes(AppPipe) 3 async create(@Body() createUser: CreateUserDto) { 4 // ... 5 }
@Param
取出 param,同时支持 Pipe 在单个 param 上的应用
ts1 @Get(':id') 2 getHello( 3 @Param('id', IdPipe) id: string, 4 @Query('name') name: string, 5 ): string { 6 console.log('——————🚀🚀🚀🚀🚀 —— name:', name); 7 console.log('——————🚀🚀🚀🚀🚀 —— id:', id); 8 return this.appService.getHello(); 9 }
@Query
取出 Query,同时支持 Pipe 在单个 Query 上的应用
typescript1 @Get(':id') 2 getHello( 3 @Param('id') id: string, 4 @Query('name', QueryPipe) name: string, 5 ): string { 6 console.log('——————🚀🚀🚀🚀🚀 —— name:', name); 7 console.log('——————🚀🚀🚀🚀🚀 —— id:', id); 8 return this.appService.getHello(); 9 }
@Body
从请求体中取出对应的数据
typescript1@Controller('user') 2export class UserController { 3 @Post('get') 4 body(@Body() getPersonDto: PersonDto) { 5 return `received: ${JSON.stringify(getPersonDto)}` 6 } 7}
类型则定义在 dto 中:
typescript1// src/dto/person.dto.ts 2 3export class PersonDto { 4 name: string 5 age: number 6}
@[Method]
除了 @Get、@Post 外,还可以用 @Put、@Delete、@Patch、@Options、@Head 装饰器
@SetMetadata
handler 和 class 可以通过 @SetMetadata 指定 metadata:
typescript1@Controller() 2@UseGuards(AppGuard) 3@SetMetadata('roles', 'user') 4export class AppController { 5 constructor(@Inject('AppService') private readonly appService: AppService) {} 6 7 @Get('/hello') 8 @SetMetadata('roles', 'admin') 9 getHello(): string { 10 return this.appService.getHello() 11 } 12}
metadata 可以在 guard 或者 Interceptor 中取出来:
typescript1@Injectable() 2export class AppGuard implements CanActivate { 3 @Inject(Reflector) 4 private readonly reflector: Reflector 5 canActivate(ctx: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { 6 const classMetadata = this.reflector.get('roles', ctx.getClass()) 7 const methodMetadata = this.reflector.get('roles', ctx.getHandler()) 8 console.log('——————🚀🚀🚀🚀🚀 —— classMetadata:', classMetadata) 9 console.log('——————🚀🚀🚀🚀🚀 —— methodMetadata:', methodMetadata) 10 return true 11 } 12}
得到的结果:
bash1——————🚀🚀🚀🚀🚀 —— classMetadata: user 2——————🚀🚀🚀🚀🚀 —— methodMetadata: admin
@Headers
通过 @Headers 装饰器取某个请求头 或者全部请求头:
typescript1 @Get('/hello') 2 getHello( 3 @Headers('Accept') accept: string, 4 @Headers() headers: Headers, 5 ): string { 6 console.log('——————🚀🚀🚀🚀🚀 —— accept:', accept); 7 console.log('——————🚀🚀🚀🚀🚀 —— headers:', headers); 8 return this.appService.getHello(); 9 }
@Ip
获取请求 IP 地址
typescript1 @Get('/ip') 2 getIp(@Ip() ip: string): string { 3 console.log('——————🚀🚀🚀🚀🚀 —— ip:', ip); 4 return ip; 5 }
@Session
利用@Session 装饰器可以获取到 session 对象。
在使用 session 之前,需要安装一个插件:
bash1pnpm install express-session
在 main.ts 里引入并启用该插件:
typescript1async function bootstrap() { 2 const app = await NestFactory.create(AppModule) 3 app.use( 4 session({ 5 secret: 'secret', 6 cookie: { maxAge: 60000 }, 7 }), 8 ) 9 await app.listen(3000) 10}
上面的代码指定了密钥跟 cookie 的过期时间。
接着刷新页面:

可以看到 Response 里已经设置了 cookie 信息。
之后的每次请求浏览器都会自动在 request header 中带上 cookie。

现在我们就可以读取和设置 session 啦
typescript1 @Get('/session') 2 getSession(@Session() se: Record<string, any>) { 3 console.log('——————🚀🚀🚀🚀🚀 —— se:', se.id); 4 if (!se.count) { 5 se.count = 0; 6 } 7 se.count += 1; 8 return { count: se.count, id: se.id }; 9 }
第一次的结果:
typescript1{ 2"count": 1, 3"id": "GSNcXmu4fHQLVtxVKCc3QIbp38m4l1Zh" 4}
只要在同一个没有过期的会话中,每次请求都会让 count+1,而 id 不变:
typescript1// 第二次请求 2{ 3"count": 2, 4"id": "GSNcXmu4fHQLVtxVKCc3QIbp38m4l1Zh" 5}
@HostParam
在@Controller 装饰器内还可以指定生效的 path:
typescript1@Controller({ host: ':host.0.0.1' }) 2export class AppController { 3 constructor(@Inject('AppService') private readonly appService: AppService) {} 4 5 @Get('/hello') 6 getHello(): string { 7 return this.appService.getHello() 8 } 9}
现在仅能通过xxx.0.0.1访问路由才有效,通过localhost访问是无效的。

host 里的参数就可以通过 @HostParam 取出来:
typescript1@Controller({ host: ':host.0.0.1' }) 2export class AppController { 3 constructor(@Inject('AppService') private readonly appService: AppService) {} 4 5 @Get('/hello') 6 getHello(@HostParam('host') host): string { 7 console.log('——————🚀🚀🚀🚀🚀 —— host:', host) 8 return this.appService.getHello() 9 } 10}
访问后的结果为:
bash1——————🚀🚀🚀🚀🚀 —— host: 127
@Req
像@Headers、@Body、@Ip等装饰器都帮我们快速从 request 对象中获取信息。如果我们想自己获取,也是可以的,@Req 就是将 Request 对象注入进来的装饰器。
typescript1 @Get('/hello') 2 getHello(@Req() req: Request): string { 3 console.log('——————🚀🚀🚀🚀🚀 —— req:', req); 4 return this.appService.getHello(); 5 }
通过 @Req 或者 @Request 装饰器,这俩是同一个东西,nest 源码里有类型声明:
typescript1export declare const Request: () => ParameterDecorator 2export declare const Req: () => ParameterDecorator
注入 request 对象后,可以手动取任何参数。
@Res
@Res 和 @Response 也是同一个东西:
typescript1export declare const Response: (options?: ResponseDecoratorOptions) => ParameterDecorator 2export declare const Res: (options?: ResponseDecoratorOptions) => ParameterDecorator
但注入 response 对象后需要手动调用才能响应而不是直接 return :
typescript1 @Get('/hello') 2 getHello(@Req() req: Request, @Res() res: Response) { 3 console.log('——————🚀🚀🚀🚀🚀 —— req:', req); 4 // return this.appService.getHello(); 5 res.end(this.appService.getHello()); 6 }
Nest 这么设计是为了避免你自己返回的响应和 Nest 返回的响应的冲突。
如果你不会自己返回响应,可以通过 passthrough 参数告诉 Nest:
typescript1 @Get('/hello') 2 getHello(@Req() req: Request, @Res({ passthrough: true }) res: Response) { 3 console.log('——————🚀🚀🚀🚀🚀 —— res:', res); 4 console.log('——————🚀🚀🚀🚀🚀 —— req:', req); 5 return this.appService.getHello(); 6 }
现在能够正常响应了。
@Next
当你有两个 handler 来处理同一个路由的时候,可以在第一个 handler 里注入 next,调用它来把请求转发到第二个 handler。
typescript1 @Get('/hello') 2 async getHello1(@Next() next: NextFunction) { 3 console.log('getHello1'); 4 const a = await next(); 5 // the code below will not be executed 6 console.log('——————🚀🚀🚀🚀🚀 —— a:', a); // undefined 7 return '123'; 8 } 9 @Get('/hello') 10 async getHello2() { 11 console.log('getHello2'); 12 return this.appService.getHello(); 13 }
这里的 log 打印结果为:
bash1getHello1 2getHello2 3——————🚀🚀🚀🚀🚀 —— a: undefined
注入 @Next 的 handler 不会处理返回值,所以上面代码中的 123不会被返回。
@HttpCode
handler 默认返回的是 200 的状态码,你可以通过 @HttpCode 修改它:
typescript1 @Get('/hello') 2 @HttpCode(202) 3 getHello() { 4 return this.appService.getHello(); 5 }

@Header
修改 response Header
tsx1 @Get('/hello') 2 @Header('reference', 'nest') 3 getHello() { 4 return this.appService.getHello(); 5 }

@Redirect
将路由重定向到其他 url 上。
typescript1 @Get('/hello') 2 @Redirect('/hello2') 3 getHello() {} 4 5 @Get('/hello2') 6 getHello2() { 7 return this.appService.getHello(); 8 }

@Render
使用@Render 可以给响应内容指定渲染模版,不过需要先安装模版引擎的包 hbs
bash1pnpm install hbs
然后准备图片和模版文件

home.hbs 内写入渲染模版
html1<img src="/nature.jpg" /> 2<p>{{name}}</p> 3<div>{{age}}</div>
再分别指定静态资源的路径和模版的路径,并指定模版引擎为 handlerbars。
typescript1import { NestFactory } from '@nestjs/core' 2import { NestExpressApplication } from '@nestjs/platform-express' 3import { AppModule } from './app.module' 4import { join } from 'path' 5 6async function bootstrap() { 7 const app = await NestFactory.create<NestExpressApplication>(AppModule) 8 app.useStaticAssets(join(process.cwd(), 'public')) 9 app.setBaseViewsDir(join(process.cwd(), 'views')) 10 app.setViewEngine('hbs') 11 await app.listen(3000) 12} 13bootstrap()
最后在 handler 中使用@Render 装饰器指定使用哪个模版
jsx1 @Get('/hello') 2 @Render('home') 3 getHello() { 4 return { name: 'qyx', age: 18 }; 5 }
效果如下:

总结
- @Module: 声明 Nest 模块
- @Controller:声明模块里的 controller
- @Injectable:声明模块里可以注入的 provider
- @Inject:通过 token 手动指定注入的 provider,token 可以是 class 或者 string
- @Optional:声明注入的 provider 是可选的,可以为空
- @Global:声明全局模块
- @Catch:声明 exception filter 处理的 exception 类型
- @UseFilters:路由级别使用 exception filter
- @UsePipes:路由级别使用 pipe
- @UseInterceptors:路由级别使用 interceptor
- @SetMetadata:在 class 或者 handler 上添加 metadata
- @Get、@Post、@Put、@Delete、@Patch、@Options、@Head:声明 get、post、put、delete、patch、options、head 的请求方式
- @Param:取出 url 中的参数,比如 /aaa/:id 中的 id
- @Query: 取出 query 部分的参数,比如 /aaa?name=xx 中的 name
- @Body:取出请求 body,通过 dto class 来接收
- @Headers:取出某个或全部请求头
- @Session:取出 session 对象,需要启用 express-session 中间件
- @HostParm: 取出 host 里的参数
- @Req、@Request:注入 request 对象
- @Res、@Response:注入 response 对象,一旦注入了这个 Nest 就不会把返回值作为响应了,除非指定 passthrough 为 true
- @Next:注入调用下一个 handler 的 next 方法
- @HttpCode: 修改响应的状态码
- @Header:修改响应头
- @Redirect:指定重定向的 url
- @Render:指定渲染用的模版引擎