当前位置: 首页 > article >正文

Nest.js全栈开发终极实践:TypeORM+微服务+Docker构建高可用企业级应用

文章目录

      • **第一部分:认识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
  1. 使用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
    
  2. 观察返回结果是否符合预期


关键概念总结

装饰器作用示例
@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 完整流程测试
  1. 通过Postman发送请求:

    GET http://localhost:3000/users
    POST http://localhost:3000/users
    Body: { "name": "Bob" }
    
  2. 观察响应结果:

    // 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 准备工作
  1. 安装所需依赖:

    npm install @nestjs/typeorm typeorm pg
    
    • @nestjs/typeorm: Nest的TypeORM集成包
    • typeorm: ORM框架
    • pg: PostgreSQL驱动
  2. 确保已安装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 }) // 字符串列,长度限制50
  name: 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)
  1. 安装验证库:

    npm install class-validator class-transformer
    
  2. 创建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;
    }
    
  3. 更新控制器:

    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 认证方案概述
1. 提交凭证
2. 验证成功
3. 返回Token
4. 携带Token
5. 验证Token
客户端
登录接口
签发JWT
受保护资源
返回数据
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 安全增强建议
  1. 使用环境变量存储密钥
  2. 实现密码强度验证
  3. 添加刷新令牌机制
  4. 设置合理的令牌有效期
  5. 记录认证日志

认证核心组件

组件作用关键方法/装饰器
JwtModuleJWT配置模块register()
JwtStrategy验证请求携带的JWTvalidate()
AuthGuard路由守卫@UseGuards(AuthGuard())
bcrypt密码哈希处理hash(), compare()
@nestjs/jwtJWT签发与验证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日志
  1. 安装依赖:
npm install winston @types/winston
  1. 创建日志配置文件:
// 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' }),
  ],
};
  1. 创建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示例)
  1. 安装依赖:
npm install @sentry/node @sentry/tracing
  1. 配置Sentry:
// sentry.config.ts
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: 'your-dsn-here',
  tracesSampleRate: 1.0,
});
  1. 集成到异常过滤器:
// 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 最佳实践建议
  1. 使用不同日志级别:

    • verbose: 详细调试信息
    • debug: 调试信息
    • log: 常规日志
    • warn: 警告信息
    • error: 错误信息
  2. 日志文件管理:

    • 使用logrotate进行日志轮换
    • 敏感信息过滤
    • 按日期分割日志文件
  3. 生产环境注意事项:

    • 禁用synchronize选项
    • 设置适当的日志级别
    • 使用集中式日志系统(ELK/Splunk)

异常处理核心机制

组件作用关键方法/装饰器
ExceptionFilter捕获并处理异常@Catch()
HttpException预定义的HTTP异常new BadRequestException()
LoggerService日志接口log(), error()
Middleware记录请求信息implements NestMiddleware

第七部分:单元测试与E2E测试

7.1 测试金字塔模型
70% 20% 10% 测试类型分布 单元测试 集成测试 E2E测试
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 测试最佳实践
  1. 测试命名规范

    describe('场景描述', () => {
      it('应完成特定行为', () => {
        // 测试逻辑
      });
    });
    
  2. 测试三部曲

    • 准备(Arrange):设置测试数据和模拟
    • 执行(Act):调用被测试方法
    • 断言(Assert):验证结果
  3. 测试隔离原则

    • 每个测试用例独立运行
    • 使用beforeEach/afterEach重置状态
    • 避免测试间的依赖关系
  4. 测试数据库策略

    // 使用测试专用数据库
    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=production
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: yoursecurepassword
      POSTGRES_DB: nestprod
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "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, // 10MB
      maxFiles: 7 
    }),
    new transports.File({
      filename: 'logs/combined.log',
      maxsize: 1024 * 1024 * 50, // 50MB
      maxFiles: 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 生产环境检查清单
  1. 禁用synchronize: true
  2. 使用HTTPS加密通信
  3. 配置防火墙规则
  4. 设置自动备份策略
  5. 实施速率限制
  6. 定期安全扫描
  7. 监控CPU/内存使用
  8. 设置报警阈值

部署架构示意图

客户端
负载均衡器
容器实例1
容器实例2
PostgreSQL集群
备份存储
日志收集系统
监控仪表盘

第九部分:微服务架构进阶

9.1 微服务核心概念
客户端
API网关
用户服务
订单服务
支付服务
用户数据库
订单数据库
支付数据库
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简单查询操作简单易用同步阻塞
gRPCHTTP/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 生产环境注意事项
  1. 服务监控

    • 使用Prometheus + Grafana监控服务指标
    • 实现健康检查端点
  2. 容错处理

    // 断路器模式
    @Get('users/:id')
    @UseFilters(CircuitBreakerFilter)
    async getUser(@Param('id') id: string) {
      return this.userService.findUser(id);
    }
    
  3. 日志追踪

    • 集成OpenTelemetry实现分布式追踪
    • 使用唯一请求ID串联日志
  4. 安全策略

    • 服务间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 全栈调试技巧

调试工具组合

45% 30% 20% 5% 调试工具使用率 浏览器开发者工具 Postman VS Code调试器 其他

常见问题排查

  1. 跨域问题

    // 后端启用CORS
    app.enableCors({
      origin: 'http://localhost:3001',
      methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
      credentials: true,
    });
    
  2. 认证失效处理

    // 前端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容器编排、备份策略

项目完整工作流

用户 前端 后端 数据库 发起请求 API调用 查询数据 返回数据 JSON响应 渲染界面 用户 前端 后端 数据库

至此,Nest.js全栈开发系列教程已全部完成!建议学习者通过以下方式巩固知识:

  1. 开发一个完整博客系统(包含用户/文章/评论模块)
  2. 尝试部署到云平台(AWS/Aliyun)
  3. 参与开源Nest.js项目
  4. 探索NestJS官方高级特性(CLI插件、自定义装饰器等)


http://www.kler.cn/a/580661.html

相关文章:

  • LeetCode --- 439周赛
  • HarmonyOS Next 中的状态管理
  • 推理LLMs发展综述:从GPT到DeepSeek
  • 手机号实名认证接口:数字时代的安全与便捷保障
  • 【IPFS应用开发】IPFS播放器-上传助手
  • 深度学习实战车辆目标跟踪与计数
  • 【网络协议详解】——MPLS LDP技术(学习笔记)
  • MySQL数据库操作
  • HarmonyOS NEXT开发实战:DevEco AI辅助编程工具(CodeGenie)的使用
  • QT系列教程(14) QT 按键事件
  • 【病毒分析】熊猫烧香病毒分析及其查杀修复
  • 【自学笔记】Rust语言基础知识点总览-持续更新
  • 两会聚焦科技金融创新,赛逸展2025成重要实践平台
  • 关于前后端整合和打包成exe文件的个人的总结和思考
  • MyBatis-Plus 与 Spring Boot 的最佳实践
  • 51c大模型~合集10
  • 小白学Agent技术[4](Agent设计模式)
  • Electron使用WebAssembly实现CRC-32 常用标准校验
  • Hcaptcha验证码自动识别方案详解
  • 台风信息查询API:数据赋能,守护安全