文章目录
- **第一部分:认识Nest.js与基础环境搭建**
- **1.1 什么是Nest.js?**
- **1.2 环境准备**
- **1.3 创建第一个项目**
- **1.4 启动开发服务器**
- **1.5 核心文件解读**
- **第二部分:基础控制器与路由**
- **2.1 控制器的作用**
- **2.2 创建自定义控制器**
- **2.3 路由参数处理**
- **2.4 状态码处理**
- **2.5 完整示例**
- **2.6 测试你的API**
- **关键概念总结**
- **第三部分:服务层与依赖注入**
- **3.1 为什么需要服务层?**
- **3.2 创建第一个服务**
- **3.3 依赖注入实战**
- **3.4 模块(Module)的作用**
- **3.5 完整流程测试**
- **3.6 依赖注入原理图解**
- **核心概念对比**
- **第四部分:数据库集成(TypeORM + PostgreSQL)**
- **4.1 准备工作**
- **4.2 配置数据库连接**
- **4.3 创建第一个实体(Entity)**
- **4.4 修改服务层操作数据库**
- **4.5 修改用户模块**
- **4.6 数据验证(DTO)**
- **4.7 完整API测试**
- **4.8 错误处理示例**
- **数据库操作关键API**
- **第五部分:身份认证(JWT策略)**
- **5.1 认证方案概述**
- **5.2 安装必要依赖**
- **5.3 用户实体增强**
- **5.4 配置JWT模块**
- **5.5 实现认证服务**
- **5.6 创建策略守卫**
- **5.7 实现认证控制器**
- **5.8 保护路由**
- **5.9 测试认证流程**
- **5.10 安全增强建议**
- **认证核心组件**
- **第六部分:异常处理与日志**
- **6.1 异常处理的重要性**
- **6.2 创建自定义异常过滤器**
- **6.3 全局注册过滤器**
- **6.4 使用内置HTTP异常**
- **6.5 日志记录配置**
- **6.6 集成Winston日志**
- **6.7 请求日志中间件**
- **6.8 错误追踪集成(Sentry示例)**
- **6.9 测试验证**
- **6.10 最佳实践建议**
- **异常处理核心机制**
- **第七部分:单元测试与E2E测试**
- **7.1 测试金字塔模型**
- **7.2 初始化测试环境**
- **7.3 服务层单元测试**
- **7.4 控制器层测试**
- **7.5 端到端测试(E2E)**
- **7.6 运行与解读测试**
- **7.7 测试最佳实践**
- **测试类型对比**
- **第八部分:部署与生产环境优化**
- **8.1 Docker容器化部署**
- **8.2 环境变量配置**
- **8.3 性能优化策略**
- **8.4 健康检查与监控**
- **8.5 日志收集方案**
- **8.6 部署验证**
- **8.7 生产环境检查清单**
- **部署架构示意图**
- **第九部分:微服务架构进阶**
- **9.1 微服务核心概念**
- **9.2 创建基础微服务**
- **9.3 实现gRPC通信**
- **9.4 RabbitMQ消息队列集成**
- **9.5 分布式事务处理(Saga模式示例)**
- **9.6 微服务通信模式对比**
- **9.7 服务发现与负载均衡**
- **9.8 测试微服务通信**
- **9.9 生产环境注意事项**
- **微服务架构核心组件**
- **第十部分:前端集成与全栈实践**
- **10.1 Swagger API文档集成**
- **10.2 前端项目配置(以React为例)**
- **10.3 实现登录认证流程**
- **10.4 前端路由保护(React示例)**
- **10.5 全栈调试技巧**
- **10.6 部署联调配置**
- **全栈开发关键点总结**
- **项目完整工作流**
第一部分:认识Nest.js与基础环境搭建
1.1 什么是Nest.js?
- Node.js后端框架(类似Express/Koa但更结构化)
- 使用TypeScript构建(也支持JavaScript)
- 结合了面向对象编程(OOP)+ 函数式编程(FP)
- 内置依赖注入、模块化架构
- 适合构建高效、可靠且易维护的服务端应用
1.2 环境准备
步骤1:安装Node.js
- 前往官网下载LTS版本(建议v18+)
- 安装完成后验证:
node -v # 显示版本号 npm -v # 显示版本号
步骤2:安装Nest CLI
npm install -g @nestjs/cli
1.3 创建第一个项目
nest new my-first-project
选择包管理器(推荐使用npm或yarn)
目录结构说明:
my-first-project/
├── src/
│ ├── app.controller.ts # 控制器(处理HTTP请求)
│ ├── app.service.ts # 服务(业务逻辑)
│ ├── app.module.ts # 根模块(组织应用结构)
│ └── main.ts # 应用入口文件
1.4 启动开发服务器
cd my-first-project
npm run start:dev
访问 http://localhost:3000 看到"Hello World!"
1.5 核心文件解读
main.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();
app.controller.ts(示例控制器):
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';@Controller() // 控制器装饰器
export class AppController {constructor(private readonly appService: AppService) {} // 依赖注入@Get() // 处理GET请求getHello(): string {return this.appService.getHello();}
}
第二部分:基础控制器与路由
2.1 控制器的作用
- 负责处理客户端请求
- 通过装饰器定义路由路径
- 调用服务层处理业务逻辑
- 返回HTTP响应
2.2 创建自定义控制器
步骤1:生成新控制器
nest generate controller user
# 或简写 nest g co user
生成文件 src/user/user.controller.ts
步骤2:基础路由示例
// user.controller.ts
import { Controller, Get } from '@nestjs/common';@Controller('users') // 定义路由前缀 /users
export class UserController {@Get() // 处理 /users 的GET请求findAll(): string {return 'All users';}@Get('profile') // 处理 /users/profile 的GET请求getProfile(): string {return 'User profile';}
}
2.3 路由参数处理
示例1:路径参数
@Get(':id') // 匹配 /users/123 形式的请求
findOne(@Param('id') id: string): string {return `User ID: ${id}`;
}
示例2:查询参数
@Get('search')
search(@Query('name') name: string): string {return `Searching for: ${name}`;
}
示例3:POST请求体
@Post()
create(@Body() userData: any): string {return `Created user: ${JSON.stringify(userData)}`;
}
2.4 状态码处理
- 默认GET返回200,POST返回201
- 手动指定状态码:
@Post()
@HttpCode(202) // 自定义状态码
createWithStatus() {return 'Created with custom status';
}
2.5 完整示例
import { Controller, Get, Post, Param, Query, Body, HttpCode } from '@nestjs/common';@Controller('users')
export class UserController {@Get()findAll(): string {return 'User list';}@Post()create(@Body() user: { name: string }): string {return `Created user: ${user.name}`;}@Get(':id')findOne(@Param('id') id: string): string {return `User ID: ${id}`;}@Get('search')search(@Query('keyword') keyword: string): string {return `Search keyword: ${keyword}`;}
}
2.6 测试你的API
-
使用Postman或curl测试:
# GET请求示例 curl http://localhost:3000/users curl http://localhost:3000/users/123 curl http://localhost:3000/users/search?keyword=john# POST请求示例 curl -X POST -H "Content-Type: application/json" -d '{"name":"Alice"}' http://localhost:3000/users
-
观察返回结果是否符合预期
关键概念总结
装饰器 | 作用 | 示例 |
---|---|---|
@Controller | 定义控制器类并设置路由前缀 | @Controller('users') |
@Get | 处理GET请求 | @Get(':id') |
@Post | 处理POST请求 | @Post() |
@Param | 获取路径参数 | @Param('id') |
@Query | 获取查询参数 | @Query('page') |
@Body | 获取请求体数据 | @Body() userData |
@HttpCode | 设置响应状态码 | @HttpCode(204) |
第三部分:服务层与依赖注入
3.1 为什么需要服务层?
- 遵循单一职责原则(控制器只处理请求/响应)
- 集中存放业务逻辑
- 方便代码复用和测试
- 通过依赖注入实现解耦
3.2 创建第一个服务
步骤1:生成服务文件
nest generate service user
# 或简写 nest g s user
生成文件:
src/user/user.service.ts
(服务类)src/user/user.service.spec.ts
(测试文件)
步骤2:基础服务示例
// user.service.ts
import { Injectable } from '@nestjs/common';@Injectable() // 标记为可注入的类
export class UserService {private users = [{ id: 1, name: 'John' },{ id: 2, name: 'Alice' }];getAllUsers() {return this.users;}createUser(name: string) {const newUser = { id: Date.now(), name };this.users.push(newUser);return newUser;}
}
3.3 依赖注入实战
改造控制器(user.controller.ts):
import { Controller, Get, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';@Controller('users')
export class UserController {constructor(private readonly userService: UserService) {} // 依赖注入@Get()getAllUsers() {return this.userService.getAllUsers();}@Post()createUser(@Body('name') name: string) {return this.userService.createUser(name);}
}
3.4 模块(Module)的作用
查看自动生成的 user.module.ts:
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';@Module({controllers: [UserController],providers: [UserService], // 注册服务
})
export class UserModule {}
关键点:
- 模块是Nest的组织单元
providers
数组注册可注入的类- 通过
@Global()
可以创建全局模块
3.5 完整流程测试
-
通过Postman发送请求:
GET http://localhost:3000/users POST http://localhost:3000/users Body: { "name": "Bob" }
-
观察响应结果:
// GET 响应 [{ "id": 1, "name": "John" },{ "id": 2, "name": "Alice" } ]// POST 响应 { "id": 1625641654845, "name": "Bob" }
3.6 依赖注入原理图解
+---------------+
| Controller |
|---------------| +---------------+
| constructor( |<--------| UserService |
| userService) | +---------------+
+---------------+▲| 通过@Module装饰器的providers数组注册
+---------------+
| Module |
|---------------|
| providers: [ |
| UserService |
| ] |
+---------------+
核心概念对比
组件 | 职责 | 关键装饰器 |
---|---|---|
控制器(Controller) | 处理HTTP请求/响应 | @Controller |
服务(Service) | 实现业务逻辑 | @Injectable |
模块(Module) | 组织应用结构,管理依赖关系 | @Module |
接下来计划讲解:
第四部分:数据库集成(TypeORM + PostgreSQL)
包括:
- TypeORM基本配置
- 实体(Entity)创建
- CRUD操作实现
- 数据验证与DTO
第四部分:数据库集成(TypeORM + PostgreSQL)
4.1 准备工作
-
安装所需依赖:
npm install @nestjs/typeorm typeorm pg
@nestjs/typeorm
: Nest的TypeORM集成包typeorm
: ORM框架pg
: PostgreSQL驱动
-
确保已安装PostgreSQL数据库(本地或使用云服务)
4.2 配置数据库连接
修改app.module.ts
:
import { TypeOrmModule } from '@nestjs/typeorm';@Module({imports: [TypeOrmModule.forRoot({type: 'postgres',host: 'localhost', // 数据库地址port: 5432, // 数据库端口username: 'postgres', // 数据库用户名password: 'your_password', // 数据库密码database: 'nestdemo', // 数据库名称entities: [__dirname + '/**/*.entity{.ts,.js}'], // 自动扫描实体文件synchronize: true, // 开发环境自动同步数据库结构(生产环境禁用!)}),UserModule,],
})
export class AppModule {}
4.3 创建第一个实体(Entity)
新建src/user/user.entity.ts
:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';@Entity() // 标记为数据库实体
export class User {@PrimaryGeneratedColumn() // 自增主键id: number;@Column({ length: 50 }) // 字符串列,长度限制50name: string;@Column({ unique: true }) // 唯一约束email: string;@Column({ default: () => 'CURRENT_TIMESTAMP' }) // 默认值createdAt: Date;
}
4.4 修改服务层操作数据库
更新user.service.ts
:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';@Injectable()
export class UserService {constructor(@InjectRepository(User) // 注入用户实体仓库private userRepository: Repository<User>,) {}async findAll(): Promise<User[]> {return this.userRepository.find();}async create(userData: Partial<User>): Promise<User> {const newUser = this.userRepository.create(userData);return this.userRepository.save(newUser);}async findOne(id: number): Promise<User | null> {return this.userRepository.findOne({ where: { id } });}async remove(id: number): Promise<void> {await this.userRepository.delete(id);}
}
4.5 修改用户模块
更新user.module.ts
:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserService } from './user.service';
import { UserController } from './user.controller';@Module({imports: [TypeOrmModule.forFeature([User])], // 注册实体到模块controllers: [UserController],providers: [UserService],
})
export class UserModule {}
4.6 数据验证(DTO)
-
安装验证库:
npm install class-validator class-transformer
-
创建
src/user/dto/create-user.dto.ts
:import { IsString, IsEmail, Length } from 'class-validator';export class CreateUserDto {@IsString()@Length(2, 50)name: string;@IsEmail()email: string; }
-
更新控制器:
import { Body, ValidationPipe } from '@nestjs/common';@Post() async create(@Body(new ValidationPipe()) createUserDto: CreateUserDto, // 自动验证 ) {return this.userService.create(createUserDto); }
4.7 完整API测试
# 创建用户
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"Lisa","email":"lisa@example.com"}' \
http://localhost:3000/users# 获取所有用户
curl http://localhost:3000/users# 获取单个用户
curl http://localhost:3000/users/1# 删除用户
curl -X DELETE http://localhost:3000/users/1
4.8 错误处理示例
// 在控制器中处理查找异常
@Get(':id')
async findOne(@Param('id') id: string) {const user = await this.userService.findOne(+id);if (!user) {throw new NotFoundException('User not found'); // 自动返回404}return user;
}
数据库操作关键API
方法 | 作用 | 示例 |
---|---|---|
repository.find() | 获取所有记录 | userRepository.find() |
repository.findOne() | 获取单条记录 | findOne({ where: { id } }) |
repository.save() | 创建/更新记录 | save(user) |
repository.delete() | 删除记录 | delete(id) |
接下来计划讲解:
第五部分:身份认证(JWT策略)
包括:
- 用户注册/登录实现
- Passport.js集成
- JWT签发与验证
- 路由守卫使用
第五部分:身份认证(JWT策略)
5.1 认证方案概述
5.2 安装必要依赖
npm install @nestjs/passport passport passport-jwt @nestjs/jwt bcrypt
npm install @types/passport-jwt @types/bcrypt --save-dev
5.3 用户实体增强
// user.entity.ts
import { BeforeInsert } from 'typeorm';@Entity()
export class User {// ...其他字段@Column()password: string;@BeforeInsert() // 自动加密密码async hashPassword() {this.password = await bcrypt.hash(this.password, 10);}
}
5.4 配置JWT模块
// auth.module.ts
import { JwtModule } from '@nestjs/jwt';@Module({imports: [JwtModule.register({global: true,secret: 'your-secret-key', // 生产环境应使用环境变量signOptions: { expiresIn: '1h' },}),],// ...
})
export class AuthModule {}
5.5 实现认证服务
// auth.service.ts
import { compare } from 'bcrypt';@Injectable()
export class AuthService {constructor(@InjectRepository(User)private usersRepository: Repository<User>,private jwtService: JwtService,) {}async validateUser(email: string, pass: string): Promise<any> {const user = await this.usersRepository.findOne({ where: { email } });if (user && await compare(pass, user.password)) {const { password, ...result } = user; // 移除密码字段return result;}return null;}async login(user: User) {const payload = { email: user.email, sub: user.id };return {access_token: this.jwtService.sign(payload),};}
}
5.6 创建策略守卫
// jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {constructor() {super({jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),ignoreExpiration: false,secretOrKey: 'your-secret-key',});}async validate(payload: any) {return { userId: payload.sub, email: payload.email };}
}
5.7 实现认证控制器
// auth.controller.ts
@Controller('auth')
export class AuthController {constructor(private authService: AuthService) {}@Post('login')async login(@Body() loginDto: LoginDto) {const user = await this.authService.validateUser(loginDto.email,loginDto.password,);if (!user) {throw new UnauthorizedException('Invalid credentials');}return this.authService.login(user);}@Post('register')async register(@Body() createUserDto: CreateUserDto) {return this.authService.register(createUserDto);}
}
5.8 保护路由
// user.controller.ts
@Get('profile')
@UseGuards(AuthGuard('jwt')) // 添加守卫
async getProfile(@Request() req) {return req.user;
}
5.9 测试认证流程
# 注册用户
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"test","email":"test@example.com","password":"123456"}' \
http://localhost:3000/auth/register# 登录获取Token
curl -X POST -H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"123456"}' \
http://localhost:3000/auth/login# 访问受保护资源
curl -H "Authorization: Bearer your-token" \
http://localhost:3000/users/profile
5.10 安全增强建议
- 使用环境变量存储密钥
- 实现密码强度验证
- 添加刷新令牌机制
- 设置合理的令牌有效期
- 记录认证日志
认证核心组件
组件 | 作用 | 关键方法/装饰器 |
---|---|---|
JwtModule | JWT配置模块 | register() |
JwtStrategy | 验证请求携带的JWT | validate() |
AuthGuard | 路由守卫 | @UseGuards(AuthGuard()) |
bcrypt | 密码哈希处理 | hash() , compare() |
@nestjs/jwt | JWT签发与验证 | sign() , verify() |
第六部分:异常处理与日志
6.1 异常处理的重要性
6.2 创建自定义异常过滤器
步骤1:生成过滤器
nest generate filter common/exceptions/http-exception
步骤2:实现过滤器逻辑
// http-exception.filter.ts
import {ExceptionFilter,Catch,ArgumentsHost,HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {catch(exception: HttpException, host: ArgumentsHost) {const ctx = host.switchToHttp();const response = ctx.getResponse<Response>();const request = ctx.getRequest<Request>();const status = exception.getStatus();response.status(status).json({statusCode: status,timestamp: new Date().toISOString(),path: request.url,message: exception.message || 'Unknown error',});}
}
6.3 全局注册过滤器
// main.ts
async function bootstrap() {const app = await NestFactory.create(AppModule);app.useGlobalFilters(new HttpExceptionFilter()); // 注册全局过滤器await app.listen(3000);
}
6.4 使用内置HTTP异常
// user.controller.ts
@Get(':id')
async findOne(@Param('id') id: string) {const user = await this.userService.findOne(+id);if (!user) {throw new NotFoundException(`User ${id} not found`);}return user;
}
6.5 日志记录配置
启用默认日志
// main.ts
const app = await NestFactory.create(AppModule, {logger: ['log', 'error', 'warn', 'debug', 'verbose'],
});
自定义日志服务
// logger.service.ts
import { LoggerService } from '@nestjs/common';export class MyLogger implements LoggerService {log(message: string) {console.log(`[LOG] ${new Date().toISOString()} - ${message}`);}error(message: string, trace: string) {console.error(`[ERROR] ${new Date().toISOString()} - ${message}`, trace);}warn(message: string) {console.warn(`[WARN] ${new Date().toISOString()} - ${message}`);}
}// main.ts
const app = await NestFactory.create(AppModule, {logger: new MyLogger(),
});
6.6 集成Winston日志
- 安装依赖:
npm install winston @types/winston
- 创建日志配置文件:
// logger/logger.config.ts
import { createLogger, format, transports } from 'winston';export const winstonConfig = {level: 'info',format: format.combine(format.timestamp(),format.printf(({ timestamp, level, message }) => {return `${timestamp} [${level}]: ${message}`;})),transports: [new transports.Console(),new transports.File({ filename: 'logs/error.log', level: 'error' }),new transports.File({ filename: 'logs/combined.log' }),],
};
- 创建NestJS适配器:
// logger/winston.logger.ts
import { LoggerService } from '@nestjs/common';
import { createLogger, Logger } from 'winston';
import { winstonConfig } from './logger.config';export class WinstonLogger implements LoggerService {private logger: Logger;constructor() {this.logger = createLogger(winstonConfig);}log(message: string) {this.logger.info(message);}error(message: string, trace: string) {this.logger.error(`${message} - ${trace}`);}warn(message: string) {this.logger.warn(message);}debug(message: string) {this.logger.debug(message);}verbose(message: string) {this.logger.verbose(message);}
}
6.7 请求日志中间件
// logger/request.logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {use(req: Request, res: Response, next: NextFunction) {const start = Date.now();const { method, originalUrl } = req;res.on('finish', () => {const duration = Date.now() - start;const { statusCode } = res;console.log(`[${method}] ${originalUrl} - ${statusCode} (${duration}ms)`,);});next();}
}// app.module.ts
export class AppModule implements NestModule {configure(consumer: MiddlewareConsumer) {consumer.apply(RequestLoggerMiddleware).forRoutes('*');}
}
6.8 错误追踪集成(Sentry示例)
- 安装依赖:
npm install @sentry/node @sentry/tracing
- 配置Sentry:
// sentry.config.ts
import * as Sentry from '@sentry/node';Sentry.init({dsn: 'your-dsn-here',tracesSampleRate: 1.0,
});
- 集成到异常过滤器:
// http-exception.filter.ts
catch(exception: HttpException, host: ArgumentsHost) {const ctx = host.switchToHttp();const request = ctx.getRequest<Request>();Sentry.captureException(exception, {extra: {path: request.url,method: request.method,body: request.body,},});// ...原有处理逻辑
}
6.9 测试验证
# 触发404错误
curl http://localhost:3000/nonexistent# 查看日志文件
tail -f logs/error.log# 预期输出示例
2023-08-20T10:00:00.000Z [ERROR] 404 - Cannot GET /nonexistent
6.10 最佳实践建议
-
使用不同日志级别:
verbose
: 详细调试信息debug
: 调试信息log
: 常规日志warn
: 警告信息error
: 错误信息
-
日志文件管理:
- 使用logrotate进行日志轮换
- 敏感信息过滤
- 按日期分割日志文件
-
生产环境注意事项:
- 禁用
synchronize
选项 - 设置适当的日志级别
- 使用集中式日志系统(ELK/Splunk)
- 禁用
异常处理核心机制
组件 | 作用 | 关键方法/装饰器 |
---|---|---|
ExceptionFilter | 捕获并处理异常 | @Catch() |
HttpException | 预定义的HTTP异常 | new BadRequestException() |
LoggerService | 日志接口 | log() , error() |
Middleware | 记录请求信息 | implements NestMiddleware |
第七部分:单元测试与E2E测试
7.1 测试金字塔模型
7.2 初始化测试环境
项目已内置测试配置:
test/
目录:存放测试相关配置jest.config.js
:Jest测试框架配置package.json
脚本:{"scripts": {"test": "jest","test:watch": "jest --watch","test:cov": "jest --coverage"} }
7.3 服务层单元测试
测试目标:UserService
创建测试文件:user.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserService } from './user.service';// 模拟Repository
const mockRepository = {find: jest.fn(),findOne: jest.fn(),save: jest.fn(),delete: jest.fn(),
};describe('UserService', () => {let service: UserService;beforeEach(async () => {const module: TestingModule = await Test.createTestingModule({providers: [UserService,{provide: getRepositoryToken(User),useValue: mockRepository,},],}).compile();service = module.get<UserService>(UserService);});afterEach(() => {jest.clearAllMocks();});describe('findAll', () => {it('应返回用户数组', async () => {const mockUsers = [{ id: 1, name: 'Test' }];mockRepository.find.mockResolvedValue(mockUsers);const result = await service.findAll();expect(result).toEqual(mockUsers);expect(mockRepository.find).toHaveBeenCalledTimes(1);});});describe('create', () => {it('应成功创建用户', async () => {const newUser = { name: 'New' };mockRepository.save.mockResolvedValue({ id: 1, ...newUser });const result = await service.create(newUser);expect(result).toHaveProperty('id', 1);expect(mockRepository.save).toHaveBeenCalledWith(newUser);});});
});
7.4 控制器层测试
测试目标:UserController
创建测试文件:user.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UserController } from './user.controller';
import { UserService } from './user.service';describe('UserController', () => {let controller: UserController;const mockUserService = {findAll: jest.fn().mockResolvedValue([{ id: 1 }]),create: jest.fn().mockImplementation((dto) => Promise.resolve({ id: 1, ...dto })),};beforeEach(async () => {const module: TestingModule = await Test.createTestingModule({controllers: [UserController],providers: [{provide: UserService,useValue: mockUserService,},],}).compile();controller = module.get<UserController>(UserController);});it('GET /users 应返回用户列表', async () => {await expect(controller.getAllUsers()).resolves.toEqual([{ id: 1 }]);expect(mockUserService.findAll).toHaveBeenCalled();});it('POST /users 应创建新用户', async () => {const dto = { name: 'Test' };await expect(controller.createUser(dto)).resolves.toEqual({id: 1,...dto,});expect(mockUserService.create).toHaveBeenCalledWith(dto);});
});
7.5 端到端测试(E2E)
创建测试文件:app.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';describe('AppController (e2e)', () => {let app: INestApplication;beforeAll(async () => {const moduleFixture: TestingModule = await Test.createTestingModule({imports: [AppModule],}).compile();app = moduleFixture.createNestApplication();await app.init();});afterAll(async () => {await app.close();});it('/ (GET)', () => {return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!');});describe('用户模块', () => {it('GET /users 应返回空数组', () => {return request(app.getHttpServer()).get('/users').expect(200).expect([]);});it('POST /users 应创建用户', async () => {const response = await request(app.getHttpServer()).post('/users').send({ name: 'E2E Test' }).expect(201);expect(response.body).toHaveProperty('id');expect(response.body.name).toBe('E2E Test');});});
});
7.6 运行与解读测试
执行测试命令:
# 运行全部测试
npm run test# 开发时监听模式
npm run test:watch# 生成覆盖率报告
npm run test:cov
覆盖率报告示例:
----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files | 92 | 85 | 90 | 92 | user.service | 100 | 100 | 100 | 100 | user.controller| 95 | 80 | 90 | 95 | 32
----------------|---------|----------|---------|---------|-------------------
7.7 测试最佳实践
-
测试命名规范:
describe('场景描述', () => {it('应完成特定行为', () => {// 测试逻辑}); });
-
测试三部曲:
- 准备(Arrange):设置测试数据和模拟
- 执行(Act):调用被测试方法
- 断言(Assert):验证结果
-
测试隔离原则:
- 每个测试用例独立运行
- 使用
beforeEach/afterEach
重置状态 - 避免测试间的依赖关系
-
测试数据库策略:
// 使用测试专用数据库 TypeOrmModule.forRoot({database: 'test_db',synchronize: true, })
测试类型对比
测试类型 | 测试范围 | 执行速度 | 维护成本 | 适合场景 |
---|---|---|---|---|
单元测试 | 单个类/方法 | 快 | 低 | 核心业务逻辑验证 |
集成测试 | 模块间交互 | 中 | 中 | 服务层与数据库交互 |
E2E测试 | 完整系统流程 | 慢 | 高 | 用户操作流程验证 |
第八部分:部署与生产环境优化
8.1 Docker容器化部署
步骤1:创建Dockerfile
# 使用Node.js官方镜像
FROM node:18-alpine# 设置工作目录
WORKDIR /app# 复制依赖文件
COPY package*.json ./# 安装依赖(生产环境不装devDependencies)
RUN npm install --only=production# 复制项目文件
COPY . .# 构建项目(如果需要编译TypeScript)
RUN npm run build# 暴露端口
EXPOSE 3000# 启动命令
CMD ["npm", "run", "start:prod"]
步骤2:创建docker-compose.yml
version: '3.8'services:app:build: .ports:- "3000:3000"environment:- NODE_ENV=productiondepends_on:- dbdb:image: postgres:15environment:POSTGRES_USER: postgresPOSTGRES_PASSWORD: yoursecurepasswordPOSTGRES_DB: nestprodvolumes:- pgdata:/var/lib/postgresql/dataports:- "5432:5432"volumes:pgdata:
步骤3:构建并启动容器
docker-compose up -d --build
8.2 环境变量配置
步骤1:安装配置模块
npm install @nestjs/config
步骤2:创建.env文件
# .env.production
DATABASE_HOST=db
DATABASE_PORT=5432
DATABASE_USER=postgres
DATABASE_PASSWORD=yoursecurepassword
DATABASE_NAME=nestprod
JWT_SECRET=prod_secret_key
步骤3:更新app.module.ts
import { ConfigModule } from '@nestjs/config';@Module({imports: [ConfigModule.forRoot({envFilePath: `.env.${process.env.NODE_ENV}`,isGlobal: true,}),TypeOrmModule.forRootAsync({imports: [ConfigModule],useFactory: (config: ConfigService) => ({type: 'postgres',host: config.get('DATABASE_HOST'),port: config.get('DATABASE_PORT'),username: config.get('DATABASE_USER'),password: config.get('DATABASE_PASSWORD'),database: config.get('DATABASE_NAME'),entities: [__dirname + '/**/*.entity{.ts,.js}'],synchronize: false, // 生产环境必须关闭!}),inject: [ConfigService],}),],
})
8.3 性能优化策略
1. 启用压缩
// main.ts
import compression from 'compression';async function bootstrap() {const app = await NestFactory.create(AppModule);app.use(compression()); // 添加GZIP压缩
}
2. 集群模式(利用多核CPU)
// main.ts
import { clusterize } from '@nestjs/clusterize';async function bootstrap() {await clusterize({workers: process.env.NODE_ENV === 'production' ? 'max' : 1,bootstrap: async () => {const app = await NestFactory.create(AppModule);await app.listen(3000);},});
}
3. 缓存策略
// 安装缓存模块
npm install cache-manager @nestjs/cache-manager// app.module.ts
import { CacheModule } from '@nestjs/cache-manager';@Module({imports: [CacheModule.register({ttl: 60, // 缓存时间(秒)max: 1000, // 最大缓存数isGlobal: true,}),],
})
8.4 健康检查与监控
1. 添加终止信号处理
// main.ts
async function bootstrap() {const app = await NestFactory.create(AppModule);// 优雅关闭process.on('SIGTERM', () => {app.close().then(() => {console.log('Application closed');process.exit(0);});});
}
2. 集成健康检查
npm install @nestjs/terminus
// health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, HealthCheck } from '@nestjs/terminus';@Controller('health')
export class HealthController {constructor(private health: HealthCheckService) {}@Get()@HealthCheck()check() {return this.health.check([]);}
}
3. Prometheus监控(可选)
npm install @nestjs/metrics prom-client
// metrics.module.ts
import { Module } from '@nestjs/common';
import { PrometheusModule } from '@nestjs/metrics';@Module({imports: [PrometheusModule.register()],
})
export class MetricsModule {}
8.5 日志收集方案
1. 生产环境日志配置
// winston.config.prod.ts
export const winstonConfig = {transports: [new transports.File({ filename: 'logs/error.log', level: 'error',maxsize: 1024 * 1024 * 10, // 10MBmaxFiles: 7 }),new transports.File({filename: 'logs/combined.log',maxsize: 1024 * 1024 * 50, // 50MBmaxFiles: 14})]
};
2. 日志查询命令
# 查看实时日志
tail -f logs/combined.log# 根据时间过滤日志
grep '2023-08-20T10' logs/error.log
8.6 部署验证
# 检查容器状态
docker ps -a# 查看应用日志
docker logs <container_id># 测试健康检查端点
curl http://localhost:3000/health# 压力测试(安装wrk)
wrk -t12 -c400 -d30s http://localhost:3000/users
8.7 生产环境检查清单
- 禁用
synchronize: true
- 使用HTTPS加密通信
- 配置防火墙规则
- 设置自动备份策略
- 实施速率限制
- 定期安全扫描
- 监控CPU/内存使用
- 设置报警阈值
部署架构示意图
第九部分:微服务架构进阶
9.1 微服务核心概念
9.2 创建基础微服务
步骤1:安装依赖
npm install @nestjs/microservices
步骤2:创建用户服务(TCP通信)
// user-service/src/main.ts
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { UserModule } from './user.module';async function bootstrap() {const app = await NestFactory.createMicroservice<MicroserviceOptions>(UserModule,{transport: Transport.TCP,options: {host: 'localhost',port: 3001,},},);await app.listen();
}
bootstrap();
步骤3:定义用户服务接口
// shared/user.interface.ts
export interface User {id: number;name: string;
}export interface FindUserRequest {id: number;
}export interface CreateUserRequest {name: string;
}
9.3 实现gRPC通信
步骤1:定义proto文件
// proto/user.proto
syntax = "proto3";package user;service UserService {rpc FindUser (FindUserRequest) returns (User) {}rpc CreateUser (CreateUserRequest) returns (User) {}
}message FindUserRequest {int32 id = 1;
}message CreateUserRequest {string name = 1;
}message User {int32 id = 1;string name = 2;
}
步骤2:配置gRPC服务端
// user-service/src/main.ts
{transport: Transport.GRPC,options: {package: 'user',protoPath: join(__dirname, 'proto/user.proto'),url: 'localhost:50051',},
}
步骤3:实现gRPC客户端
// api-gateway/src/user.client.ts
@Client({transport: Transport.GRPC,options: {package: 'user',protoPath: join(__dirname, 'proto/user.proto'),url: 'localhost:50051',},
})
client: ClientGrpc;private userService: UserService;onModuleInit() {this.userService = this.client.getService<UserService>('UserService');
}@Get('users/:id')
async findUser(@Param('id') id: number) {return this.userService.findUser({ id });
}
9.4 RabbitMQ消息队列集成
步骤1:安装依赖
npm install @nestjs/microservices amqplib amqp-connection-manager
步骤2:配置消息生产者
// order-service/src/order.service.ts
@Injectable()
export class OrderService {constructor(@Inject('RABBITMQ_CLIENT') private readonly client: ClientProxy,) {}async createOrder(orderData: CreateOrderDto) {// 发送创建订单事件this.client.emit('order_created', orderData);return { status: 'processing' };}
}
步骤3:配置消息消费者
// payment-service/src/payment.consumer.ts
@Controller()
export class PaymentController {@EventPattern('order_created')async handleOrderCreated(data: CreateOrderDto) {// 处理支付逻辑console.log('Processing payment for order:', data);// 发送支付完成事件this.client.emit('payment_processed', {orderId: data.id,status: 'paid',});}
}
9.5 分布式事务处理(Saga模式示例)
// 订单创建Saga流程
async createOrderSaga(orderData) {try {// 1. 创建订单(Pending状态)const order = await this.orderService.createPendingOrder(orderData);// 2. 扣减库存await this.inventoryService.reserveStock(order.items);// 3. 处理支付const payment = await this.paymentService.processPayment(order);// 4. 确认订单await this.orderService.confirmOrder(order.id);return order;} catch (error) {// 补偿操作await this.orderService.cancelOrder(order.id);await this.inventoryService.releaseStock(order.items);throw error;}
}
9.6 微服务通信模式对比
模式 | 协议 | 适用场景 | 优点 | 缺点 |
---|---|---|---|---|
请求-响应 | HTTP/REST | 简单查询操作 | 简单易用 | 同步阻塞 |
gRPC | HTTP/2 | 高性能内部通信 | 高效、强类型 | 需要proto定义 |
消息队列 | AMQP | 异步任务处理 | 解耦、可靠 | 架构复杂度增加 |
事件驱动 | Pub/Sub | 实时数据更新 | 实时性高 | 消息顺序需处理 |
9.7 服务发现与负载均衡
使用Consul示例配置:
// 服务注册
import { ConsulService } from '@nestjs/consul';@Module({providers: [{provide: 'CONSUL_CLIENT',useFactory: () => {return new ConsulService({host: 'consul-server',port: '8500',});},},],
})
客户端负载均衡:
@Client({transport: Transport.TCP,options: {serviceName: 'user-service',loadBalancer: new RoundRobinLoadBalancer(),discoverer: new ConsulDiscoverer({host: 'consul-server',port: 8500,}),},
})
9.8 测试微服务通信
// 测试gRPC服务
describe('UserService (gRPC)', () => {let client: UserServiceClient;beforeAll(async () => {const packageDefinition = await loadPackageDefinition(loadSync(join(__dirname, 'proto/user.proto')));const proto = packageDefinition.user as any;client = new proto.UserService('localhost:50051',credentials.createInsecure());});it('should return user details', (done) => {client.findUser({ id: 1 }, (err, response) => {expect(response).toEqual({ id: 1, name: 'Test User' });done();});});
});
9.9 生产环境注意事项
-
服务监控:
- 使用Prometheus + Grafana监控服务指标
- 实现健康检查端点
-
容错处理:
// 断路器模式 @Get('users/:id') @UseFilters(CircuitBreakerFilter) async getUser(@Param('id') id: string) {return this.userService.findUser(id); }
-
日志追踪:
- 集成OpenTelemetry实现分布式追踪
- 使用唯一请求ID串联日志
-
安全策略:
- 服务间TLS加密通信
- JWT认证传递
- 速率限制
微服务架构核心组件
组件 | 作用 | 常用工具 |
---|---|---|
API网关 | 请求路由、聚合、认证 | Kong, NestJS网关 |
服务注册中心 | 服务发现与管理 | Consul, etcd, Zookeeper |
配置中心 | 统一管理配置 | Spring Cloud Config |
消息中间件 | 异步通信 | RabbitMQ, Kafka |
分布式追踪系统 | 请求链路追踪 | Jaeger, Zipkin |
第十部分:前端集成与全栈实践
10.1 Swagger API文档集成
步骤1:安装依赖
npm install @nestjs/swagger swagger-ui-express
步骤2:配置Swagger模块
// main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';async function bootstrap() {const app = await NestFactory.create(AppModule);const config = new DocumentBuilder().setTitle('NestJS API').setDescription('全栈开发接口文档').setVersion('1.0').addBearerAuth() // 启用JWT认证.build();const document = SwaggerModule.createDocument(app, config);SwaggerModule.setup('api-docs', app, document);await app.listen(3000);
}
步骤3:添加接口注释
// user.controller.ts
@ApiOperation({ summary: '获取用户列表' })
@ApiResponse({ status: 200, description: '返回用户数组' })
@Get()
getAllUsers() {return this.userService.findAll();
}// create-user.dto.ts
export class CreateUserDto {@ApiProperty({ example: '张三', description: '用户姓名' })@IsString()name: string;
}
访问文档:http://localhost:3000/api-docs
10.2 前端项目配置(以React为例)
步骤1:创建React应用
npx create-react-app nest-client
cd nest-client
步骤2:配置代理(解决跨域)
// package.json
{"proxy": "http://localhost:3000"
}
步骤3:安装axios
npm install axios
10.3 实现登录认证流程
// src/api/auth.js
import axios from 'axios';export const login = async (credentials) => {const response = await axios.post('/auth/login', credentials);localStorage.setItem('access_token', response.data.access_token);return response.data;
};export const getProfile = async () => {return axios.get('/users/profile', {headers: {Authorization: `Bearer ${localStorage.getItem('access_token')}`}});
};
10.4 前端路由保护(React示例)
// src/components/PrivateRoute.js
import { Navigate } from 'react-router-dom';const PrivateRoute = ({ children }) => {const isAuthenticated = !!localStorage.getItem('access_token');return isAuthenticated ? children : <Navigate to="/login" />;
};
10.5 全栈调试技巧
调试工具组合:
常见问题排查:
-
跨域问题:
// 后端启用CORS app.enableCors({origin: 'http://localhost:3001',methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',credentials: true, });
-
认证失效处理:
// 前端axios拦截器 axios.interceptors.response.use(response => response,error => {if (error.response.status === 401) {window.location = '/login';}return Promise.reject(error);} );
10.6 部署联调配置
生产环境配置示例:
# nginx配置
server {listen 80;server_name yourdomain.com;location /api {proxy_pass http://backend:3000;proxy_set_header Host $host;}location / {root /var/www/client;try_files $uri $uri/ /index.html;}
}
全栈开发关键点总结
层级 | 技术栈 | 关注重点 |
---|---|---|
前端 | React/Vue + Axios | 状态管理、路由守卫 |
网关层 | Nginx | 负载均衡、HTTPS配置 |
后端 | NestJS + TypeORM | 业务逻辑、数据库优化 |
基础设施 | Docker + PostgreSQL | 容器编排、备份策略 |
项目完整工作流
至此,Nest.js全栈开发系列教程已全部完成!建议学习者通过以下方式巩固知识:
- 开发一个完整博客系统(包含用户/文章/评论模块)
- 尝试部署到云平台(AWS/Aliyun)
- 参与开源Nest.js项目
- 探索NestJS官方高级特性(CLI插件、自定义装饰器等)