Skip to content
第 169 / 250 章Node⏱ 10 分钟阅读

第 169 章:控制器(Controller)详解

学习目标

  • 掌握控制器装饰器
  • 学会各种参数装饰器
  • 理解请求/响应对象
  • 学会子路由和分组

一、控制器基础

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

@Controller('users')   // 路由前缀
export class UserController {
  @Get()              // GET /users
  findAll() {}

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

  @Post()             // POST /users
  create() {}

  @Put(':id')         // PUT /users/:id
  update() {}

  @Patch(':id')       // PATCH /users/:id
  partialUpdate() {}

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

二、参数装饰器

2.1 路由参数

typescript
@Get(':id')
findOne(@Param('id') id: string) {
  return { id };
}

// 多个参数
@Get(':category/:id')
find(@Param() params: { category: string; id: string }) {
  return params;
}

2.2 查询参数

typescript
@Get()
findAll(
  @Query('page') page = 1,
  @Query('size') size = 10,
  @Query('sort') sort?: string,
) {
  return { page, size, sort };
}

2.3 请求体

typescript
interface CreateUserDto {
  name: string;
  email: string;
}

@Post()
create(@Body() body: CreateUserDto) {
  return body;
}

// 部分字段
@Post()
create(@Body('name') name: string) {
  return { name };
}

2.4 Headers

typescript
@Get()
findAll(@Headers('authorization') auth: string) {
  return { auth };
}

2.5 Cookies

typescript
@Get()
findAll(@Cookies('sessionId') sessionId: string) {
  return { sessionId };
}

三、请求与响应对象

typescript
import { Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';

@Get()
findAll(@Req() req: Request, @Res() res: Response) {
  res.status(200).json({ code: 200, data: [] });
}

四、状态码与 Headers

typescript
import { HttpCode, Header, HttpStatus } from '@nestjs/common';

@Post()
@HttpCode(HttpStatus.CREATED)   // 201
create() {}

@Get()
@Header('Cache-Control', 'no-store')
findAll() {}

// 动态
@Post()
create(@Res({ passthrough: true }) res: Response) {
  res.status(201);
  return { ok: true };
}

五、子路由

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

  @Get(':id')
  findOne() {}

  // 嵌套子资源
  @Get(':id/posts')
  findPosts(@Param('id') userId: string) {}
}

六、动态路由匹配

typescript
// 数字 ID
@Get(':id(\\d+)')
findById(@Param('id') id: number) {}   // 只匹配数字

// 邮箱格式
@Get(':email([^\\s@]+@[^\\s@]+\\.[^\\s@]+)')
findByEmail(@Param('email') email: string) {}

七、异步控制器

typescript
@Get()
async findAll() {
  const users = await this.userService.findAll();
  return users;
}

// 完整异步
@Get()
async findAll(@Res({ passthrough: true }) res: Response) {
  const users = await this.userService.findAll();
  res.header('X-Total-Count', String(users.length));
  return users;
}

八、完整 CRUD 示例

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

interface User { id: number; name: string; email: string; }
interface CreateUserDto { name: string; email: string; }
interface UpdateUserDto { name?: string; email?: string; }

@Controller('api/users')
export class UserController {
  private users: User[] = [];
  private nextId = 1;

  @Get()
  findAll() {
    return { code: 200, data: this.users };
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    const user = this.users.find(u => u.id === id);
    if (!user) throw new NotFoundException(`User ${id} not found`);
    return { code: 200, data: user };
  }

  @Post()
  create(@Body() dto: CreateUserDto) {
    const user: User = { id: this.nextId++, ...dto };
    this.users.push(user);
    return { code: 201, data: user };
  }

  @Put(':id')
  update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateUserDto) {
    const user = this.users.find(u => u.id === id);
    if (!user) throw new NotFoundException();
    Object.assign(user, dto);
    return { code: 200, data: user };
  }

  @Delete(':id')
  remove(@Param('id', ParseIntPipe) id: number) {
    const idx = this.users.findIndex(u => u.id === id);
    if (idx < 0) throw new NotFoundException();
    this.users.splice(idx, 1);
    return { code: 204 };
}

九、ParseIntPipe(自动转换)

typescript
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
  // id 自动转为 number,非数字直接 400
}

内置管道:

  • ValidationPipe
  • ParseIntPipe
  • ParseFloatPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • ParseEnumPipe
  • DefaultValuePipe

十、本章小结

装饰器作用
@Controller(path)控制器
@Get / @Post / @Put / @DeleteHTTP 方法
@Param(key)路径参数
@Query(key)查询参数
@Body()请求体
@Headers()请求头
@Req() @Res()原始对象
@HttpCode()状态码
@Header()响应头

动手练习

  1. 实现完整 CRUD 的 users 控制器
  2. 用 ParseIntPipe 转换 ID
  3. 给所有接口加统一返回格式
  4. 写一个带 query 分页的列表接口

推荐阅读


下一章:第 170 章:模块(Module)与依赖注入

本站基于 VitePress 构建 · 由 Codebook 团队维护