NestJS API文档

学习目标

  • 掌握NestJS Swagger模块的使用方法
  • 理解OpenAPI规范的基本概念
  • 学习如何使用API装饰器注释API端点
  • 了解如何配置和自定义Swagger文档
  • 掌握API版本控制的实现方法

核心知识点

1. Swagger简介

Swagger(现在称为OpenAPI)是一种用于描述和文档化RESTful API的规范。它允许我们:

  • 自动生成API文档
  • 提供交互式API测试界面
  • 定义API的请求和响应格式
  • 描述API的认证方式
  • 支持API版本控制

在NestJS中,Swagger集成通过@nestjs/swagger包提供。

2. 安装和配置

首先,我们需要安装Swagger模块:

npm install --save @nestjs/swagger swagger-ui-express

然后,在应用的主文件中配置Swagger:

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('NestJS API')
    .setDescription('The NestJS API description')
    .setVersion('1.0')
    .addTag('nestjs')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  await app.listen(3000);
}
bootstrap();

3. 基本使用

3.1 为控制器添加装饰器

// src/app.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody } from '@nestjs/swagger';
import { AppService } from './app.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { Cat } from './interfaces/cat.interface';

@ApiTags('cats')
@Controller('cats')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  @ApiOperation({ summary: '获取所有猫' })
  @ApiResponse({ status: 200, description: '成功获取所有猫' })
  getCats(): Cat[] {
    return this.appService.getCats();
  }

  @Get(':id')
  @ApiOperation({ summary: '根据ID获取猫' })
  @ApiParam({ name: 'id', description: '猫的ID' })
  @ApiResponse({ status: 200, description: '成功获取猫' })
  @ApiResponse({ status: 404, description: '猫不存在' })
  getCat(@Param('id') id: string): Cat {
    return this.appService.getCat(id);
  }

  @Post()
  @ApiOperation({ summary: '创建猫' })
  @ApiBody({ type: CreateCatDto })
  @ApiResponse({ status: 201, description: '成功创建猫' })
  @ApiResponse({ status: 400, description: '请求数据无效' })
  createCat(@Body() createCatDto: CreateCatDto): Cat {
    return this.appService.createCat(createCatDto);
  }
}

3.2 为数据传输对象(DTO)添加装饰器

// src/dto/create-cat.dto.ts
import { ApiProperty } from '@nestjs/swagger';

export class CreateCatDto {
  @ApiProperty({ description: '猫的名字', example: 'Tom' })
  name: string;

  @ApiProperty({ description: '猫的年龄', example: 3 })
  age: number;

  @ApiProperty({ description: '猫的品种', example: 'Persian' })
  breed: string;
}

3.3 为接口添加装饰器

// src/interfaces/cat.interface.ts
import { ApiProperty } from '@nestjs/swagger';

export class Cat {
  @ApiProperty({ description: '猫的ID', example: '1' })
  id: string;

  @ApiProperty({ description: '猫的名字', example: 'Tom' })
  name: string;

  @ApiProperty({ description: '猫的年龄', example: 3 })
  age: number;

  @ApiProperty({ description: '猫的品种', example: 'Persian' })
  breed: string;
}

4. Swagger配置选项

NestJS的Swagger模块提供了丰富的配置选项,我们可以通过DocumentBuilder类来配置:

  • setTitle():设置文档标题
  • setDescription():设置文档描述
  • setVersion():设置API版本
  • addTag():添加API标签
  • addBearerAuth():添加Bearer认证
  • addApiKey():添加API密钥认证
  • addBasicAuth():添加基本认证
  • addOAuth2():添加OAuth2认证
  • setTermsOfService():设置服务条款链接
  • setContact():设置联系信息
  • setLicense():设置许可证信息

5. API装饰器

NestJS的Swagger模块提供了以下常用的API装饰器:

5.1 控制器装饰器

  • @ApiTags():为控制器添加标签
  • @ApiBearerAuth():为控制器添加Bearer认证
  • @ApiBasicAuth():为控制器添加基本认证
  • @ApiOAuth2():为控制器添加OAuth2认证

5.2 方法装饰器

  • @ApiOperation():描述API操作
  • @ApiResponse():描述API响应
  • @ApiParam():描述API路径参数
  • @ApiQuery():描述API查询参数
  • @ApiBody():描述API请求体
  • @ApiHeader():描述API请求头
  • @ApiExcludeEndpoint():排除API端点

5.3 属性装饰器

  • @ApiProperty():描述模型属性
  • @ApiPropertyOptional():描述可选的模型属性
  • @ApiHideProperty():隐藏模型属性

6. 高级配置

6.1 自定义Swagger UI

我们可以通过传递选项对象来自定义Swagger UI:

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('NestJS API')
    .setDescription('The NestJS API description')
    .setVersion('1.0')
    .addTag('nestjs')
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document, {
    swaggerOptions: {
      filter: true,
      showRequestDuration: true,
      defaultModelsExpandDepth: -1,
    },
  });

  await app.listen(3000);
}
bootstrap();

6.2 多个Swagger文档

我们可以为不同的API版本或模块创建多个Swagger文档:

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // v1 API文档
  const configV1 = new DocumentBuilder()
    .setTitle('NestJS API v1')
    .setDescription('The NestJS API v1 description')
    .setVersion('1.0')
    .addTag('v1')
    .build();
  const documentV1 = SwaggerModule.createDocument(app, configV1, {
    include: [AppModule],
  });
  SwaggerModule.setup('api/v1', app, documentV1);

  // v2 API文档
  const configV2 = new DocumentBuilder()
    .setTitle('NestJS API v2')
    .setDescription('The NestJS API v2 description')
    .setVersion('2.0')
    .addTag('v2')
    .build();
  const documentV2 = SwaggerModule.createDocument(app, configV2, {
    include: [AppModule],
  });
  SwaggerModule.setup('api/v2', app, documentV2);

  await app.listen(3000);
}
bootstrap();

7. API版本控制

7.1 路径版本控制

我们可以通过在路由路径中添加版本前缀来实现API版本控制:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { CatsV1Controller } from './cats-v1.controller';
import { CatsV2Controller } from './cats-v2.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsV1Controller, CatsV2Controller],
  providers: [CatsService],
})
export class AppModule {}
// src/cats-v1.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { CatsService } from './cats.service';
import { CatV1 } from './interfaces/cat-v1.interface';

@ApiTags('cats v1')
@Controller('v1/cats')
export class CatsV1Controller {
  constructor(private readonly catsService: CatsService) {}

  @Get()
getCats(): CatV1[] {
    return this.catsService.getCatsV1();
  }
}
// src/cats-v2.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { CatsService } from './cats.service';
import { CatV2 } from './interfaces/cat-v2.interface';

@ApiTags('cats v2')
@Controller('v2/cats')
export class CatsV2Controller {
  constructor(private readonly catsService: CatsService) {}

  @Get()
getCats(): CatV2[] {
    return this.catsService.getCatsV2();
  }
}

7.2 头部版本控制

我们可以通过自定义中间件来实现基于头部的API版本控制:

// src/common/middleware/version.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class VersionMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const version = req.headers['x-api-version'] || '1';
    req.url = `/v${version}${req.url}`;
    next();
  }
}

然后在应用模块中使用:

// src/app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { CatsV1Controller } from './cats-v1.controller';
import { CatsV2Controller } from './cats-v2.controller';
import { CatsService } from './cats.service';
import { VersionMiddleware } from './common/middleware/version.middleware';

@Module({
  controllers: [CatsV1Controller, CatsV2Controller],
  providers: [CatsService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(VersionMiddleware)
      .forRoutes('cats');
  }
}

实用案例分析

案例1:完整的API文档系统

需求分析

我们需要实现一个完整的API文档系统,包括:

  • 为所有API端点生成文档
  • 支持Bearer认证
  • 提供交互式API测试界面
  • 实现API版本控制
  • 自定义Swagger UI配置

实现方案

  1. 安装所需依赖
npm install --save @nestjs/swagger swagger-ui-express @nestjs/jwt passport-jwt
  1. 创建认证模块
// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { UsersModule } from '../users/users.module';

@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: process.env.JWT_SECRET || 'secretKey',
      signOptions: { expiresIn: '60s' },
    }),
  ],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService],
})
export class AuthModule {}
  1. 创建认证服务
// src/auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
  ) {}

  async validateUser(username: string, pass: string): Promise<any> {
    const user = await this.usersService.findOneByUsername(username);
    if (user && user.password === pass) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

  async login(user: any) {
    const payload = { username: user.username, sub: user.userId };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}
  1. 创建JWT策略
// src/auth/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET || 'secretKey',
    });
  }

  async validate(payload: any) {
    const user = await this.authService.validateUser(payload.username, '');
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}
  1. 创建用户模块
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}
  1. 创建用户服务
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { User } from './interfaces/user.interface';

@Injectable()
export class UsersService {
  private readonly users: User[] = [
    { userId: 1, username: 'john', password: 'changeme' },
    { userId: 2, username: 'chris', password: 'secret' },
    { userId: 3, username: 'maria', password: 'guess' },
  ];

  async findOneByUsername(username: string): Promise<User | undefined> {
    return this.users.find(user => user.username === username);
  }

  async findAll(): Promise<User[]> {
    return this.users;
  }
}
  1. 创建用户控制器
// src/users/users.controller.ts
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { UsersService } from './users.service';
import { User } from './interfaces/user.interface';
import { LoginDto } from './dto/login.dto';
import { AuthService } from '../auth/auth.service';

@ApiTags('users')
@Controller('users')
export class UsersController {
  constructor(
    private readonly usersService: UsersService,
    private readonly authService: AuthService,
  ) {}

  @Post('login')
  @ApiOperation({ summary: '用户登录' })
  @ApiResponse({ status: 200, description: '登录成功,返回token' })
  @ApiResponse({ status: 401, description: '用户名或密码错误' })
  async login(@Body() loginDto: LoginDto) {
    const user = await this.usersService.findOneByUsername(loginDto.username);
    if (!user || user.password !== loginDto.password) {
      throw new UnauthorizedException('用户名或密码错误');
    }
    return this.authService.login(user);
  }

  @Get()
  @UseGuards(AuthGuard('jwt'))
  @ApiOperation({ summary: '获取所有用户' })
  @ApiBearerAuth()
  @ApiResponse({ status: 200, description: '成功获取所有用户' })
  @ApiResponse({ status: 401, description: '未授权' })
  async findAll(): Promise<User[]> {
    return this.usersService.findAll();
  }
}
  1. 创建数据传输对象
// src/users/dto/login.dto.ts
import { ApiProperty } from '@nestjs/swagger';

export class LoginDto {
  @ApiProperty({ description: '用户名', example: 'john' })
  username: string;

  @ApiProperty({ description: '密码', example: 'changeme' })
  password: string;
}
  1. 创建用户接口
// src/users/interfaces/user.interface.ts
import { ApiProperty } from '@nestjs/swagger';

export class User {
  @ApiProperty({ description: '用户ID', example: 1 })
  userId: number;

  @ApiProperty({ description: '用户名', example: 'john' })
  username: string;

  @ApiProperty({ description: '密码', example: 'changeme' })
  password: string;
}
  1. 配置Swagger
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('NestJS API')
    .setDescription('The NestJS API description')
    .setVersion('1.0')
    .addTag('users')
    .addBearerAuth({
      type: 'http',
      scheme: 'bearer',
      bearerFormat: 'JWT',
      name: 'JWT',
description: 'Enter JWT token',
      in: 'header',
    })
    .build();
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document, {
    swaggerOptions: {
      filter: true,
      showRequestDuration: true,
      persistAuthorization: true,
    },
  });

  await app.listen(3000);
}
bootstrap();
  1. 创建应用模块
// src/app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { AuthModule } from './auth/auth.module';

@Module({
  imports: [UsersModule, AuthModule],
})
export class AppModule {}

常见问题与解决方案

1. Swagger文档不显示

可能原因

  • Swagger模块未正确配置
  • API装饰器使用错误
  • 路径冲突

解决方案

  • 检查Swagger模块配置是否正确
  • 确保所有API端点都添加了适当的装饰器
  • 检查Swagger UI路径是否与其他路由冲突

2. 认证不工作

可能原因

  • 认证装饰器使用错误
  • JWT配置错误
  • Swagger UI认证设置错误

解决方案

  • 确保在控制器或方法上添加了@ApiBearerAuth()装饰器
  • 检查JWT配置是否正确
  • 在Swagger UI中正确设置认证令牌

3. 模型定义不显示

可能原因

  • 模型类未添加@ApiProperty()装饰器
  • 模型类未被Swagger模块扫描到
  • 类型引用错误

解决方案

  • 确保所有模型属性都添加了@ApiProperty()装饰器
  • 确保模型类被正确导入和使用
  • 检查类型引用是否正确

4. API版本控制冲突

可能原因

  • 路由路径冲突
  • 中间件配置错误
  • Swagger文档配置错误

解决方案

  • 确保不同版本的API使用不同的路由路径
  • 检查中间件配置是否正确
  • 为不同版本的API创建单独的Swagger文档

最佳实践

  1. 一致的命名规范:使用一致的命名规范为API端点、模型和属性命名
  2. 详细的文档:为所有API端点添加详细的描述和示例
  3. 认证集成:正确集成认证机制到Swagger文档中
  4. 版本控制:为API实现合理的版本控制策略
  5. 错误处理:为所有API端点添加适当的错误响应描述
  6. 模型验证:使用DTO和验证装饰器确保API数据的有效性
  7. 自定义UI:根据需要自定义Swagger UI配置
  8. 测试集成:使用Swagger UI测试API端点

代码优化建议

  1. 使用配置服务:将Swagger配置放到配置服务中,便于管理
  2. 创建装饰器工厂:创建自定义装饰器工厂简化重复的装饰器使用
  3. 使用全局前缀:为API添加全局前缀,便于路由管理
  4. 实现文档版本控制:为不同版本的API创建不同的Swagger文档
  5. 添加API标签:使用标签组织API端点,提高文档可读性

总结

NestJS的Swagger模块提供了一种简洁、高效的方式来创建和管理API文档。通过本文的学习,你应该已经掌握了:

  • 如何安装和配置Swagger模块
  • 如何使用API装饰器注释API端点和模型
  • 如何配置和自定义Swagger文档
  • 如何实现API版本控制
  • 如何集成认证机制到Swagger文档中

API文档是现代API开发的重要组成部分,它可以帮助开发者和使用者更好地理解和使用API。合理使用NestJS的Swagger集成,可以大大提高API的可维护性和可用性。

互动问答

  1. 以下哪个是NestJS Swagger模块的正确安装命令?
    A. npm install --save @nestjs/swagger
    B. npm install --save swagger
    C. npm install --save @nestjs/swagger swagger-ui-express
    D. npm install --save openapi

  2. 如何为API端点添加描述?

  3. 如何为模型属性添加示例值?

  4. 如何在Swagger文档中集成Bearer认证?

  5. 如何实现API版本控制?

« 上一篇 NestJS健康检查 下一篇 » NestJS CORS配置