{T}

NestJS工作原理与装饰器详解

学习目标:理解 NestJS 的完整工作流程,掌握装饰器的原理和参数装饰器的使用。


一、NestJS 工作流程图解

1.1 完整请求流程

code
NestJS 请求处理流程:
│
├── 前端发起请求
│   └── HTTP Request(GET、POST、PUT、DELETE)
│
├── Controller 层(控制器)
│   ├── 接收请求
│   ├── 解析参数
│   ├── 调用 Service
│   └── 返回响应
│
├── Service 层(服务)
│   ├── 业务逻辑处理
│   ├── 数据转换
│   └── 调用 ORM
│
├── ORM 层(对象关系映射)
│   ├── 转换 SQL
│   └── 执行数据库操作
│
├── Database 层(数据库)
│   ├── 执行 SQL
│   ├── 读取/写入数据
│   └── 返回结果
│
└── 响应返回
    ├── Database → ORM → Service → Controller → 前端
    └── Response(JSON、HTML、XML)

1.2 各层职责详解

层级职责关键词示例
Controller接收请求、解析参数、返回响应路由、参数解析@Controller('users')
Service业务逻辑处理、数据转换业务规则、数据处理@Injectable()
ORM对象关系映射、SQL 转换Prisma、TypeORMprisma.user.findMany()
Database数据持久化、查询执行PostgreSQL、MySQLSELECT * FROM users

1.3 RESTful 请求类型

code
RESTful API 请求类型:
│
├── GET
│   ├── 作用:获取资源
│   ├── 示例:GET /users
│   └── 特点:幂等、安全
│
├── POST
│   ├── 作用:创建资源
│   ├── 示例:POST /users
│   └── 特点:非幂等
│
├── PUT
│   ├── 作用:更新资源(整体)
│   ├── 示例:PUT /users/1
│   └── 特点:幂等
│
├── PATCH
│   ├── 作用:更新资源(部分)
│   ├── 示例:PATCH /users/1
│   └── 特点:幂等
│
└── DELETE
    ├── 作用:删除资源
    ├── 示例:DELETE /users/1
    └── 特点:幂等

二、装饰器(Decorators)原理

2.1 什么是装饰器

装饰器定义

  • 装饰器是一个函数
  • 用于修改类、方法、属性或参数的行为
  • 类似于 Java 的注解(Annotation)

官方定义(TypeScript 文档):

typescript
// 装饰器本质上就是一个函数
function sealed(target: any) {
  // target 就是使用装饰器的那个类或方法
  console.log('装饰器被调用', target);
}

// 使用装饰器
@sealed
class Person {
  name: string;
}

等价于

typescript
// 装饰器会被编译成函数调用
const Person = sealed(class Person {
  name: string;
});

2.2 装饰器的工作原理

编译前(TypeScript):

typescript
@Controller('users')
export class UserController {
  @Get()
  findAll() {
    return 'This action returns all users';
  }
}

编译后(JavaScript):

javascript
// 生成的元数据
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  var c = arguments.length,
      r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
      d;
  for (var i = decorators.length - 1; i >= 0; i--) {
    if (d = decorators[i]) {
      r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    }
  }
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};

// 应用装饰器
UserController = __decorate([
  (0, common_1.Controller)('users')
], UserController);

__decorate([
  (0, common_1.Get)()
], UserController.prototype, "findAll", null);

核心逻辑

code
装饰器执行过程:
│
├── 第一步:定义装饰器函数
│   └── function Controller(path: string) { ... }
│
├── 第二步:TypeScript 编译器识别装饰器
│   └── 发现 @Controller、@Get 等装饰器
│
├── 第三步:生成元数据(Metadata)
│   ├── __decorate 函数
│   └── Reflect.metadata API
│
└── 第四步:运行时应用装饰器
    ├── 修改类或方法的行为
    └── 附加元数据信息

2.3 TypeScript 配置

tsconfig.json

json
{
  "compilerOptions": {
    // 启用装饰器功能
    "experimentalDecorators": true,
    
    // 启用装饰器元数据
    "emitDecoratorMetadata": true,
    
    // 其他配置
    "target": "ES2021",
    "module": "commonjs",
    "strict": true
  }
}

配置说明

配置项作用必要性
experimentalDecorators启用装饰器功能必须
emitDecoratorMetadata生成装饰器元数据必须
target编译目标版本重要
module模块系统重要

为什么需要元数据?

code
元数据的作用:
│
├── 依赖注入
│   ├── 识别构造函数参数类型
│   ├── 自动注入依赖
│   └── 实现控制反转(IoC)
│
├── 路由映射
│   ├── 识别控制器路径
│   ├── 识别请求方法
│   └── 映射到具体处理函数
│
└── 参数解析
    ├── 识别参数类型
    ├── 自动提取请求参数
    └── 类型转换和验证

2.4 四种装饰器类型

1. 类装饰器(Class Decorator)

typescript
// 类装饰器
function Controller(path: string) {
  return function (target: any) {
    // target 就是类本身
    console.log(`Controller path: ${path}`);
    console.log(`Class name: ${target.name}`);
  };
}

@Controller('users')
class UserController {
  // ...
}

2. 方法装饰器(Method Decorator)

typescript
// 方法装饰器
function Get(path: string) {
  return function (
    target: any,           // 类的原型
    propertyKey: string,   // 方法名
    descriptor: PropertyDescriptor  // 属性描述符
  ) {
    console.log(`GET path: ${path}`);
    console.log(`Method name: ${propertyKey}`);
  };
}

class UserController {
  @Get('')
  findAll() {
    return 'All users';
  }
}

3. 属性装饰器(Property Decorator)

typescript
// 属性装饰器
function Inject(token: any) {
  return function (target: any, propertyKey: string) {
    console.log(`Inject ${token} into ${propertyKey}`);
  };
}

class UserController {
  @Inject('UserService')
  userService: any;
}

4. 参数装饰器(Parameter Decorator)

typescript
// 参数装饰器
function Body(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`Body parameter at index ${parameterIndex}`);
}

class UserController {
  create(@Body() userData: any) {
    return userData;
  }
}

三、NestJS 核心装饰器

3.1 控制器装饰器

@Controller() 装饰器源码

typescript
// node_modules/@nestjs/common/decorators/core/controller.decorator.ts

import { Path } from '@nestjs/common/interfaces';

export function Controller(): ClassDecorator;
export function Controller(prefix: string): ClassDecorator;
export function Controller(prefix: string[]): ClassDecorator;

export function Controller(prefix?: string | string[]): ClassDecorator {
  return (target: object) => {
    // 设置元数据
    Reflect.defineMetadata(CONTROLLER_WATERMARK, true, target);
    Reflect.defineMetadata(PATH_METADATA, prefix, target);
  };
}

使用示例

typescript
// 单个路径
@Controller('users')
export class UserController {}

// 多个路径
@Controller(['users', 'members'])
export class UserController {}

// 根路径
@Controller()
export class AppController {}

3.2 请求方法装饰器

@Get() 装饰器源码

typescript
// node_modules/@nestjs/common/decorators/http/request-mapping.decorator.ts

export function Get(path?: string | string[]): MethodDecorator {
  return RequestMapping({
    path,
    method: RequestMethod.GET,
  });
}

function RequestMapping(metadata: RequestMappingMetadata): MethodDecorator {
  return (target: any, key: string, descriptor: PropertyDescriptor) => {
    // 设置路径元数据
    Reflect.defineMetadata(PATH_METADATA, metadata.path, descriptor.value);
    // 设置方法元数据
    Reflect.defineMetadata(METHOD_METADATA, metadata.method, descriptor.value);
  };
}

常用请求方法装饰器

typescript
import { Controller, Get, Post, Put, Delete, Patch } from '@nestjs/common';

@Controller('users')
export class UserController {
  // GET /users
  @Get()
  findAll() {}

  // GET /users/:id
  @Get(':id')
  findOne(@Param('id') id: string) {}

  // POST /users
  @Post()
  create(@Body() userData: any) {}

  // PUT /users/:id
  @Put(':id')
  update(@Param('id') id: string, @Body() userData: any) {}

  // PATCH /users/:id
  @Patch(':id')
  partialUpdate(@Param('id') id: string, @Body() userData: any) {}

  // DELETE /users/:id
  @Delete(':id')
  remove(@Param('id') id: string) {}
}

四、参数装饰器详解

4.1 参数装饰器总览

必须记住的参数装饰器图

code
NestJS 参数装饰器速记图:
│
├── @Request() / @Req()     → 获取完整请求对象
│   └── 包含:params、query、body、headers 等
│
├── @Query()                → 获取查询参数(?name=value)
│   └── 示例:GET /users?name=Tom&age=20
│
├── @Param()                → 获取路径参数(/users/:id)
│   └── 示例:GET /users/123
│
├── @Body()                 → 获取请求体(POST/PUT)
│   └── 示例:POST /users { "name": "Tom" }
│
├── @Headers()              → 获取请求头
│   └── 示例:{ authorization: "Bearer xxx" }
│
├── @Ip()                   → 获取客户端 IP
│   └── 示例:192.168.1.1
│
└── @Session()              → 获取 Session 对象
    └── 示例:{ userId: 123 }

4.2 @Query - 查询参数

获取所有查询参数

typescript
import { Controller, Get, Query } from '@nestjs/common';

@Controller('users')
export class UserController {
  @Get('search')
  search(@Query() query: any) {
    console.log(query); // { name: 'Tom', age: '20' }
    return `Searching for ${query.name}`;
  }
}

请求示例

bash
GET /users/search?name=Tom&age=20

响应

json
{
  "message": "Searching for Tom"
}

获取单个查询参数

typescript
@Controller('users')
export class UserController {
  @Get('search')
  search(@Query('name') name: string) {
    console.log(name); // Tom
    return `Searching for ${name}`;
  }
}

4.3 @Param - 路径参数

获取所有路径参数

typescript
import { Controller, Get, Param } from '@nestjs/common';

@Controller('users')
export class UserController {
  @Get(':id')
  findOne(@Param() params: any) {
    console.log(params); // { id: '123' }
    return `User ID: ${params.id}`;
  }
}

请求示例

bash
GET /users/123

响应

json
{
  "message": "User ID: 123"
}

获取单个路径参数

typescript
@Controller('users')
export class UserController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    console.log(id); // 123
    return `User ID: ${id}`;
  }
}

多个路径参数

typescript
@Controller('users')
export class UserController {
  @Get(':userId/posts/:postId')
  findPost(
    @Param('userId') userId: string,
    @Param('postId') postId: string,
  ) {
    return `User ${userId}, Post ${postId}`;
  }
}

请求示例

bash
GET /users/123/posts/456

响应

json
{
  "message": "User 123, Post 456"
}

4.4 @Body - 请求体

获取完整请求体

typescript
import { Controller, Post, Body } from '@nestjs/common';

@Controller('users')
export class UserController {
  @Post()
  create(@Body() userData: any) {
    console.log(userData); // { name: 'Tom', email: 'tom@example.com' }
    return {
      message: 'User created',
      data: userData,
    };
  }
}

请求示例

bash
POST /users
Content-Type: application/json

{
  "name": "Tom",
  "email": "tom@example.com"
}

响应

json
{
  "message": "User created",
  "data": {
    "name": "Tom",
    "email": "tom@example.com"
  }
}

获取单个字段

typescript
@Controller('users')
export class UserController {
  @Post()
  create(@Body('name') name: string, @Body('email') email: string) {
    console.log(name);  // Tom
    console.log(email); // tom@example.com
    return `User ${name} created with email ${email}`;
  }
}

使用 DTO(推荐)

typescript
// user.dto.ts
export class CreateUserDto {
  name: string;
  email: string;
  age: number;
}

// user.controller.ts
import { CreateUserDto } from './user.dto';

@Controller('users')
export class UserController {
  @Post()
  create(@Body() userData: CreateUserDto) {
    console.log(userData);
    // 类型安全
    const { name, email, age } = userData;
    return { message: 'User created', data: userData };
  }
}

4.5 @Headers - 请求头

获取所有请求头

typescript
import { Controller, Get, Headers } from '@nestjs/common';

@Controller('users')
export class UserController {
  @Get('profile')
  getProfile(@Headers() headers: any) {
    console.log(headers);
    // {
    //   authorization: 'Bearer xxx',
    //   'content-type': 'application/json',
    //   'user-agent': 'Mozilla/5.0...'
    // }
    return 'Profile data';
  }
}

获取单个请求头

typescript
@Controller('users')
export class UserController {
  @Get('profile')
  getProfile(@Headers('authorization') token: string) {
    console.log(token); // Bearer xxx
    return 'Profile data';
  }
}

4.6 @Request - 完整请求对象

获取完整请求对象

typescript
import { Controller, Get, Request, Req } from '@nestjs/common';

@Controller('users')
export class UserController {
  // 使用 @Request() 或 @Req()
  @Get('info')
  getInfo(@Request() req: any) {
    console.log(req);
    // {
    //   method: 'GET',
    //   url: '/users/info',
    //   headers: { ... },
    //   query: { ... },
    //   params: { ... },
    //   body: { ... }
    // }
    
    return {
      method: req.method,
      url: req.url,
      query: req.query,
      params: req.params,
    };
  }
}

4.7 其他常用装饰器

@Ip() - 客户端 IP

typescript
import { Controller, Get, Ip } from '@nestjs/common';

@Controller('users')
export class UserController {
  @Get('ip')
  getClientIp(@Ip() ip: string) {
    console.log(ip); // 192.168.1.1
    return `Your IP: ${ip}`;
  }
}

@Session() - Session 对象

typescript
import { Controller, Get, Session } from '@nestjs/common';

@Controller('users')
export class UserController {
  @Get('profile')
  getProfile(@Session() session: any) {
    console.log(session); // { userId: 123 }
    return session;
  }
}

五、实战示例

5.1 完整的 CRUD 控制器

typescript
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Body,
  Param,
  Query,
  Headers,
} from '@nestjs/common';

@Controller('users')
export class UserController {
  // GET /users?page=1&limit=10
  @Get()
  findAll(
    @Query('page') page: number = 1,
    @Query('limit') limit: number = 10,
  ) {
    return {
      message: 'Get all users',
      page,
      limit,
    };
  }

  // GET /users/:id
  @Get(':id')
  findOne(@Param('id') id: string) {
    return {
      message: `Get user ${id}`,
      id,
    };
  }

  // POST /users
  @Post()
  create(
    @Body() userData: any,
    @Headers('authorization') token: string,
  ) {
    return {
      message: 'User created',
      data: userData,
      token,
    };
  }

  // PUT /users/:id
  @Put(':id')
  update(
    @Param('id') id: string,
    @Body() userData: any,
  ) {
    return {
      message: `User ${id} updated`,
      id,
      data: userData,
    };
  }

  // DELETE /users/:id
  @Delete(':id')
  remove(@Param('id') id: string) {
    return {
      message: `User ${id} deleted`,
      id,
    };
  }
}

5.2 组合使用示例

typescript
import {
  Controller,
  Get,
  Post,
  Body,
  Param,
  Query,
  Headers,
  Request,
} from '@nestjs/common';

@Controller('api')
export class ApiController {
  // 复杂查询
  @Get('search')
  search(
    @Query('keyword') keyword: string,
    @Query('page') page: number = 1,
    @Query('limit') limit: number = 10,
    @Headers('authorization') token: string,
  ) {
    return {
      message: 'Search results',
      keyword,
      page,
      limit,
      authenticated: !!token,
    };
  }

  // 嵌套资源
  @Get('users/:userId/posts/:postId')
  getUserPost(
    @Param('userId') userId: string,
    @Param('postId') postId: string,
  ) {
    return {
      message: 'User post',
      userId,
      postId,
    };
  }

  // 完整请求信息
  @Post('analyze')
  analyzeRequest(
    @Request() req: any,
    @Body() data: any,
  ) {
    return {
      method: req.method,
      url: req.url,
      headers: req.headers,
      query: req.query,
      params: req.params,
      body: data,
      ip: req.ip,
    };
  }
}

六、装饰器最佳实践

6.1 使用 DTO 保证类型安全

定义 DTO

typescript
// src/user/dto/create-user.dto.ts
export class CreateUserDto {
  name: string;
  email: string;
  age: number;
  password: string;
}

// src/user/dto/update-user.dto.ts
export class UpdateUserDto {
  name?: string;
  email?: string;
  age?: number;
}

在控制器中使用

typescript
import { CreateUserDto, UpdateUserDto } from './dto';

@Controller('users')
export class UserController {
  @Post()
  create(@Body() userData: CreateUserDto) {
    // 类型安全,自动提示
    const { name, email, age, password } = userData;
    return { message: 'User created', data: userData };
  }

  @Put(':id')
  update(
    @Param('id') id: string,
    @Body() userData: UpdateUserDto,
  ) {
    return { message: `User ${id} updated`, data: userData };
  }
}

6.2 参数验证

使用 class-validator

typescript
// src/user/dto/create-user.dto.ts
import { IsString, IsEmail, IsNumber, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(2)
  name: string;

  @IsEmail()
  email: string;

  @IsNumber()
  age: number;

  @IsString()
  @MinLength(6)
  password: string;
}

启用验证

typescript
// main.ts
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 启用全局验证管道
  app.useGlobalPipes(new ValidationPipe());
  
  await app.listen(3000);
}
bootstrap();

6.3 参数默认值

typescript
@Controller('users')
export class UserController {
  @Get()
  findAll(
    @Query('page') page: string = '1',
    @Query('limit') limit: string = '10',
  ) {
    return {
      page: parseInt(page),
      limit: parseInt(limit),
    };
  }
}

七、装饰器执行顺序

7.1 装饰器执行顺序

code
装饰器执行顺序(从外到内,从下到上):
│
├── 类装饰器
│   └── @Controller()
│
├── 方法装饰器
│   └── @Get()、@Post()、@Put()、@Delete()
│
└── 参数装饰器
    ├── @Body()、@Query()、@Param()
    └── 从左到右执行

示例

typescript
@Controller('users')        // 3. 最后执行
export class UserController {
  @Get(':id')               // 2. 第二执行
  findOne(
    @Param('id') id: string,  // 1. 先执行(从左到右)
  ) {
    return `User ${id}`;
  }
}

7.2 参数装饰器执行顺序

typescript
@Post()
create(
  @Body() body: any,          // 1. 第一个执行
  @Headers() headers: any,    // 2. 第二个执行
  @Ip() ip: string,           // 3. 第三个执行
) {
  return { body, headers, ip };
}

八、学习要点总结

8.1 核心概念速记

code
NestJS 工作原理与装饰器核心概念:
│
├── 工作流程
│   ├── 前端请求 → Controller → Service → ORM → Database
│   └── Database → ORM → Service → Controller → 前端响应
│
├── 装饰器本质
│   ├── 装饰器是一个函数
│   ├── 接收 target 参数(类、方法、属性、参数)
│   └── 通过元数据(Metadata)附加信息
│
├── TypeScript 配置
│   ├── experimentalDecorators: true(启用装饰器)
│   └── emitDecoratorMetadata: true(生成元数据)
│
└── 参数装饰器速记
    ├── @Query()  → 查询参数(?name=value)
    ├── @Param()  → 路径参数(/users/:id)
    ├── @Body()   → 请求体(POST/PUT)
    ├── @Headers() → 请求头
    └── @Request() → 完整请求对象

8.2 参数装饰器对比表

装饰器获取内容请求示例使用场景
@Query()查询参数GET /users?name=Tom过滤、分页
@Param()路径参数GET /users/:id资源标识
@Body()请求体POST /users { "name": "Tom" }创建/更新资源
@Headers()请求头Authorization: Bearer xxx认证、追踪
@Request()完整请求包含所有信息复杂场景
@Ip()客户端 IP192.168.1.1日志、限流
@Session()Session{ userId: 123 }会话管理

8.3 重点知识清单

知识点重要程度掌握程度
NestJS 请求流程未掌握 / 已掌握
装饰器原理未掌握 / 已掌握
TypeScript 配置未掌握 / 已掌握
@Query() 使用未掌握 / 已掌握
@Param() 使用未掌握 / 已掌握
@Body() 使用未掌握 / 已掌握
@Headers() 使用未掌握 / 已掌握
@Request() 使用未掌握 / 已掌握

8.4 课后思考题

  1. NestJS 的请求处理流程是什么?
  2. 什么是装饰器?它的本质是什么?
  3. 为什么需要启用 emitDecoratorMetadata
  4. @Query() 和 @Param() 有什么区别?
  5. 如何在控制器中获取完整的请求信息?

参考资料


上一章24-NestJS开发工具扩展

下一章26-NestJS管道与数据验证