从 0 到 1 搭建 NestJS API 文档:Swagger 配置 + DTO 校验 + 测试全流程,新手直接抄作业

0 阅读10分钟

前言:为什么写这篇?

最近在转全栈,学习 nest 在做 ai-knowledge 个人知识库AI助手项目时,用 NestJS 搭完接口后,不知道怎么测试输出接口文档,试了 Swagger 后直接解放双手 —— 自动生成文档 + 在线测试,不用再手动写接口说明,Postman 都省了!这篇就把实战中踩过的坑、总结的技巧全分享给大家,新手也能直接抄作业~


目录

  1. Swagger 是什么?一句话搞懂
  1. 安装与基础配置|3 步搞定
  1. 核心装饰器|按场景
  1. 完整实战示例|从 DTO Controller
  1. Swagger UI 测试指南|小白也会用
  1. 踩坑记录|5 个高频问题 + 解决方案
  1. 附:500 错误排查万能技

1. Swagger 是什么?一句话搞懂

Swagger(也叫 OpenAPI)是 API 文档自动生成工具 + 在线测试平台,配置好后:

  • 直接在页面填参数、点按钮测试,不用 Postman/curl,前后端联调效率翻倍
  • 自动同步代码变更,改接口后文档实时更新,再也不用手动维护!

2. 安装与基础配置|3 步搞定

2.1 先装依赖

用 pnpm/npm/yarn 都行,我习惯用 pnpm:

pnpm add @nestjs/swagger

2.2 配置 main.ts(核心步骤)

在项目入口文件加 Swagger 配置,复制粘贴改改标题就行:

// main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // Swagger 核心配置
  const swaggerConfig = new DocumentBuilder()
    .setTitle('AI Knowledge API')           // 文档标题(自定义)
    .setDescription('AI 知识库系统接口文档|前端鱼姐实战版') // 描述
    .setVersion('1.0')                       // 接口版本
    .addTag('document')                      // 接口分组标签(后续用)
    .addTag('knowledge-base')                // 可以加多个分组
    .build();
  // 创建文档+挂载到 /api 路径
  const document = SwaggerModule.createDocument(app, swaggerConfig);
  SwaggerModule.setup('api', app, document); // 访问地址:http://localhost:3000/api
  await app.listen(3000);
}

2.3 验证是否成功

启动项目 pnpm start:dev,浏览器打开 http://localhost:3000/api,能看到 Swagger UI 页面就搞定啦~


3. 核心装饰器|按场景用就对了

Swagger 靠装饰器生成文档,分 Controller 装饰器(描述接口)和 DTO 装饰器(描述参数),记熟这几个就够日常用!

3.1 Controller 装饰器(接口层面)

给 Controller 类和接口方法加注解,控制文档的分组、说明、参数类型:

import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
@ApiTags('document')  // 分组标签,Swagger 里按这个分类显示
@Controller('document-parse')
export class DocumentController {
  // POST 接口:创建文档
  @Post()
  @ApiOperation({ summary: '创建文档' })           // 接口简短说明
  @ApiResponse({ status: 201, description: '创建成功~' }) // 成功响应
  @ApiResponse({ status: 400, description: '参数填错啦!' }) // 错误响应
  create(@Body() dto: CreateDocumentDto) {
    return this.service.create(dto);
  }
  // GET 接口:通过 ID 查文档
  @Get(':id')
  @ApiOperation({ summary: '获取单个文档' })
  @ApiParam({ name: 'id', description: '文档唯一ID' })  // 路径参数(:id)
  findOne(@Param('id') id: string) {
    return this.service.findOne(id);
  }
  // GET 接口:搜索文档
  @Get()
  @ApiOperation({ summary: '搜索文档' })
  @ApiQuery({ name: 'keyword', required: false, description: '搜索关键词' }) // 查询参数(?keyword=xxx)
  search(@Query('keyword') keyword: string) {
    return this.service.search(keyword);
  }
}

3.2 DTO 装饰器(参数层面)

DTO 是接口的参数模板,既要用 class-validator 做数据校验,也要用 Swagger 装饰器做文档说明 ——两个都要写,少一个就踩坑!

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsString, IsOptional, IsNotEmpty } from 'class-validator';
export class CreateDocumentDto {
  // 必选字段:文件名
  @ApiProperty({ 
    description: '文件名(含后缀)', 
    example: 'readme.pdf'  // Swagger 页面会预填这个示例,超贴心~
  })
  @IsString()      // 校验:必须是字符串
  @IsNotEmpty()    // 校验:不能为空
  fileName: string;
  // 可选字段:备注
  @ApiPropertyOptional({ description: '文档备注(可选)' }) // 可选字段用这个装饰器
  @IsString()
  @IsOptional()    // 配合 class-validator 的可选校验
  remark?: string;
}

3.3 装饰器速查表(收藏即用)

装饰器用在哪作用
@ApiTags('xxx')Controller 类接口分组(Swagger 里按组显示)
@ApiOperation({ summary })接口方法接口简短说明
@ApiResponse({ status, description })接口方法响应码说明(成功 / 失败都要写)
@ApiParam({ name, description })接口方法路径参数(:id 这类)说明
@ApiQuery({ name, required })接口方法查询参数(?key=val 这类)说明
@ApiProperty({...})DTO 属性必选参数的文档说明
@ApiPropertyOptional({...})DTO 属性可选参数的文档说明

4. 完整实战示例|从 DTO 到 Controller

以 document 模块为例,完整走一遍配置流程,直接抄代码就能用~

4.1 第一步:写 DTO(参数模板)

// src/document/dto/create-document.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
export class CreateDocumentDto {
  @ApiProperty({ description: '文件名', example: '技术文档.pdf' })
  @IsString()
  @IsNotEmpty()
  fileName: string;
  @ApiProperty({ description: '文件类型(如 pdf/docx)', example: 'pdf' })
  @IsString()
  @IsNotEmpty()
  fileType: string;
  @ApiProperty({ description: '文件大小(单位:KB)', example: '2048' })
  @IsString()
  @IsNotEmpty()
  fileSize: string;
}

4.2 第二步:写 Controller(接口 + 文档)

// src/document/document.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { DocumentService } from './document.service';
import { CreateDocumentDto } from './dto/create-document.dto';
@ApiTags('document') // 分组:document
@Controller('document-parse') // 接口前缀:/document-parse
export class DocumentController {
  constructor(private readonly documentService: DocumentService) {}
  @Post()
  @ApiOperation({ summary: '创建文档' }) // 接口说明
  @ApiResponse({ status: 201, description: '文档创建成功!' })
  @ApiResponse({ status: 400, description: '参数错误,请检查字段格式~' })
  create(@Body() createDocumentDto: CreateDocumentDto) {
    return this.documentService.create(createDocumentDto);
  }
}

4.3 第三步:确认 main.ts 配置(已在第二步配置过)

启动项目后,访问 http://localhost:3000/api,就能看到 document 分组下的接口啦~


5. Swagger UI 测试指南|小白也会用

文档生成后,直接在页面测试接口,不用开 Postman,步骤超简单:

5.1 测试步骤(图文拆解)

image.png

  1. 打开 http://localhost:3000/api,找到要测试的接口(按 Tag 分组查找)
  1. 点击接口展开,再点击右上角的 Try it out 按钮(进入测试模式)
  1. 填写参数:如果是 POST 接口,会自动显示 DTO 定义的字段和示例值,直接改就行;路径参数 / 查询参数直接填输入框
  1. 点击 Execute 按钮(发送请求)
  1. 查看结果:下方会显示响应体、状态码、响应头,成功失败一目了然~

5.2 额外福利:复制 curl 命令

测试成功后,Swagger 会自动生成对应的 curl 命令,复制到终端就能用,不用自己手写!示例:

curl -X 'POST' \
  'http://localhost:3000/document-parse' \
  -H 'Content-Type: application/json' \
  -d '{
  "fileName": "技术文档.pdf",
  "fileType": "pdf",
  "fileSize": "2048"
}'

5.3 两个实用地址(收藏!)


6. 踩坑记录|5 个高频问题 + 解决方案

这部分是重点!都是我在 ai-knowledge 项目中实际踩过的坑,花了好几个小时排查,大家直接避坑~

坑 1:数据库连接失败 ——SQLite 驱动找不到

报错信息

DriverPackageNotInstalledError: SQLite package has not been found installed. Please run "npm install sqlite3".

原因:项目用了 pnpm monorepo,sqlite3 包被 pnpm 的严格依赖隔离机制挡住了(pnpm 不允许包访问未声明的依赖)

解决方案:把数据库驱动改成 better-sqlite3(项目已安装,兼容性更好)

// src/database/database.module.ts
TypeOrmModule.forRoot({
  type: 'better-sqlite3',  // 原来是 'sqlite',改成这个
  database: ':memory:',    // 内存数据库(按需修改)
  entities: [__dirname + '/../**/*.entity{.ts,.js}'],
  synchronize: true,
})

坑 2:better-sqlite3 原生二进制未编译

报错信息

Error: Could not locate the bindings file. Tried: .../better-sqlite3/build/better_sqlite3.node

原因:better-sqlite3 是 C++ 原生模块,安装时需要编译,pnpm 可能跳过了编译步骤

解决方案:手动触发编译

# 进入 better-sqlite3 包目录
cd node_modules/.pnpm/better-sqlite3@12.8.0/node_modules/better-sqlite3
# 执行编译命令
npx prebuild-install

坑 3:timestamp 类型不兼容 better-sqlite3

报错信息

DataTypeNotSupportedError: Data type "timestamp" in "DocumentParse.createdAt" is not supported by "better-sqlite3" database.

原因:SQLite 没有原生的 timestamp 类型,better-sqlite3 驱动校验更严格

解决方案:Entity 中把 timestamp 改成 datetime

// src/document/entities/document-parse.entity.ts
@CreateDateColumn({ type: 'datetime' }) // 原来是 'timestamp',改成 'datetime'
createdAt: Date;
@UpdateDateColumn({ type: 'datetime' })
updatedAt: Date;

坑 4:ConfigService 找不到

报错信息

Nest could not find ConfigService element (this provider does not exist in the current context)

原因:main.ts 中用了 app.get(ConfigService),但 AppModule 没导入 ConfigModule

解决方案:在 app.module.ts 中导入 ConfigModule

// src/app.module.ts
import { ConfigModule } from '@nestjs/config';
@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: '.env', // 加载 .env 文件(按需配置)
    }),
    // 其他模块...
  ],
})
export class AppModule {}

坑 5:POST 请求 500——whitelist 过滤掉所有字段(最隐蔽!)

报错信息

{"statusCode": 500, "message": "Internal server error"}

底层原因(终端日志可见):

SqliteError: NOT NULL constraint failed: document_parse.fileName

问题分析

main.ts 中配置了全局校验管道,whitelist: true 会过滤掉没有 class-validator 装饰器的字段 —— 如果 DTO 中只写了 @ApiProperty(Swagger 装饰器),没写 @IsString 这类校验装饰器,参数会被全部过滤,请求体变成空对象 {}, 数据库 NOT NULL 字段报错。

解决方案:DTO 中每个字段必须同时加 Swagger 装饰器 + class-validator 装饰器

// 正确写法
@ApiProperty({ description: '文件名' }) // Swagger 文档用
@IsString() // class-validator 校验用(防止被过滤)
@IsNotEmpty()
fileName: string;

记忆口诀:@ApiProperty 管文档,@IsString 管校验,两个都要有,缺一个就凉~


附:500 错误排查万能技巧

Nest 默认会隐藏 500 错误的详细信息,排查时按以下步骤来,效率翻倍:

方法 1:看终端日志

启动项目的终端会打印完整的错误堆栈,大部分问题看日志就能定位。

方法 2:临时加 try-catch

在 Controller/Service 中加 try-catch,手动返回错误信息(仅开发环境用,上线前删掉):

@Post()
async create(@Body() dto: CreateDocumentDto) {
  try {
    return await this.service.create(dto);
  } catch (error) {
    // 临时返回错误详情
    return { 
      message: '接口报错啦~', 
      error: error.message, 
      stack: error.stack 
    };
  }
}

方法 3:关闭开发环境错误隐藏

在 main.ts 中配置全局异常过滤器,开发环境返回完整错误(生产环境禁用!):

import { HttpException, HttpStatus } from '@nestjs/common';
app.useGlobalFilters({
  catch(exception, host) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception instanceof HttpException 
      ? exception.getStatus() 
      : HttpStatus.INTERNAL_SERVER_ERROR;
    
    // 开发环境返回详细错误
    if (process.env.NODE_ENV === 'development') {
      response.status(status).json({
        statusCode: status,
        message: exception.message,
        stack: exception.stack,
      });
    } else {
      // 生产环境返回通用提示
      response.status(status).json({
        statusCode: status,
        message: '服务器内部错误',
      });
    }
  },
});

最后总结

Swagger 真的是 NestJS 开发的必备工具,配置一次受益全程 —— 自动生成文档、在线测试、同步接口变更,还能减少前后端沟通成本~ 这篇教程的配置和避坑技巧都是实战总结,大家直接抄代码,有问题评论区见呀~

该项目开源github,需要源码的自取即可。

如果觉得有用,记得点赞收藏,下次用 Swagger 时直接翻出来!❤️