Skip to content
On this page

NestJS控制器

控制器负责处理传入的请求并将响应返回给客户端。控制器定义了应用程序的路由,这些路由告诉应用程序如何响应客户端的请求(例如,通过特定的URL路径或HTTP方法调用)。

控制器基础

控制器定义

使用@Controller()装饰器定义控制器:

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

@Controller('cats') // 定义路由前缀
export class CatsController {
  @Get() // GET /cats
  findAll(): string {
    return 'This action returns all cats';
  }

  @Get(':id') // GET /cats/:id
  findOne(@Param('id') id: string): string {
    return `This action returns a #${id} cat`;
  }

  @Post() // POST /cats
  create(@Body() createCatDto: any): string {
    return 'This action adds a new cat';
  }

  @Put(':id') // PUT /cats/:id
  update(@Param('id') id: string, @Body() updateCatDto: any): string {
    return `This action updates a #${id} cat`;
  }

  @Delete(':id') // DELETE /cats/:id
  remove(@Param('id') id: string): string {
    return `This action removes a #${id} cat`;
  }
}

控制器元数据

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

@Controller('cats')
export class CatsController {
  @Get()
  @Header('Cache-Control', 'none') // 设置响应头
  @HttpCode(HttpStatus.OK) // 设置HTTP状态码
  findAll(): string {
    return 'This action returns all cats';
  }

  @Get('docs')
  @Redirect('https://docs.nestjs.com', 302) // 重定向
  getDocs(@Query('version') version: string) {
    if (version && version === '5') {
      return { url: 'https://docs.nestjs.com/v5/' }; // 动态重定向
    }
  }
}

路由处理

HTTP方法装饰器

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

@Controller('users')
export class UsersController {
  @Get()     // GET请求
  findAll(): string {
    return 'Get all users';
  }

  @Post()    // POST请求
  create(): string {
    return 'Create user';
  }

  @Put(':id') // PUT请求
  update(@Param('id') id: string): string {
    return `Update user ${id}`;
  }

  @Delete(':id') // DELETE请求
  remove(@Param('id') id: string): string {
    return `Delete user ${id}`;
  }

  @Patch(':id') // PATCH请求
  partialUpdate(@Param('id') id: string): string {
    return `Partially update user ${id}`;
  }

  @Head(':id') // HEAD请求
  head(@Param('id') id: string): void {
    // 只返回头部信息
  }

  @Options(':id') // OPTIONS请求
  options(@Param('id') id: string): string {
    return `Options for user ${id}`;
  }

  @All(':id') // 所有HTTP方法
  handleAll(@Param('id') id: string): string {
    return `Handle all methods for user ${id}`;
  }
}

路由参数和查询参数

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

@Controller('users')
export class UsersController {
  // 路由参数
  @Get(':id')
  findOne(
    @Param('id') id: string,
    @Headers('authorization') auth: string,
    @Ip() ip: string,
  ): string {
    return `User ID: ${id}, Auth: ${auth}, IP: ${ip}`;
  }

  // 多个路由参数
  @Get(':userId/posts/:postId')
  getPost(
    @Param('userId') userId: string,
    @Param('postId') postId: string,
  ): string {
    return `User ${userId}, Post ${postId}`;
  }

  // 解构路由参数
  @Get(':id')
  getWithDestructuring(@Param() params: any): string {
    return `ID: ${params.id}`;
  }

  // 查询参数
  @Get()
  findAll(
    @Query('page') page: number = 1,
    @Query('limit') limit: number = 10,
    @Query() queries: any, // 所有查询参数
  ): string {
    return `Page: ${page}, Limit: ${limit}, All: ${JSON.stringify(queries)}`;
  }

  // 带默认值的查询参数
  @Get('search')
  search(
    @Query('q') query: string,
    @Query('sort', new ParseIntPipe({ optional: true })) sort = 1,
  ): string {
    return `Search: ${query}, Sort: ${sort}`;
  }
}

请求处理

请求体处理

typescript
import { Controller, Post, Put, Body, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { UpdateCatDto } from './dto/update-cat.dto';

@Controller('cats')
export class CatsController {
  // 简单请求体
  @Post()
  create(@Body() body: any): string {
    return `Create cat with data: ${JSON.stringify(body)}`;
  }

  // 使用DTO
  @Post()
  createWithDto(@Body() createCatDto: CreateCatDto): string {
    return `Create cat: ${createCatDto.name}, ${createCatDto.age}`;
  }

  // 使用验证管道
  @Post()
  @UsePipes(new ValidationPipe())
  createWithValidation(@Body() createCatDto: CreateCatDto): string {
    return `Create cat with validation: ${createCatDto.name}`;
  }

  // 部分更新
  @Put(':id')
  update(
    @Param('id') id: string,
    @Body() updateCatDto: UpdateCatDto,
  ): string {
    return `Update cat ${id} with: ${JSON.stringify(updateCatDto)}`;
  }

  // 处理嵌套对象
  @Post('complex')
  createComplex(@Body('user') user: any, @Body('settings') settings: any): string {
    return `User: ${user.name}, Settings: ${settings.theme}`;
  }
}

文件上传

typescript
import { 
  Controller, 
  Post, 
  UseInterceptors, 
  UploadedFile, 
  UploadedFiles 
} from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';

@Controller('upload')
export class UploadController {
  @Post('single')
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(@UploadedFile() file: Express.Multer.File) {
    return {
      filename: file.filename,
      originalName: file.originalname,
      size: file.size,
    };
  }

  @Post('multiple')
  @UseInterceptors(FilesInterceptor('files', 10)) // 最多10个文件
  uploadMultipleFiles(@UploadedFiles() files: Array<Express.Multer.File>) {
    return {
      fileCount: files.length,
      files: files.map(file => ({
        filename: file.filename,
        originalName: file.originalname,
        size: file.size,
      })),
    };
  }
}

响应处理

响应格式

typescript
import { 
  Controller, 
  Get, 
  Post, 
  Put, 
  Delete, 
  Res, 
  HttpStatus,
  Render,
  Header,
  StreamableFile
} from '@nestjs/common';
import { Response } from 'express';
import { createReadStream } from 'fs';
import { join } from 'path';

@Controller('cats')
export class CatsController {
  // JSON响应
  @Get(':id')
  findOne(@Param('id') id: string): any {
    return {
      id,
      name: 'Kitty',
      age: 3,
    };
  }

  // 使用原生响应对象
  @Get('stream/:id')
  stream(@Param('id') id: string, @Res() res: Response) {
    res.status(HttpStatus.OK).json({
      id,
      name: 'Kitty',
    });
  }

  // 模板渲染
  @Get(':id')
  @Render('cat') // 渲染模板
  getCat(@Param('id') id: string) {
    return { id, name: 'Kitty' };
  }

  // 设置响应头
  @Get('headers')
  @Header('Content-Type', 'application/json')
  @Header('Custom-Header', 'value')
  withHeaders(): any {
    return { message: 'Headers set' };
  }

  // 流式文件响应
  @Get('file')
  getFile(): StreamableFile {
    const file = createReadStream(join(process.cwd(), 'files/document.pdf'));
    return new StreamableFile(file);
  }

  // 自定义响应
  @Get('custom')
  customResponse(@Res({ passthrough: true }) res: Response) {
    res.status(HttpStatus.OK);
    return { message: 'Custom response' };
  }
}

控制器装饰器

方法级装饰器

typescript
import { 
  Controller, 
  Get, 
  UseGuards, 
  UseInterceptors, 
  UseFilters,
  UsePipes,
  SerializeOptions
} from '@nestjs/common';
import { RolesGuard } from '../guards/roles.guard';
import { LoggingInterceptor } from '../interceptors/logging.interceptor';
import { HttpExceptionFilter } from '../filters/http-exception.filter';
import { ValidationPipe } from '../pipes/validation.pipe';

@Controller('admin')
@UseGuards(RolesGuard) // 类级别守卫
@UseInterceptors(LoggingInterceptor) // 类级别拦截器
export class AdminController {
  @Get('users')
  @UseGuards(RolesGuard) // 方法级别守卫
  @UsePipes(ValidationPipe) // 方法级别管道
  @UseFilters(HttpExceptionFilter) // 方法级别过滤器
  getUsers(): string {
    return 'Admin users';
  }

  @Get('settings')
  @SerializeOptions({ strategy: 'excludeAll' }) // 序列化选项
  getSettings(): any {
    return { secret: 'secret', public: 'public' };
  }
}

路由通配符

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

@Controller('files')
export class FilesController {
  // 路径模式匹配
  @Get('ab*cd') // 匹配 abcd, ab_cd, abecd 等
  wildCard(): string {
    return 'Pattern matched';
  }

  @Get('changelog') // 精确匹配
  changelog(): string {
    return 'Changelog';
  }

  @Get('file/*') // 匹配 /file/ 后的任何路径
  catchAll(): string {
    return 'Catch all';
  }

  @All('*') // 捕获所有未匹配的路由
  notFound(): string {
    return 'Not found';
  }
}

中间件和控制器

控制器级中间件

typescript
import { 
  Module, 
  NestModule, 
  MiddlewareConsumer, 
  RequestMethod 
} from '@nestjs/common';
import { LoggerMiddleware } from './middleware/logger.middleware';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class CatsModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(
        { path: 'cats', method: RequestMethod.ALL },
        { path: 'cats/:id', method: RequestMethod.GET },
      );
  }
}

路由分组

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

@Controller('api/v1/users')
export class UsersV1Controller {
  @Get()       // GET /api/v1/users
  findAll(): string {
    return 'Get all users (v1)';
  }

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

  @Post()      // POST /api/v1/users
  create(): string {
    return 'Create user (v1)';
  }

  @Put(':id')  // PUT /api/v1/users/:id
  update(@Param('id') id: string): string {
    return `Update user ${id} (v1)`;
  }

  @Delete(':id') // DELETE /api/v1/users/:id
  remove(@Param('id') id: string): string {
    return `Remove user ${id} (v1)`;
  }
}

控制器最佳实践

1. 控制器职责单一

typescript
// 好的控制器设计 - 职责单一
@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Get()
  findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string): Promise<Cat> {
    return this.catsService.findOne(id);
  }

  @Post()
  create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
    return this.catsService.create(createCatDto);
  }
}

// 避免控制器过于复杂
@Controller('complex')
export class ComplexController {
  // 避免在一个控制器中处理过多逻辑
}

2. 使用DTO进行数据验证

typescript
// dto/create-cat.dto.ts
import { IsString, IsInt, Min, Max } from 'class-validator';

export class CreateCatDto {
  @IsString()
  name: string;

  @IsInt()
  @Min(0)
  @Max(30)
  age: number;

  @IsString()
  breed: string;
}

// 控制器中使用
@Controller('cats')
export class CatsController {
  @Post()
  @UsePipes(new ValidationPipe())
  create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
    return this.catsService.create(createCatDto);
  }
}

3. 合理使用装饰器

typescript
@Controller('users')
export class UsersController {
  @Get()
  @HttpCode(HttpStatus.OK)
  @Header('Cache-Control', 'max-age=3600')
  @UseInterceptors(CacheInterceptor) // 使用缓存
  findAll(): Promise<User[]> {
    return this.usersService.findAll();
  }

  @Get(':id')
  @UseGuards(AuthGuard, RolesGuard) // 多个守卫
  findOne(@Param('id') id: string): Promise<User> {
    return this.usersService.findOne(id);
  }
}

4. 错误处理

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

@Controller('cats')
export class CatsController {
  @Get(':id')
  async findOne(@Param('id') id: string) {
    try {
      const cat = await this.catsService.findOne(id);
      if (!cat) {
        throw new HttpException('Cat not found', HttpStatus.NOT_FOUND);
      }
      return cat;
    } catch (error) {
      if (error instanceof HttpException) {
        throw error;
      }
      throw new HttpException(
        'Internal server error',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }
}

通过合理使用控制器,可以构建清晰、可维护的API端点。