NestJS 框架详解
概述
NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架。它使用 TypeScript 构建,结合了面向对象编程、函数式编程和函数响应式编程的元素。
核心概念
什么是 NestJS
NestJS 是一个渐进式 Node.js 框架,用于构建高效、可靠和可扩展的服务器端应用程序。
核心特性:
- 基于 TypeScript
- 依赖注入系统
- 模块化架构
- 装饰器模式
- 开箱即用的支持
NestJS vs Express
特性 | Express | NestJS |
---|---|---|
架构 | 轻量级框架 | 企业级框架 |
类型安全 | 需要额外配置 | 原生支持 |
依赖注入 | 手动管理 | 内置支持 |
模块化 | 需要手动组织 | 内置模块系统 |
学习曲线 | 简单 | 中等 |
基础架构
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 框架,它提供了:
- 模块化架构 - 清晰的代码组织结构
- 依赖注入 - 松耦合的组件设计
- 装饰器模式 - 声明式的编程风格
- 开箱即用 - 丰富的内置功能
- TypeScript 支持 - 类型安全的开发体验
- 测试友好 - 完善的测试支持
- 可扩展性 - 支持微服务架构
通过合理使用 NestJS 的各种特性,可以构建出高质量、可维护的服务器端应用程序。