NestJs 是一款用于构建高效且可伸缩 Web 应用程序的渐进式 Node.js 框架。看下官方给的简介,NestJs模块化的体系结构允许开发者使用任何其他的库,从而提供灵活性;为 Nodejs 提供一个适应性强大的生态系统;利用最新的js特性,为nodejs 提供更加方便的设计模式和成熟的解决方案。
NestJs 主要有 8 个组件(Controller 控制器、Component 组件、Module 模块、Middlewares 中间件、Exception Filters 异常过滤器、Pipes 管道、Guards 守卫、Interceptors 拦截器),主要通过 Controller、Component、Module 三个最核心的组件构成。
import {
Body,
Controller,
Get,
Param,
Post,
} 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 readonly catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise {
return this.catsService.findAll();
}
@Get(':id')
findOne(
@Param('id', new ParseIntPipe())
id,
) {
// logic
}
}
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;
}
}
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
mport { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('/cats’); // ‘/cats’ 变更为 CatsController 则是对控制器进行中间件配置
}
}
与此同时,NestJs 同样也支持函数式的中间件、异步中间件。
// 异步中间件
import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
async resolve(name: string): Promise {
await someAsyncJob();
return async (req, res, next) => {
await someAsyncJob();
console.log(`[${name}] Request...`); // [ApplicationModule] Request...
next();
};
}
}
// 函数式中间件
export function logger(req, res, next) {
console.log(`Request...`);
next();
};
全局中间件配置,需要在 NestFactory 创建的实例上进行配置,这里配置方法和 express 类似
const app = await NestFactory.create(ApplicationModule);
app.use(logger);
await app.listen(3000);
// 设置异常过滤器
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
// 捕获异常,并给异常过滤器处理
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { HttpException } from '@nestjs/common';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
response
.status(status)
.json({
statusCode: exception.getStatus(),
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
NestJs 支持开发者自定义异常、设置异常的处理顺序、设置全局异常处理等。
import { HttpExceptionFilter } from './exceptions/http-exception.filter';
import { AnyExceptionFilter } from './exceptions/any-exception.filter’;
const app = await NestFactory.create(ApplicationModule);
// 异常捕获顺序根据 useGlobalFilters 顺序决定
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalFilters(new AnyExceptionFilter());
await app.listen(3000);
// create-cat.dto.ts
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
readonly name: string;
@IsInt()
readonly age: number;
@IsString()
readonly breed: string;
}
// validation.pipe.ts
import { PipeTransform, Pipe, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform {
async transform(value, metadata: ArgumentMetadata) {
const { metatype } = metadata;
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): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.find((type) => metatype === type);
}
}
// cats.controler.ts
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
同样地,管道也可以设置对全局路由进行处理(@useGlobalPipes)。
注:守卫是在每个中间件之后执行的, 但在管道之前。
守卫是一个使用 @Injectable() 装饰器的类。 守卫需要实现 canActivate 方法。
// 角色装饰器 roles.decorator.ts
import { ReflectMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => ReflectMetadata('roles', roles);
// 用角色装饰器修饰路由 cats.controller.ts
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
// 基于权限判断的守卫: roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
const hasRole = () => user.roles.some((role) => roles.includes(role));
return user && user.roles && hasRole();
}
}
从这里可以看出,首先拿到执行上下文的关键就是这里的反射器,通过反射拿到待执行处理的路由访问权限信息,判断该请求是否具备访问改路由的权限,从而很好的将权限校验的逻辑从业务逻辑中分离出来。
在函数执行之前/之后绑定额外的逻辑 转换从函数返回的结果 转换从函数抛出的异常 根据所选条件完全重写函数 (例如, 缓存目的)
NestJs 的拦截器借助于 rxjs 强大功能来实现。
// 请求/响应日志记录 logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
call$: Observable,
): Observable {
console.log('Before...');
const now = Date.now();
return call$.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
// UseInterceptors来用装饰控制器
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
├── jest.json
├── node_modules
├── package-lock.json
├── package.json
├── src
│ ├── app.module.ts // 模块聚合
│ ├── cats // 业务模块
│ │ ├── cats.controller.spec.ts
│ │ ├── cats.controller.ts
│ │ ├── cats.module.ts
│ │ ├── cats.service.ts
│ │ ├── dto
│ │ └── interfaces
│ ├── common
│ │ ├── decorators // 自定义装饰器等等
│ │ ├── filters // 过滤器:异常处理等等
│ │ ├── guards // 守卫:权限校验等等
│ │ ├── interceptors // 拦截器:AOP处理
│ │ ├── middlewares // 中间件处理
│ │ └── pipes // 管道处理,DTO数据处理等等
│ └── main.ts // 主入口
├── tsconfig.json
├── tslint.json
└── yarn.lock