NestJS框架
NestJS是一个用于构建高效、可扩展的Node.js服务器端应用程序的框架。它结合了面向对象编程(OOP)、函数式编程(FP)和函数响应式编程(FRP)的元素,并以TypeScript为主要开发语言。NestJS借鉴了Angular的设计理念,提供了一个开箱即用的应用架构。
NestJS核心概念
模块化架构
NestJS使用模块组织应用代码:
typescript
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
export class CatsModule {}
// app.module.ts
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
控制器
控制器负责处理传入的请求并返回响应:
typescript
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
return 'This action adds a new cat';
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
@Get(':id')
async findOne(@Param('id') id: string): Promise<Cat> {
return this.catsService.findOne(id);
}
}
提供者
提供者是NestJS的基础概念,可以被注入依赖:
typescript
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
findOne(id: string): Cat {
return this.cats.find(cat => cat.id === id);
}
}
中间件
中间件是在路由处理程序之前调用的函数:
typescript
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('Request...');
next();
}
}
// 在模块中应用中间件
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'cats', method: RequestMethod.GET });
}
}
异常过滤器
处理应用程序中的异常:
typescript
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,
});
}
}
// 使用异常过滤器
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
管道
管道用于数据转换和验证:
typescript
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
// 使用管道
@Post()
async create(
@Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
this.catsService.create(createCatDto);
}
守卫
守卫负责确定请求是否应该由路由处理程序处理:
typescript
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();
return this.validateRequest(request);
}
private validateRequest(request: any): boolean {
// 在这里实现授权逻辑
return true;
}
}
// 使用守卫
@Controller('cats')
@UseGuards(AuthGuard)
export class CatsController {}
拦截器
拦截器可以在方法执行前后添加额外的逻辑:
typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
// 使用拦截器
@UseInterceptors(LoggingInterceptor)
@Get()
findAll() {
return this.catsService.findAll();
}
高级功能
依赖注入
NestJS内置了IoC容器,实现了依赖注入:
typescript
// 自定义提供者
import { Connection } from './connection';
const connectionProvider = {
provide: 'CONNECTION',
useFactory: () => {
const connection = new Connection();
connection.connect();
return connection;
},
};
@Module({
providers: [connectionProvider],
})
export class AppModule {}
// 注入自定义提供者
@Injectable()
export class CatsRepository {
constructor(@Inject('CONNECTION') private connection: Connection) {}
}
自定义装饰器
创建自定义装饰器简化代码:
typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
// 使用自定义装饰器
@Get()
async findOne(@User('firstName') firstName: string) {
console.log(`Hello ${firstName}`);
}
微服务
NestJS支持多种微服务传输方式:
typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: { port: 3001 },
},
);
await app.listen();
}
bootstrap();
// 控制器
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
sum(@Payload() data: number[]): number {
return (data || []).reduce((a, b) => a + b, 0);
}
}
WebSockets
NestJS支持实时通信:
typescript
import { SubscribeMessage, WebSocketGateway, OnGatewayInit, WebSocketServer } from '@nestjs/websockets';
import { Logger } from '@nestjs/common';
import { Server, Socket } from 'socket.io';
@WebSocketGateway()
export class ChatGateway implements OnGatewayInit {
@WebSocketServer() server: Server;
private logger: Logger = new Logger('ChatGateway');
afterInit() {
this.logger.log('WebSocket Gateway initialized');
}
@SubscribeMessage('message')
handleMessage(client: Socket, payload: string): void {
this.server.emit('message', payload);
}
}
数据库集成
TypeORM
NestJS与TypeORM集成用于数据库操作:
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [User],
synchronize: true,
}),
],
})
export class AppModule {}
// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
}
// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User> {
return this.usersRepository.findOne({ where: { id } });
}
async create(user: Partial<User>): Promise<User> {
const newUser = this.usersRepository.create(user);
return this.usersRepository.save(newUser);
}
async remove(id: number): Promise<void> {
await this.usersRepository.delete(id);
}
}
Mongoose
NestJS与Mongoose集成用于MongoDB:
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/nest'),
],
})
export class AppModule {}
// cat.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type CatDocument = Cat & Document;
@Schema()
export class Cat {
@Prop({ required: true })
name: string;
@Prop()
age: number;
@Prop()
breed: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
// cats.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { Cat, CatSchema } from './schemas/cat.schema';
@Module({
imports: [
MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }]),
],
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
// cats.service.ts
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';
@Injectable()
export class CatsService {
constructor(@InjectModel(Cat.name) private catModel: Model<CatDocument>) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return createdCat.save();
}
async findAll(): Promise<Cat[]> {
return this.catModel.find().exec();
}
}
认证与授权
JWT认证
typescript
// auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { JwtStrategy } from './strategies/jwt.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: 'secretKey',
signOptions: { expiresIn: '60m' },
}),
],
providers: [AuthService, JwtStrategy],
controllers: [AuthController],
})
export class AuthModule {}
// 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: 'secretKey',
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.usersService.findOne(username);
if (user && user.password === pass) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { username: user.username, sub: user.userId };
return {
access_token: this.jwtService.sign(payload),
};
}
}
// 在控制器中使用JWT守卫
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@Controller('profile')
export class ProfileController {
@UseGuards(JwtAuthGuard)
@Get()
getProfile() {
return { message: 'This is protected data' };
}
}
测试
单元测试
typescript
// cats.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CatsService } from './cats.service';
describe('CatsService', () => {
let service: CatsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CatsService],
}).compile();
service = module.get<CatsService>(CatsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return an array of cats', () => {
const result = service.findAll();
expect(Array.isArray(result)).toBe(true);
});
});
端到端测试
typescript
// cats.e2e-spec.ts
import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { CatsModule } from '../../src/cats/cats.module';
import { CatsService } from '../../src/cats/cats.service';
import { INestApplication } from '@nestjs/common';
describe('Cats', () => {
let app: INestApplication;
let catsService = { findAll: () => ['test'] };
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [CatsModule],
})
.overrideProvider(CatsService)
.useValue(catsService)
.compile();
app = moduleRef.createNestApplication();
await app.init();
});
it(`/GET cats`, () => {
return request(app.getHttpServer())
.get('/cats')
.expect(200)
.expect(catsService.findAll());
});
afterAll(async () => {
await app.close();
});
});
部署
Docker部署
dockerfile
# Dockerfile
FROM node:14-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY .. .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/main"]
yaml
# docker-compose.yml
version: '3'
services:
api:
build: .
ports:
- '3000:3000'
environment:
- NODE_ENV=production
- DATABASE_HOST=db
depends_on:
- db
db:
image: postgres
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: nest
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
生产环境配置
NestJS项目通常使用@nestjs/config
管理配置:
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: `.env.${process.env.NODE_ENV}`,
}),
],
})
export class AppModule {}
// 使用配置
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AppService {
constructor(private configService: ConfigService) {}
getHello(): string {
const dbHost = this.configService.get<string>('DATABASE_HOST');
return `Hello World! DB Host: ${dbHost}`;
}
}
NestJS最佳实践
- 结构化应用:按功能或领域组织模块
- 分离关注点:控制器处理HTTP请求,服务处理业务逻辑
- 使用DTO:为数据传输定义明确的接口
- 异常处理:使用内置的异常过滤器系统
- 使用验证管道:验证输入数据
- 文档化API:使用Swagger/OpenAPI
- 利用依赖注入:更好的代码组织和测试
- 编写测试:为服务和控制器编写单元测试
- 环境配置:使用ConfigModule管理不同环境的配置
NestJS通过结合现代JavaScript概念、TypeScript类型系统和面向对象设计原则,提供了一个强大的框架来构建企业级应用程序。它的模块化、可测试性和可扩展性使其成为构建复杂后端系统的理想选择。