Skip to content

NestJS 框架详解

概述

NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架。它使用 TypeScript 构建,结合了面向对象编程、函数式编程和函数响应式编程的元素。

核心概念

什么是 NestJS

NestJS 是一个渐进式 Node.js 框架,用于构建高效、可靠和可扩展的服务器端应用程序。

核心特性:

  • 基于 TypeScript
  • 依赖注入系统
  • 模块化架构
  • 装饰器模式
  • 开箱即用的支持

NestJS vs Express

特性ExpressNestJS
架构轻量级框架企业级框架
类型安全需要额外配置原生支持
依赖注入手动管理内置支持
模块化需要手动组织内置模块系统
学习曲线简单中等

基础架构

1. 模块系统

模块是 NestJS 应用程序的基本构建块。

typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

2. 控制器

控制器负责处理传入的请求并返回响应。

typescript
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

3. 服务

服务包含业务逻辑,可以被控制器或其他服务注入使用。

typescript
// app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

依赖注入

基本概念

依赖注入是 NestJS 的核心特性,它允许类从外部接收依赖项。

typescript
// 错误示例:直接实例化
class UserController {
  constructor() {
    this.userService = new UserService(); // 紧密耦合
  }
}

// 正确示例:依赖注入
@Controller('users')
class UserController {
  constructor(private readonly userService: UserService) {} // 松耦合
}

提供者

提供者是 NestJS 依赖注入系统的基本概念。

typescript
// user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  private users = [];

  async createUser(userData: any) {
    const user = {
      id: Date.now(),
      ...userData,
      createdAt: new Date()
    };
    this.users.push(user);
    return user;
  }

  async findAll() {
    return this.users;
  }
}

控制器详解

路由装饰器

typescript
import { Controller, Get, Post, Put, Delete, Param, Body, Query } from '@nestjs/common';

@Controller('users')
export class UserController {
  
  // GET /users
  @Get()
  findAll() {
    return this.userService.findAll();
  }

  // GET /users/123
  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(id);
  }

  // POST /users
  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }

  // PUT /users/123
  @Put(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.userService.update(id, updateUserDto);
  }

  // DELETE /users/123
  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.userService.remove(id);
  }
}

参数获取

typescript
@Controller('users')
export class UserController {
  
  // 路径参数
  @Get(':id')
  findOne(@Param('id') id: string) {
    return `User ${id}`;
  }

  // 查询参数
  @Get()
  findAll(@Query('page') page: number, @Query('limit') limit: number) {
    return `Page ${page}, Limit ${limit}`;
  }

  // 请求体
  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return createUserDto;
  }

  // 请求头
  @Get()
  findAll(@Headers('authorization') auth: string) {
    return `Auth: ${auth}`;
  }
}

数据传输对象 (DTO)

创建 DTO

typescript
// create-user.dto.ts
import { IsString, IsEmail, MinLength, IsOptional } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(3)
  username: string;

  @IsEmail()
  email: string;

  @IsString()
  @MinLength(6)
  password: string;

  @IsOptional()
  @IsString()
  fullName?: string;
}

验证管道

typescript
// main.ts
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }));
  
  await app.listen(3000);
}

中间件

创建中间件

typescript
// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
    next();
  }
}

应用中间件

typescript
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*');
  }
}

守卫

创建守卫

typescript
// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization;
    
    if (!token) {
      return false;
    }
    
    // 验证 token 的逻辑
    return this.validateToken(token);
  }

  private validateToken(token: string): boolean {
    // 实现 token 验证逻辑
    return token.startsWith('Bearer ');
  }
}

使用守卫

typescript
// 全局守卫
// main.ts
app.useGlobalGuards(new AuthGuard());

// 控制器级别
@Controller('users')
@UseGuards(AuthGuard)
export class UserController {}

// 方法级别
@Get('profile')
@UseGuards(AuthGuard)
getProfile() {
  return 'Protected profile';
}

拦截器

创建拦截器

typescript
// logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap, map } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const method = request.method;
    const url = request.url;
    const now = Date.now();

    return next.handle().pipe(
      tap(() => {
        console.log(`${method} ${url} ${Date.now() - now}ms`);
      }),
      map(data => ({
        success: true,
        data,
        timestamp: new Date().toISOString()
      }))
    );
  }
}

使用拦截器

typescript
// 全局拦截器
app.useGlobalInterceptors(new LoggingInterceptor());

// 控制器级别
@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UserController {}

// 方法级别
@Get()
@UseInterceptors(LoggingInterceptor)
findAll() {
  return this.userService.findAll();
}

异常过滤器

创建异常过滤器

typescript
// 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,
    });
  }
}

使用异常过滤器

typescript
// 全局异常过滤器
app.useGlobalFilters(new HttpExceptionFilter());

// 控制器级别
@Controller('users')
@UseFilters(HttpExceptionFilter)
export class UserController {}

// 方法级别
@Get(':id')
@UseFilters(HttpExceptionFilter)
findOne(@Param('id') id: string) {
  throw new HttpException('User not found', 404);
}

管道

内置管道

typescript
import { ParseIntPipe, ParseBoolPipe, DefaultValuePipe } from '@nestjs/common';

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
  return this.userService.findOne(id);
}

@Get()
findAll(
  @Query('active', new DefaultValuePipe(true), ParseBoolPipe) active: boolean,
) {
  return this.userService.findAll(active);
}

自定义管道

typescript
// validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (!value) {
      throw new BadRequestException('Value is required');
    }
    return value;
  }
}

数据库集成

TypeORM 配置

typescript
// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'nest_demo',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true, // 仅开发环境使用
    }),
  ],
})
export class AppModule {}

实体定义

typescript
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 100 })
  username: string;

  @Column({ unique: true })
  email: string;

  @Column({ select: false })
  password: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

服务中使用 Repository

typescript
// 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 create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.userRepository.create(createUserDto);
    return await this.userRepository.save(user);
  }

  async findAll(): Promise<User[]> {
    return await this.userRepository.find();
  }

  async findOne(id: number): Promise<User> {
    return await this.userRepository.findOne({ where: { id } });
  }

  async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
    await this.userRepository.update(id, updateUserDto);
    return await this.findOne(id);
  }

  async remove(id: number): Promise<void> {
    await this.userRepository.delete(id);
  }
}

认证与授权

JWT 认证

typescript
// auth.module.ts
import { JwtModule } from '@nestjs/jwt';

@Module({
  imports: [
    JwtModule.register({
      secret: 'your-secret-key',
      signOptions: { expiresIn: '1h' },
    }),
  ],
  providers: [AuthService],
  exports: [AuthService],
})
export class AuthModule {}

认证服务

typescript
// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../user/user.service';
import * as bcrypt from 'bcrypt';

@Injectable()
export class AuthService {
  constructor(
    private userService: UserService,
    private jwtService: JwtService,
  ) {}

  async validateUser(username: string, password: string): Promise<any> {
    const user = await this.userService.findByUsername(username);
    if (user && await bcrypt.compare(password, user.password)) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }

  async login(user: any) {
    const payload = { username: user.username, sub: user.id };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}

JWT 策略

typescript
// jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';

@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, username: payload.username };
  }
}

微服务

创建微服务

typescript
// main.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: 'localhost',
      port: 3001,
    },
  });
  await app.listen();
}
bootstrap();

客户端调用

typescript
// app.module.ts
import { ClientsModule, Transport } from '@nestjs/microservices';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'USER_SERVICE',
        transport: Transport.TCP,
        options: {
          host: 'localhost',
          port: 3001,
        },
      },
    ]),
  ],
})
export class AppModule {}

消息模式

typescript
// 服务端
@MessagePattern('getUser')
getUser(@Payload() id: number) {
  return this.userService.findOne(id);
}

// 客户端
@Inject('USER_SERVICE')
private client: ClientProxy;

async getUser(id: number) {
  return this.client.send('getUser', id).toPromise();
}

测试

单元测试

typescript
// user.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './user.entity';

describe('UserService', () => {
  let service: UserService;
  let mockRepository: any;

  beforeEach(async () => {
    mockRepository = {
      find: jest.fn(),
      findOne: jest.fn(),
      save: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: getRepositoryToken(User),
          useValue: mockRepository,
        },
      ],
    }).compile();

    service = module.get<UserService>(UserService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('should return all users', async () => {
    const users = [{ id: 1, username: 'test' }];
    mockRepository.find.mockResolvedValue(users);
    
    expect(await service.findAll()).toBe(users);
  });
});

E2E 测试

typescript
// 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;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });

  afterAll(async () => {
    await app.close();
  });
});

部署

Docker 配置

dockerfile
# Dockerfile
FROM node:16-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

EXPOSE 3000

CMD ["node", "dist/main.js"]

Docker Compose

yaml
# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=mysql
    depends_on:
      - mysql

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: nest_demo
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:

PM2 配置

javascript
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'nest-app',
    script: 'dist/main.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'development',
    },
    env_production: {
      NODE_ENV: 'production',
    },
  }],
};

最佳实践

1. 项目结构

src/
├── common/                 # 通用模块
│   ├── decorators/        # 自定义装饰器
│   ├── filters/           # 异常过滤器
│   ├── guards/            # 守卫
│   ├── interceptors/      # 拦截器
│   └── pipes/             # 管道
├── config/                # 配置
├── modules/               # 业务模块
│   ├── auth/             # 认证模块
│   ├── user/             # 用户模块
│   └── product/          # 产品模块
├── app.module.ts
└── main.ts

2. 环境配置

typescript
// config/configuration.ts
export default () => ({
  port: parseInt(process.env.PORT, 10) || 3000,
  database: {
    host: process.env.DB_HOST,
    port: parseInt(process.env.DB_PORT, 10) || 3306,
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_DATABASE,
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '1h',
  },
});

3. 错误处理

typescript
// 自定义异常
export class UserNotFoundException extends HttpException {
  constructor() {
    super('User not found', HttpStatus.NOT_FOUND);
  }
}

// 全局异常过滤器
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: exception instanceof HttpException
        ? exception.message
        : 'Internal server error',
    });
  }
}

4. 日志记录

typescript
// 使用 Winston 日志
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';

const app = await NestFactory.create(AppModule, {
  logger: WinstonModule.createLogger({
    transports: [
      new winston.transports.Console({
        format: winston.format.combine(
          winston.format.timestamp(),
          winston.format.colorize(),
          winston.format.simple(),
        ),
      }),
      new winston.transports.File({
        filename: 'logs/error.log',
        level: 'error',
      }),
    ],
  }),
});

总结

NestJS 是一个强大的企业级 Node.js 框架,它提供了:

  1. 模块化架构 - 清晰的代码组织结构
  2. 依赖注入 - 松耦合的组件设计
  3. 装饰器模式 - 声明式的编程风格
  4. 开箱即用 - 丰富的内置功能
  5. TypeScript 支持 - 类型安全的开发体验
  6. 测试友好 - 完善的测试支持
  7. 可扩展性 - 支持微服务架构

通过合理使用 NestJS 的各种特性,可以构建出高质量、可维护的服务器端应用程序。