主题
统一响应结构
字段约定
NestJS 和 HTTP 没有强制要求统一响应结构。code、message、data 是项目约定,不是框架标准;
采用统一结构后,普通 JSON API 的成功和错误响应都必须遵循同一个 ApiResponse<T> 结构:
ts
export interface ApiResponse<T = unknown> {
code: number
message: string
data?: T
}| 字段 | 类型 | 说明 |
|---|---|---|
code | number | 稳定的业务状态码,0 表示成功,非零值表示失败。 |
message | string | 面向用户或开发者的结果说明,不作为客户端分支判断条件。 |
data | T | 可选;成功响应存在数据时返回。 |
目录结构
bash
src/
├── common/
│ ├── interfaces/
│ │ └── api-response.interface.ts # code、message、data 响应结构
│ └── constants/
│ └── common-response.constant.ts # 通用成功和错误 code、message
└── modules/
└── users/
└── constants/
└── user-response.constant.ts # 用户业务 code 和 message业务码分配
| 范围 | 归属 |
|---|---|
0 | 成功 |
10000–19999 | 通用错误 |
20000–99999 | 业务错误 |
20000–99999 内的模块划分由具体项目决定。HTTP 状态码表达协议层结果,业务码区分具体业务结果;业务码发布后不修改含义,也不重新分配。
响应定义管理
code 和默认 message 不单独维护,而是组成完整响应定义。通用响应定义放在 common-response.constant.ts,业务响应定义放在对应模块的 *-response.constant.ts:
ts
export const CommonResponse = {
SUCCESS: { code: 0, message: '请求成功' },
CREATED: { code: 0, message: '创建成功' },
UPDATED: { code: 0, message: '更新成功' },
DELETED: { code: 0, message: '删除成功' },
VALIDATION_FAILED: { code: 10001, message: '请求参数校验失败' },
REQUEST_FAILED: { code: 10002, message: '请求失败' },
INTERNAL_SERVER_ERROR: { code: 10003, message: '服务器内部错误' },
} as constts
export const UserResponse = {
USER_NOT_FOUND: { code: 20001, message: '用户不存在' },
EMAIL_ALREADY_EXISTS: { code: 20002, message: '邮箱已被注册' },
} as const客户端根据 code 判断结果,不解析 message。参数校验失败时使用第一条具体消息,默认消息仅作为兜底;未知异常只返回通用消息,不暴露堆栈、SQL 或第三方原始错误。
响应示例
请求成功时返回对应的 2xx HTTP 状态码:
json
{
"code": 0,
"message": "请求成功",
"data": {
"id": 1,
"name": "Vfan"
}
}成功但没有响应数据时省略 data:
json
{
"code": 0,
"message": "请求成功"
}业务资源不存在时返回 404 Not Found:
json
{
"code": 20001,
"message": "用户不存在"
}输入校验失败时返回 400 Bad Request,message 使用第一条具体错误:
json
{
"code": 10001,
"message": "email must be an email"
}客户端根据 HTTP 状态码识别错误类别,根据 code 区分具体业务结果;不要解析可能调整或国际化的 message。错误定义应集中管理,避免在代码中散落业务码和消息字符串。
统一成功响应
使用全局 Interceptor 包装 Controller 返回的数据:
ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import type { Observable } from 'rxjs'
import { map } from 'rxjs'
import { CommonResponse } from '../constants/common-response.constant'
import type { ApiResponse } from '../interfaces/api-response.interface'
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
intercept(_context: ExecutionContext, next: CallHandler<T>): Observable<ApiResponse<T>> {
return next.handle().pipe(
map((data) => ({
...CommonResponse.SUCCESS,
...(data === undefined ? {} : { data }),
})),
)
}
}Interceptor 只转换成功结果,不要捕获异常后返回伪成功响应。
统一错误响应
ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common'
import type { Response } from 'express'
import { CommonResponse } from '../constants/common-response.constant'
import type { ApiResponse } from '../interfaces/api-response.interface'
@Catch()
export class HttpErrorFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<Response>()
const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR
const exceptionResponse = exception instanceof HttpException ? exception.getResponse() : null
const body: Record<string, unknown> =
typeof exceptionResponse === 'object' && exceptionResponse !== null
? (exceptionResponse as Record<string, unknown>)
: {}
const rawMessage = typeof exceptionResponse === 'string' ? exceptionResponse : body.message
const isValidationError = Array.isArray(rawMessage)
const firstValidationMessage = isValidationError
? rawMessage.find((message): message is string => typeof message === 'string')
: undefined
const isDefinedError = status < 500 && typeof body.code === 'number' && typeof body.message === 'string'
const fallbackError = isValidationError
? CommonResponse.VALIDATION_FAILED
: status >= 500
? CommonResponse.INTERNAL_SERVER_ERROR
: CommonResponse.REQUEST_FAILED
const result: ApiResponse<unknown> = {
code: isDefinedError ? (body.code as number) : fallbackError.code,
message: isValidationError
? (firstValidationMessage ?? fallbackError.message)
: isDefinedError
? (body.message as string)
: fallbackError.message,
}
response.status(status).json(result)
}
}业务主动抛出异常
ts
import { NotFoundException } from '@nestjs/common'
import { UserResponse } from './constants/user-response.constant'
throw new NotFoundException(UserResponse.USER_NOT_FOUND)全局注册
使用 APP_INTERCEPTOR 和 APP_FILTER 注册后,实例由依赖注入容器管理:
ts
import { Module } from '@nestjs/common'
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core'
import { HttpErrorFilter } from './common/filters/http-error.filter'
import { TransformInterceptor } from './common/interceptors/transform.interceptor'
@Module({
providers: [
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
{ provide: APP_FILTER, useClass: HttpErrorFilter },
],
})
export class AppModule {}