面向已经了解 JavaScript/TypeScript 基础、希望系统掌握 NestJS 的开发者。本文从框架思想、核心概念、工程实践、测试部署到进阶架构逐步展开,并用清晰示例和流程图串起真实开发思路。
目录
- [1. NestJS 是什么](#1. NestJS 是什么 "#1-nestjs-%E6%98%AF%E4%BB%80%E4%B9%88")
- [2. 环境准备与第一个项目](#2. 环境准备与第一个项目 "#2-%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87%E4%B8%8E%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%A1%B9%E7%9B%AE")
- [3. NestJS 的核心心智模型](#3. NestJS 的核心心智模型 "#3-nestjs-%E7%9A%84%E6%A0%B8%E5%BF%83%E5%BF%83%E6%99%BA%E6%A8%A1%E5%9E%8B")
- [4. Controller:接收请求](#4. Controller:接收请求 "#4-controller%E6%8E%A5%E6%94%B6%E8%AF%B7%E6%B1%82")
- [5. Provider 与依赖注入](#5. Provider 与依赖注入 "#5-provider-%E4%B8%8E%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5")
- [6. Module:组织业务边界](#6. Module:组织业务边界 "#6-module%E7%BB%84%E7%BB%87%E4%B8%9A%E5%8A%A1%E8%BE%B9%E7%95%8C")
- [7. DTO、Pipe 与参数校验](#7. DTO、Pipe 与参数校验 "#7-dtopipe-%E4%B8%8E%E5%8F%82%E6%95%B0%E6%A0%A1%E9%AA%8C")
- [8. Exception Filter:统一异常处理](#8. Exception Filter:统一异常处理 "#8-exception-filter%E7%BB%9F%E4%B8%80%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86")
- [9. Middleware、Guard、Interceptor 的区别](#9. Middleware、Guard、Interceptor 的区别 "#9-middlewareguardinterceptor-%E7%9A%84%E5%8C%BA%E5%88%AB")
- [10. 配置管理](#10. 配置管理 "#10-%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86")
- [11. 数据库访问](#11. 数据库访问 "#11-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BF%E9%97%AE")
- [12. 认证与授权](#12. 认证与授权 "#12-%E8%AE%A4%E8%AF%81%E4%B8%8E%E6%8E%88%E6%9D%83")
- [13. 文件上传](#13. 文件上传 "#13-%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0")
- [14. Swagger 接口文档](#14. Swagger 接口文档 "#14-swagger-%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3")
- [15. 测试体系](#15. 测试体系 "#15-%E6%B5%8B%E8%AF%95%E4%BD%93%E7%B3%BB")
- [16. 日志、监控与可观测性](#16. 日志、监控与可观测性 "#16-%E6%97%A5%E5%BF%97%E7%9B%91%E6%8E%A7%E4%B8%8E%E5%8F%AF%E8%A7%82%E6%B5%8B%E6%80%A7")
- [17. 缓存、任务调度与队列](#17. 缓存、任务调度与队列 "#17-%E7%BC%93%E5%AD%98%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E4%B8%8E%E9%98%9F%E5%88%97")
- [18. 微服务与消息通信](#18. 微服务与消息通信 "#18-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%8E%E6%B6%88%E6%81%AF%E9%80%9A%E4%BF%A1")
- [19. 项目分层与代码组织](#19. 项目分层与代码组织 "#19-%E9%A1%B9%E7%9B%AE%E5%88%86%E5%B1%82%E4%B8%8E%E4%BB%A3%E7%A0%81%E7%BB%84%E7%BB%87")
- [20. 性能、安全与生产部署](#20. 性能、安全与生产部署 "#20-%E6%80%A7%E8%83%BD%E5%AE%89%E5%85%A8%E4%B8%8E%E7%94%9F%E4%BA%A7%E9%83%A8%E7%BD%B2")
- [21. 从入门到精通的学习路线](#21. 从入门到精通的学习路线 "#21-%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E7%B2%BE%E9%80%9A%E7%9A%84%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF")
- [22. 常见问题与排查清单](#22. 常见问题与排查清单 "#22-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E4%B8%8E%E6%8E%92%E6%9F%A5%E6%B8%85%E5%8D%95")
1. NestJS 是什么
NestJS 是一个基于 Node.js 的服务端框架,默认使用 TypeScript,底层可以运行在 Express 或 Fastify 之上。它借鉴了 Angular 的模块化、依赖注入、装饰器等思想,让后端应用在规模变大后仍然保持清晰结构。
如果用一句话理解:
NestJS 是一个帮助你用工程化方式写 Node.js 后端应用的框架。
1.1 为什么不用原生 Express
Express 很灵活,但项目变大后常见问题是:
- 路由、业务逻辑、数据库操作混在一起。
- 依赖关系靠手动传参,难以测试。
- 缺少统一的模块边界。
- 异常、日志、鉴权、校验容易散落在业务代码里。
- 团队成员风格不统一,项目越写越乱。
NestJS 不是要替代 Express 的底层能力,而是在 Express/Fastify 之上提供一套强约束的应用架构。
1.2 NestJS 适合什么场景
NestJS 特别适合:
- 中大型 Node.js 后端项目。
- BFF、网关、管理后台服务。
- REST API、GraphQL API。
- 微服务、消息队列消费者。
- 需要长期维护、多人协作的 TypeScript 项目。
如果只是几十行脚本或非常简单的接口,直接使用 Express/Koa 也可以。但一旦涉及模块拆分、权限、数据库、测试、部署,NestJS 的收益就很明显。
1.3 NestJS 架构鸟瞰
核心请求链路是:
text
Request -> Middleware -> Guard -> Interceptor -> Pipe -> Controller -> Service -> Database -> Response
理解这条链路,NestJS 的大部分概念就能串起来。
2. 环境准备与第一个项目
2.1 安装 Node.js
建议使用 Node.js LTS 版本。可以先检查版本:
bash
node -v
npm -v
推荐 Node.js 18+ 或 20+。
2.2 安装 Nest CLI
bash
npm i -g @nestjs/cli
nest --version
Nest CLI 可以快速创建项目、生成模块、控制器、服务等文件。
2.3 创建项目
bash
nest new nest-demo
cd nest-demo
npm run start:dev
启动后访问:
text
http://localhost:3000
默认会返回:
text
Hello World!
2.4 默认目录结构
text
src/
├── app.controller.ts
├── app.controller.spec.ts
├── app.module.ts
├── app.service.ts
└── main.ts
核心文件说明:
main.ts:应用入口,负责创建 Nest 应用并启动监听端口。app.module.ts:根模块,组织应用依赖。app.controller.ts:路由入口,处理 HTTP 请求。app.service.ts:业务服务,被 Controller 调用。*.spec.ts:测试文件。
2.5 启动流程图
2.6 main.ts 示例
ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
NestFactory.create(AppModule) 会从根模块开始递归解析所有模块、控制器和服务。
3. NestJS 的核心心智模型
NestJS 里最重要的三个角色:
- Controller:处理请求和响应。
- Provider/Service:承载业务逻辑。
- Module:组织 Controller 和 Provider。
3.1 三者关系
一个标准业务模块通常长这样:
text
users/
├── dto/
│ ├── create-user.dto.ts
│ └── update-user.dto.ts
├── entities/
│ └── user.entity.ts
├── users.controller.ts
├── users.service.ts
├── users.repository.ts
└── users.module.ts
3.2 一个简单用户模块
生成模块:
bash
nest g module users
nest g controller users
nest g service users
生成后:
text
src/users/
├── users.controller.spec.ts
├── users.controller.ts
├── users.module.ts
├── users.service.spec.ts
└── users.service.ts
3.3 请求如何进入业务代码
核心原则:
- Controller 尽量薄,只做协议层处理。
- Service 放业务规则。
- Repository/ORM 负责数据持久化。
- Module 定义边界和依赖关系。
4. Controller:接收请求
Controller 是 HTTP 路由入口,用装饰器声明请求路径、方法和参数来源。
4.1 基础写法
ts
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
@Post()
create(@Body() body: { name: string; email: string }) {
return this.usersService.create(body);
}
}
访问路径:
GET /users->findAll()GET /users/1->findOne('1')POST /users->create(body)
4.2 常用参数装饰器
| 装饰器 | 来源 | 示例 |
|---|---|---|
@Param() |
路径参数 | /users/:id |
@Query() |
查询参数 | /users?page=1 |
@Body() |
请求体 | POST JSON body |
@Headers() |
请求头 | authorization |
@Req() |
原始 request | Express request |
@Res() |
原始 response | Express response |
推荐优先使用 @Param、@Query、@Body 等声明式装饰器,少用 @Req() 和 @Res(),否则会让代码和底层平台强绑定。
4.3 Query 参数示例
ts
import { Controller, Get, Query } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
findAll(
@Query('page') page = '1',
@Query('pageSize') pageSize = '20',
) {
return {
page: Number(page),
pageSize: Number(pageSize),
};
}
}
真实项目里不建议在 Controller 手动转换复杂参数,应该交给 DTO 和 Pipe。
4.4 RESTful 路由设计
text
GET /users 查询用户列表
GET /users/:id 查询单个用户
POST /users 创建用户
PATCH /users/:id 部分更新用户
PUT /users/:id 全量替换用户
DELETE /users/:id 删除用户
对应代码:
ts
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
} from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
findAll() {}
@Get(':id')
findOne(@Param('id') id: string) {}
@Post()
create(@Body() dto: CreateUserDto) {}
@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateUserDto) {}
@Delete(':id')
remove(@Param('id') id: string) {}
}
5. Provider 与依赖注入
Provider 是 NestJS 里可被依赖注入管理的对象,最常见的是 Service。
5.1 Service 基础示例
ts
import { Injectable, NotFoundException } from '@nestjs/common';
interface User {
id: string;
name: string;
email: string;
}
@Injectable()
export class UsersService {
private readonly users: User[] = [];
findAll() {
return this.users;
}
findOne(id: string) {
const user = this.users.find((item) => item.id === id);
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
}
create(input: Omit<User, 'id'>) {
const user = {
id: String(Date.now()),
...input,
};
this.users.push(user);
return user;
}
}
@Injectable() 表示这个类可以被 Nest 容器管理。
5.2 依赖注入是什么
依赖注入的意思是:
类不自己创建依赖,而是声明自己需要什么,由框架负责创建和传入。
不推荐:
ts
export class UsersController {
private readonly usersService = new UsersService();
}
推荐:
ts
export class UsersController {
constructor(private readonly usersService: UsersService) {}
}
好处:
- 依赖关系清晰。
- 方便替换实现。
- 方便单元测试 Mock。
- 生命周期由框架统一管理。
5.3 Provider 注册与解析流程
5.4 自定义 Provider
有时你不想直接注入类,而是注入一个配置、常量或接口实现。
ts
export const PAYMENT_CLIENT = Symbol('PAYMENT_CLIENT');
export interface PaymentClient {
pay(amount: number): Promise<string>;
}
注册:
ts
import { Module } from '@nestjs/common';
@Module({
providers: [
{
provide: PAYMENT_CLIENT,
useValue: {
async pay(amount: number) {
return `paid:${amount}`;
},
},
},
],
exports: [PAYMENT_CLIENT],
})
export class PaymentModule {}
注入:
ts
import { Inject, Injectable } from '@nestjs/common';
@Injectable()
export class OrdersService {
constructor(
@Inject(PAYMENT_CLIENT)
private readonly paymentClient: PaymentClient,
) {}
async payOrder(orderId: string, amount: number) {
const paymentId = await this.paymentClient.pay(amount);
return { orderId, paymentId };
}
}
5.5 Provider 的几种注册方式
ts
@Module({
providers: [
UsersService,
{ provide: 'APP_NAME', useValue: 'nest-demo' },
{ provide: 'USER_REPOSITORY', useClass: UserRepository },
{
provide: 'CONFIG',
useFactory: () => ({
timeout: 3000,
}),
},
],
})
export class AppModule {}
常见形式:
useClass:用某个类作为实现。useValue:用固定值。useFactory:用工厂函数动态创建。useExisting:复用已有 Provider。
6. Module:组织业务边界
Module 是 NestJS 组织代码的基本单元。
6.1 Module 基础写法
ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
6.2 imports、providers、controllers、exports
ts
@Module({
imports: [DatabaseModule],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
含义:
imports:导入其他模块暴露出来的能力。controllers:当前模块的 HTTP 路由入口。providers:当前模块内部的服务。exports:把当前模块的服务暴露给其他模块。
6.3 模块依赖示意图
模块设计的关键是边界清晰:
- 用户相关逻辑放
UsersModule。 - 订单相关逻辑放
OrdersModule。 - 支付相关逻辑放
PaymentModule。 - 共享配置放
ConfigModule。
6.4 Global Module
某些模块可以声明为全局模块,例如配置模块、日志模块。
ts
import { Global, Module } from '@nestjs/common';
@Global()
@Module({
providers: [LoggerService],
exports: [LoggerService],
})
export class LoggerModule {}
全局模块被导入一次后,其他模块可以直接注入它导出的 Provider。
注意:不要滥用全局模块。业务模块不应该轻易变成全局模块,否则依赖关系会变得隐式。
6.5 动态模块
动态模块用于根据配置创建模块,例如数据库连接、第三方 SDK。
ts
import { DynamicModule, Module } from '@nestjs/common';
interface SmsModuleOptions {
accessKey: string;
secretKey: string;
}
@Module({})
export class SmsModule {
static forRoot(options: SmsModuleOptions): DynamicModule {
return {
module: SmsModule,
providers: [
{
provide: 'SMS_OPTIONS',
useValue: options,
},
SmsService,
],
exports: [SmsService],
};
}
}
使用:
ts
@Module({
imports: [
SmsModule.forRoot({
accessKey: process.env.SMS_ACCESS_KEY!,
secretKey: process.env.SMS_SECRET_KEY!,
}),
],
})
export class AppModule {}
7. DTO、Pipe 与参数校验
DTO 是 Data Transfer Object,用来描述请求或响应的数据结构。
Pipe 负责参数转换和校验。
7.1 安装校验依赖
bash
npm i class-validator class-transformer
7.2 创建 DTO
ts
import { IsEmail, IsString, Length } from 'class-validator';
export class CreateUserDto {
@IsString()
@Length(2, 20)
name: string;
@IsEmail()
email: string;
@IsString()
@Length(6, 32)
password: string;
}
7.3 Controller 使用 DTO
ts
import { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
@Post()
create(@Body() dto: CreateUserDto) {
return dto;
}
}
只定义 DTO 还不会自动校验,需要开启全局 Pipe。
7.4 开启全局 ValidationPipe
ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
await app.listen(3000);
}
bootstrap();
配置含义:
whitelist: true:移除 DTO 中未声明的字段。forbidNonWhitelisted: true:传入未声明字段时报错。transform: true:自动把参数转换成 DTO 对应类型。
7.5 校验流程图
7.6 Query DTO 示例
ts
import { Type } from 'class-transformer';
import { IsInt, IsOptional, Max, Min } from 'class-validator';
export class QueryUserDto {
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page = 1;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
pageSize = 20;
}
使用:
ts
@Get()
findAll(@Query() query: QueryUserDto) {
return this.usersService.findAll(query);
}
7.7 自定义 Pipe
例如校验 ID 必须是正整数:
ts
import {
BadRequestException,
Injectable,
PipeTransform,
} from '@nestjs/common';
@Injectable()
export class PositiveIntPipe implements PipeTransform<string, number> {
transform(value: string) {
const result = Number(value);
if (!Number.isInteger(result) || result <= 0) {
throw new BadRequestException(`${value} is not a positive integer`);
}
return result;
}
}
使用:
ts
@Get(':id')
findOne(@Param('id', PositiveIntPipe) id: number) {
return this.usersService.findOne(id);
}
8. Exception Filter:统一异常处理
NestJS 内置了很多 HTTP 异常类,例如:
BadRequestExceptionUnauthorizedExceptionForbiddenExceptionNotFoundExceptionConflictExceptionInternalServerErrorException
8.1 抛出业务异常
ts
import { ConflictException, Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
async create(dto: CreateUserDto) {
const exists = await this.findByEmail(dto.email);
if (exists) {
throw new ConflictException('Email already exists');
}
return this.save(dto);
}
}
8.2 默认异常响应
json
{
"statusCode": 409,
"message": "Email already exists",
"error": "Conflict"
}
8.3 自定义全局异常过滤器
ts
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();
const isHttpException = exception instanceof HttpException;
const status = isHttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message = isHttpException
? exception.message
: 'Internal server error';
response.status(status).json({
code: status,
message,
path: request.url,
timestamp: new Date().toISOString(),
});
}
}
注册:
ts
app.useGlobalFilters(new GlobalExceptionFilter());
8.4 异常处理流程
建议:
- 业务错误用明确异常类表达。
- 不要空
catch。 - 日志中避免输出密码、Token、身份证等敏感信息。
- 对外错误信息不要泄露内部实现细节。
9. Middleware、Guard、Interceptor 的区别
这三个概念容易混淆。先看执行顺序:
9.1 Middleware
Middleware 最接近 Express 中间件,适合处理:
- 请求日志。
- 请求 ID。
- CORS 前置处理。
- 原始 request/response 层面的逻辑。
ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const startedAt = Date.now();
res.on('finish', () => {
const duration = Date.now() - startedAt;
console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
});
next();
}
}
注册:
ts
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(RequestLoggerMiddleware).forRoutes('*');
}
}
9.2 Guard
Guard 用来决定请求能不能继续执行,最典型场景是认证与授权。
ts
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization;
if (!token) {
throw new UnauthorizedException('Missing authorization header');
}
return true;
}
}
使用:
ts
@UseGuards(AuthGuard)
@Get('profile')
getProfile() {
return { id: 1, name: 'Tom' };
}
9.3 Interceptor
Interceptor 可以包裹 Controller 方法执行前后,适合:
- 响应格式统一包装。
- 接口耗时统计。
- 缓存。
- 序列化响应。
- 对返回 Observable 做转换。
ts
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable, map } from 'rxjs';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
return next.handle().pipe(
map((data) => ({
code: 0,
message: 'success',
data,
})),
);
}
}
注册:
ts
app.useGlobalInterceptors(new ResponseInterceptor());
9.4 怎么选择
| 场景 | 推荐 |
|---|---|
| 请求进入最早阶段处理 | Middleware |
| 判断是否允许访问接口 | Guard |
| 请求前后统一处理返回值 | Interceptor |
| 参数校验和转换 | Pipe |
| 异常统一格式化 | Exception Filter |
10. 配置管理
生产项目不要在代码中硬编码环境变量、密钥和服务地址。
10.1 安装配置模块
bash
npm i @nestjs/config
10.2 使用 ConfigModule
ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env.local', '.env'],
}),
],
})
export class AppModule {}
.env:
env
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/demo
JWT_SECRET=please-change-me
10.3 注入 ConfigService
ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AppConfigService {
constructor(private readonly configService: ConfigService) {}
get port() {
return this.configService.get<number>('PORT', 3000);
}
get jwtSecret() {
return this.configService.getOrThrow<string>('JWT_SECRET');
}
}
10.4 配置校验
推荐启动时校验必要环境变量,避免应用启动后才暴露配置错误。
bash
npm i joi
ts
import * as Joi from 'joi';
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required(),
JWT_SECRET: Joi.string().required(),
}),
});
10.5 配置加载流程
11. 数据库访问
NestJS 不绑定特定 ORM,常用选择有:
- TypeORM:装饰器风格,和 Nest 官方集成成熟。
- Prisma:类型安全强,迁移体验好。
- Sequelize:传统 ORM。
- Knex:SQL Builder。
- 原生数据库驱动:适合极致控制。
下面分别给出 TypeORM 和 Prisma 示例。
11.1 使用 TypeORM
安装:
bash
npm i @nestjs/typeorm typeorm pg
注册:
ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users/entities/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
url: process.env.DATABASE_URL,
entities: [User],
synchronize: false,
}),
],
})
export class AppModule {}
生产环境不要开启 synchronize: true,应使用迁移脚本管理表结构。
实体:
ts
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 64 })
name: string;
@Column({ unique: true })
email: string;
@Column()
passwordHash: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
模块注册实体:
ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
Service 注入 Repository:
ts
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>,
) {}
async findAll() {
return this.usersRepository.find({
order: { createdAt: 'DESC' },
});
}
async findOne(id: string) {
const user = await this.usersRepository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
}
async create(dto: CreateUserDto) {
const exists = await this.usersRepository.exists({
where: { email: dto.email },
});
if (exists) {
throw new ConflictException('Email already exists');
}
const user = this.usersRepository.create({
name: dto.name,
email: dto.email,
passwordHash: dto.password,
});
return this.usersRepository.save(user);
}
}
这里为了示例简化了密码处理,真实项目必须使用 bcrypt、argon2 等方式存储哈希后的密码,不能保存明文密码。
11.2 使用 Prisma
安装:
bash
npm i prisma @prisma/client
npx prisma init
prisma/schema.prisma:
prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(uuid())
name String
email String @unique
passwordHash String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
生成客户端:
bash
npx prisma migrate dev --name init
npx prisma generate
PrismaService:
ts
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
PrismaModule:
ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
Service 使用:
ts
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
async findAll() {
return this.prisma.user.findMany({
orderBy: { createdAt: 'desc' },
});
}
async findOne(id: string) {
const user = await this.prisma.user.findUnique({ where: { id } });
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
}
async create(dto: CreateUserDto) {
const exists = await this.prisma.user.findUnique({
where: { email: dto.email },
});
if (exists) {
throw new ConflictException('Email already exists');
}
return this.prisma.user.create({
data: {
name: dto.name,
email: dto.email,
passwordHash: dto.password,
},
});
}
}
11.3 数据库访问分层建议
小项目可以在 Service 直接使用 ORM。中大型项目建议引入 Repository 或 Domain Service,以降低业务逻辑和 ORM 的耦合。
12. 认证与授权
认证回答"你是谁",授权回答"你能做什么"。
12.1 JWT 登录流程
12.2 安装依赖
bash
npm i @nestjs/jwt passport passport-jwt @nestjs/passport bcrypt
npm i -D @types/passport-jwt @types/bcrypt
12.3 AuthModule
ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { UsersModule } from '../users/users.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
UsersModule,
JwtModule.registerAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.getOrThrow<string>('JWT_SECRET'),
signOptions: { expiresIn: '2h' },
}),
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
12.4 AuthService
ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) {}
async login(email: string, password: string) {
const user = await this.usersService.findByEmail(email);
if (!user) {
throw new UnauthorizedException('Invalid email or password');
}
const passwordMatched = await bcrypt.compare(password, user.passwordHash);
if (!passwordMatched) {
throw new UnauthorizedException('Invalid email or password');
}
const payload = {
sub: user.id,
email: user.email,
roles: user.roles,
};
return {
accessToken: await this.jwtService.signAsync(payload),
};
}
}
注意:登录失败时不要提示"邮箱不存在"或"密码错误",统一返回无效账号密码,减少账号枚举风险。
12.5 JwtStrategy
ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.getOrThrow<string>('JWT_SECRET'),
});
}
validate(payload: { sub: string; email: string; roles: string[] }) {
return {
userId: payload.sub,
email: payload.email,
roles: payload.roles,
};
}
}
12.6 使用 Guard 保护接口
ts
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller('users')
export class UsersController {
@UseGuards(AuthGuard('jwt'))
@Get('me')
getProfile(@Req() request: Request & { user: unknown }) {
return request.user;
}
}
12.7 RBAC 角色授权
定义角色装饰器:
ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
角色 Guard:
ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext) {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles?.length) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
使用:
ts
@UseGuards(AuthGuard('jwt'), RolesGuard)
@Roles('admin')
@Delete(':id')
remove(@Param('id') id: string) {
return this.usersService.remove(id);
}
12.8 认证授权整体流程
13. 文件上传
NestJS 基于 Multer 支持文件上传。
13.1 单文件上传
ts
import {
Controller,
Post,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
@Controller('files')
export class FilesController {
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
upload(@UploadedFile() file: Express.Multer.File) {
return {
originalName: file.originalname,
mimetype: file.mimetype,
size: file.size,
};
}
}
13.2 限制文件大小和类型
ts
import { ParseFilePipe, MaxFileSizeValidator, FileTypeValidator } from '@nestjs/common';
@Post('avatar')
@UseInterceptors(FileInterceptor('file'))
uploadAvatar(
@UploadedFile(
new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 1024 * 1024 * 2 }),
new FileTypeValidator({ fileType: /^image\/(png|jpeg)$/ }),
],
}),
)
file: Express.Multer.File,
) {
return { size: file.size };
}
13.3 文件上传流程
生产项目中通常不建议把上传文件直接保存在应用服务器本地磁盘,而是上传到对象存储,例如 S3、OSS、COS、OBS 等。
14. Swagger 接口文档
Swagger 可以自动生成接口文档,方便前后端协作。
14.1 安装
bash
npm i @nestjs/swagger swagger-ui-express
14.2 main.ts 注册 Swagger
ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
const config = new DocumentBuilder()
.setTitle('Nest Demo API')
.setDescription('NestJS demo API document')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
访问:
text
http://localhost:3000/docs
14.3 DTO 添加文档注解
ts
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsString, Length } from 'class-validator';
export class CreateUserDto {
@ApiProperty({ example: 'Tom' })
@IsString()
@Length(2, 20)
name: string;
@ApiProperty({ example: 'tom@example.com' })
@IsEmail()
email: string;
@ApiProperty({ example: '123456' })
@IsString()
@Length(6, 32)
password: string;
}
14.4 Controller 添加文档注解
ts
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
@ApiTags('users')
@Controller('users')
export class UsersController {
@ApiOperation({ summary: 'Create user' })
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
@ApiBearerAuth()
@ApiOperation({ summary: 'Get current user profile' })
@Get('me')
getProfile() {}
}
14.5 Swagger 生成流程
15. 测试体系
NestJS 默认使用 Jest。
测试分为:
- 单元测试:测试单个类或函数,Mock 依赖。
- 集成测试:测试模块之间配合。
- E2E 测试:从 HTTP 请求角度验证完整链路。
15.1 单元测试 Service
Service 示例:
ts
@Injectable()
export class UsersService {
constructor(private readonly usersRepository: UsersRepository) {}
async findOne(id: string) {
const user = await this.usersRepository.findOne(id);
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
}
}
测试:
ts
import { NotFoundException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
const usersRepository = {
findOne: jest.fn(),
};
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
UsersService,
{
provide: UsersRepository,
useValue: usersRepository,
},
],
}).compile();
service = moduleRef.get(UsersService);
jest.clearAllMocks();
});
it('returns user when found', async () => {
usersRepository.findOne.mockResolvedValue({ id: '1', name: 'Tom' });
await expect(service.findOne('1')).resolves.toEqual({
id: '1',
name: 'Tom',
});
});
it('throws NotFoundException when user does not exist', async () => {
usersRepository.findOne.mockResolvedValue(null);
await expect(service.findOne('missing')).rejects.toBeInstanceOf(
NotFoundException,
);
});
});
15.2 单元测试 Controller
ts
import { Test } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
describe('UsersController', () => {
let controller: UsersController;
const usersService = {
findAll: jest.fn(),
};
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [UsersController],
providers: [
{
provide: UsersService,
useValue: usersService,
},
],
}).compile();
controller = moduleRef.get(UsersController);
jest.clearAllMocks();
});
it('returns users', async () => {
usersService.findAll.mockResolvedValue([{ id: '1' }]);
await expect(controller.findAll()).resolves.toEqual([{ id: '1' }]);
});
});
15.3 E2E 测试
ts
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module';
describe('UsersController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.init();
});
afterAll(async () => {
await app.close();
});
it('/users (POST) validates request body', async () => {
await request(app.getHttpServer())
.post('/users')
.send({ email: 'not-email' })
.expect(400);
});
});
15.4 测试金字塔
建议:
- 业务规则优先写 Service 单元测试。
- 参数校验、Guard、Interceptor 适合补 E2E。
- 数据库逻辑可以用测试数据库或 Testcontainers。
- 不要为了覆盖率写没有断言价值的测试。
16. 日志、监控与可观测性
生产环境必须关注:
- 请求耗时。
- 错误率。
- 业务异常。
- 数据库慢查询。
- 外部服务调用耗时。
- 进程 CPU、内存、事件循环延迟。
16.1 使用内置 Logger
ts
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class OrdersService {
private readonly logger = new Logger(OrdersService.name);
async createOrder() {
this.logger.log('Creating order');
try {
return await this.doCreateOrder();
} catch (error) {
this.logger.error('Create order failed', error);
throw error;
}
}
}
日志必须包含足够上下文,但不要输出敏感信息。
16.2 请求日志 Interceptor
ts
import {
CallHandler,
ExecutionContext,
Injectable,
Logger,
NestInterceptor,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class AccessLogInterceptor implements NestInterceptor {
private readonly logger = new Logger(AccessLogInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const request = context.switchToHttp().getRequest();
const startedAt = Date.now();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - startedAt;
this.logger.log(`${request.method} ${request.url} ${duration}ms`);
}),
);
}
}
16.3 结构化日志
真实生产环境建议输出 JSON 日志,例如:
json
{
"level": "info",
"message": "request completed",
"method": "GET",
"path": "/users",
"statusCode": 200,
"duration": 35,
"requestId": "req_123"
}
结构化日志便于日志平台检索和聚合。
16.4 可观测性链路
17. 缓存、任务调度与队列
17.1 缓存
安装:
bash
npm i @nestjs/cache-manager cache-manager
注册:
ts
import { CacheModule } from '@nestjs/cache-manager';
@Module({
imports: [
CacheModule.register({
ttl: 60_000,
max: 1000,
}),
],
})
export class AppModule {}
使用:
ts
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable } from '@nestjs/common';
import { Cache } from 'cache-manager';
@Injectable()
export class UsersService {
constructor(@Inject(CACHE_MANAGER) private readonly cache: Cache) {}
async findOne(id: string) {
const cacheKey = `user:${id}`;
const cached = await this.cache.get(cacheKey);
if (cached) {
return cached;
}
const user = await this.loadUserFromDatabase(id);
await this.cache.set(cacheKey, user, 60_000);
return user;
}
}
缓存原则:
- 缓存 Key 设计要稳定。
- 数据更新时要考虑失效策略。
- 不要缓存敏感信息。
- 热点接口适合缓存,强一致接口谨慎缓存。
17.2 定时任务
安装:
bash
npm i @nestjs/schedule
注册:
ts
import { ScheduleModule } from '@nestjs/schedule';
@Module({
imports: [ScheduleModule.forRoot()],
})
export class AppModule {}
任务:
ts
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class ReportTask {
private readonly logger = new Logger(ReportTask.name);
@Cron('0 0 * * *')
async generateDailyReport() {
this.logger.log('Generating daily report');
}
}
17.3 队列
耗时任务不适合放在 HTTP 请求里同步执行,例如:
- 发送邮件。
- 生成报表。
- 视频转码。
- 调用不稳定的第三方服务。
- 批量导入导出。
可以使用 BullMQ。
bash
npm i @nestjs/bullmq bullmq ioredis
流程:
18. 微服务与消息通信
NestJS 支持多种微服务传输层:
- TCP
- Redis
- NATS
- MQTT
- RabbitMQ
- Kafka
- gRPC
18.1 HTTP 与消息通信的区别
| 模式 | 特点 | 场景 |
|---|---|---|
| HTTP REST | 同步请求响应,简单直观 | 对外 API、BFF |
| 消息队列 | 异步解耦,削峰填谷 | 订单事件、通知、任务 |
| gRPC | 强类型、高性能 | 内部服务通信 |
| Kafka | 高吞吐事件流 | 日志、埋点、事件驱动 |
18.2 创建 TCP 微服务
ts
import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 8877,
},
});
await app.listen();
}
bootstrap();
Controller:
ts
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
sum(numbers: number[]) {
return numbers.reduce((total, item) => total + item, 0);
}
}
客户端:
ts
import { ClientProxyFactory, Transport } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';
const client = ClientProxyFactory.create({
transport: Transport.TCP,
options: {
host: '127.0.0.1',
port: 8877,
},
});
const result = await firstValueFrom(client.send({ cmd: 'sum' }, [1, 2, 3]));
18.3 事件驱动架构
事件驱动的好处:
- 服务之间低耦合。
- 消费者可以独立扩展。
- 失败可以重试。
- 更适合异步业务流程。
风险:
- 链路更难追踪。
- 最终一致性复杂。
- 消息重复消费需要幂等设计。
- 死信队列和补偿机制不可缺少。
19. 项目分层与代码组织
19.1 推荐目录结构
text
src/
├── main.ts
├── app.module.ts
├── common/
│ ├── decorators/
│ ├── filters/
│ ├── guards/
│ ├── interceptors/
│ ├── pipes/
│ └── types/
├── config/
│ ├── app.config.ts
│ └── database.config.ts
├── modules/
│ ├── auth/
│ ├── users/
│ ├── orders/
│ └── payments/
├── database/
│ ├── migrations/
│ └── seeds/
└── shared/
├── logger/
└── clients/
19.2 分层职责
职责建议:
- Controller:HTTP 参数、状态码、认证装饰器,不写复杂业务。
- Application Service:编排业务流程。
- Domain Service:表达核心业务规则。
- Repository:封装数据访问。
- Client:封装外部系统调用。
- DTO:定义入参出参结构。
19.3 一个订单创建示例
需求:
- 用户提交订单。
- 校验用户存在。
- 校验库存。
- 创建订单。
- 扣减库存。
- 发送订单创建事件。
流程图:
Service 示例:
ts
@Injectable()
export class OrdersService {
constructor(
private readonly usersService: UsersService,
private readonly inventoryService: InventoryService,
private readonly ordersRepository: OrdersRepository,
private readonly eventBus: EventBus,
) {}
async createOrder(userId: string, dto: CreateOrderDto) {
await this.usersService.ensureUserExists(userId);
await this.inventoryService.ensureStock(dto.productId, dto.quantity);
const order = await this.ordersRepository.create({
userId,
productId: dto.productId,
quantity: dto.quantity,
status: 'CREATED',
});
await this.inventoryService.decreaseStock(dto.productId, dto.quantity);
await this.eventBus.publish('OrderCreated', { orderId: order.id });
return order;
}
}
这段代码适合理解业务编排,但真实生产中还要考虑事务、幂等、库存并发、消息一致性。
19.4 事务处理
涉及多次数据库写操作时,要考虑事务。
TypeORM 示例:
ts
await this.dataSource.transaction(async (manager) => {
const order = await manager.save(Order, orderEntity);
await manager.decrement(Product, { id: productId }, 'stock', quantity);
return order;
});
Prisma 示例:
ts
await this.prisma.$transaction(async (tx) => {
const order = await tx.order.create({ data: orderData });
await tx.product.update({
where: { id: productId },
data: { stock: { decrement: quantity } },
});
return order;
});
事务边界建议放在应用服务层,因为它最清楚一个业务用例包含哪些写操作。
20. 性能、安全与生产部署
20.1 性能优化方向
常见优化点:
- 使用 Fastify 替代 Express。
- 数据库查询加索引。
- 避免 N+1 查询。
- 对热点数据加缓存。
- 使用分页,不返回超大列表。
- 大任务异步化。
- 合理设置连接池。
- 开启 gzip/br 压缩。
- 避免同步阻塞 CPU 的计算。
20.2 使用 Fastify
安装:
bash
npm i @nestjs/platform-fastify
main.ts:
ts
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
await app.listen({ port: 3000, host: '0.0.0.0' });
}
bootstrap();
20.3 安全加固
常见安全措施:
- 开启 Helmet。
- 配置 CORS 白名单。
- 请求体大小限制。
- 登录接口限流。
- 密码哈希存储。
- JWT secret 使用强随机密钥。
- 敏感配置不入库、不提交 Git。
- 错误响应不暴露堆栈。
- 对外部输入全部校验。
- 文件上传限制大小、类型并做安全扫描。
安装 Helmet:
bash
npm i helmet
使用:
ts
import helmet from 'helmet';
app.use(helmet());
限流:
bash
npm i @nestjs/throttler
ts
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
@Module({
imports: [
ThrottlerModule.forRoot([
{
ttl: 60_000,
limit: 100,
},
]),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}
20.4 Docker 部署
示例 Dockerfile:
dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main.js"]
20.5 健康检查
安装:
bash
npm i @nestjs/terminus
示例:
ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(private readonly health: HealthCheckService) {}
@Get()
@HealthCheck()
check() {
return this.health.check([]);
}
}
健康检查用于:
- Kubernetes readiness/liveness probe。
- 发布后验证服务状态。
- 监控系统探活。
20.6 生产启动流程
21. 从入门到精通的学习路线
21.1 第一阶段:能写接口
目标:
- 理解
main.ts、Module、Controller、Service。 - 能写 REST API。
- 会用 DTO 和 ValidationPipe。
- 会处理常见异常。
练习项目:
- Todo API。
- 用户 CRUD。
- 简单博客 API。
必须掌握:
- 路由参数。
- 请求体校验。
- 依赖注入。
- 模块拆分。
21.2 第二阶段:能做完整业务
目标:
- 接入数据库。
- 实现登录注册。
- 使用 JWT 保护接口。
- 添加 Swagger。
- 写单元测试和 E2E 测试。
练习项目:
- 用户中心。
- 订单管理。
- 后台管理系统 API。
必须掌握:
- ORM。
- 事务。
- Guard。
- Interceptor。
- Exception Filter。
- ConfigModule。
21.3 第三阶段:能做生产系统
目标:
- 日志和监控。
- 缓存。
- 队列。
- 限流。
- Docker 部署。
- 数据库迁移。
练习项目:
- 电商订单系统。
- 消息通知系统。
- 报表导出系统。
必须掌握:
- 幂等。
- 重试。
- 超时。
- 降级。
- 安全加固。
- 性能排查。
21.4 第四阶段:能做架构设计
目标:
- 模块边界清晰。
- 能拆分领域。
- 能设计微服务通信。
- 能处理最终一致性。
- 能组织大型项目代码。
重点能力:
- 领域建模。
- 事件驱动。
- 分布式事务补偿。
- 可观测性设计。
- 自动化测试策略。
- CI/CD 流程设计。
21.5 学习路线图
22. 常见问题与排查清单
22.1 依赖注入失败
常见报错:
text
Nest can't resolve dependencies of the UsersService
排查:
- Provider 是否写进当前模块的
providers。 - 被其他模块使用时,是否在原模块
exports。 - 当前模块是否
imports了提供该 Provider 的模块。 - 是否出现循环依赖。
- 自定义 token 是否拼写一致。
22.2 DTO 校验不生效
排查:
- 是否安装
class-validator和class-transformer。 - 是否在
main.ts注册ValidationPipe。 - DTO 字段是否加了校验装饰器。
- 请求体 Content-Type 是否为
application/json。 - 是否使用了 TypeScript interface,interface 运行时会被擦除,DTO 应使用 class。
22.3 环境变量读取不到
排查:
- 是否导入
ConfigModule.forRoot()。 .env文件路径是否正确。- 变量名大小写是否一致。
- Docker/K8s 部署时是否注入环境变量。
- 是否使用
getOrThrow暴露启动时配置错误。
22.4 Guard 不生效
排查:
- 是否在 Controller 或方法上添加
@UseGuards()。 - 全局 Guard 是否通过
APP_GUARD注册。 - 多个 Guard 的执行顺序是否符合预期。
- JWT Strategy 是否正确注册。
- 请求头是否是
Authorization: Bearer <token>。
22.5 数据库连接失败
排查:
- 数据库服务是否启动。
- 连接字符串是否正确。
- 账号密码是否正确。
- 网络和防火墙是否允许访问。
- 连接池配置是否合理。
- 容器环境中 host 是否不能使用
localhost。
22.6 线上接口偶发慢
排查:
- 数据库慢查询。
- 是否缺少索引。
- 是否存在 N+1 查询。
- 外部服务是否慢。
- 是否有同步 CPU 密集计算。
- Node.js 事件循环是否阻塞。
- 日志是否同步写入或过多。
结语
学习 NestJS 不只是学习几个装饰器,更重要的是建立后端工程化思维:
- 用 Module 管理边界。
- 用 Provider 管理依赖。
- 用 DTO 和 Pipe 保护输入。
- 用 Guard 和 Interceptor 承载横切逻辑。
- 用 Filter 统一异常出口。
- 用测试保证业务演进。
- 用日志、监控和部署体系支撑生产稳定性。
当你能清楚回答以下问题时,就已经从"会用 NestJS"走向"能驾驭 NestJS":
- 一个请求在系统里经过哪些环节?
- 一个业务模块的边界在哪里?
- 业务规则应该放在哪一层?
- 依赖应该如何注入和替换?
- 异常、日志、鉴权、校验如何统一处理?
- 如何让代码可测试、可维护、可部署、可观测?
NestJS 的真正价值不在于让你少写几行代码,而在于让团队在长期迭代中仍然保持稳定、清晰和可控。