Nest 提供了一个开箱即用的应用程序架构,支持Typescript,和 Angular 架构十分相似,基本模式如下:
src
├── app.controller.ts
├── app.module.ts
└── main.ts
// main.ts
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
await app.listen(3000);
}
bootstrap();
然后根模块里导入各种功能模块。
1、控制器
控制器可理解为路由,专门负责处理各种HTTP请求(GET,POST等)
1.1、基本用法
使用 Controller 装饰器定义一个控制器(可接受字符串作为路径),配合 Get,Post 等装饰器形成路由:
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'test';
}
}
// 访问,output:test
curl -X GET localhost:3000/cats
Get等方法装饰器也可以接受参数:
import { Controller, Get } from '@nestjs/common';
@Controller('v1')
export class CatsController {
@Get('cats')
findAll(): string {
return 'test';
}
}
// 访问,output:test
curl -X GET localhost:3000/v1/cats
注意:这里的 findAll 的名称可以是任意的,从访问地址中也可看出。也不能用箭头函数(装饰器不支持),最后一点就是 controller 必须依附在 module 里:
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsController } from './cats/cats.controller';
@Module({
imports: [],
controllers: [AppController, CatsController],
providers: [AppService],
})
export class AppModule {}
1.2、路由相关
1、获取参数
import { Controller, Get, Req,Res } from '@nestjs/common';
import { Request,Response } from 'express';
@Controller('cats')
export class CatsController {
@Get()
findAll(@Req() request: Request,@Res() response:Response): string {
return 'This action returns all cats';
}
}
还有诸如: @Body,@Param,@Query,@Headers. @Put,@Delete,@Patch,@Options,@Head,@All
2、动态参数 & 路由注册顺序(路由参数都是可选的)
@Get(':id')
findOne(@Param() params): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
// GET /cats 请求不会命中第二个处理程序,因为所有路由参数都是可选的,应该调换位置
@Controller('cats')
export class CatsController {
@Get(':id')
findOne(@Param('id') id: string) {
return `This action returns a #${id} cat`;
}
@Get()
findAll() {
// This endpoint will never get called
// because the "/cats" request is going
// to be captured by the "/cats/:id" route handler
}
}
3、异步接口 Async / await
// Promise
@Get()
async findAll(): Promise<any[]> {
return [];
}
// Observable
@Get()
findAll(): Observable<any[]> {
return of([]);
}
4、请求负载 DTO
这里使用类而不是接口来定义 DTO,因为接口在TS编译时会被删除,而类不会。这有助于其他特性的使用(管道等特性能够在访问变量的元类型时提供更多的可能性)
export class CreateCatDto {
readonly name: string;
readonly age: number;
readonly breed: string;
}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return 'This action adds a new cat';
}
2、提供者
控制器在处理 HTTP 请求时,应该将更复杂的任务委托给提供者。service、helper等都可以称为提供者。
2.1、定义
使用 @Injectable 装饰器定义一个提供者
// cat.service.ts
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;
}
}
2.2、注册该提供者
和控制器一样,提供者也需要依附于module中才能使用
// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class ApplicationModule {}
2.3、调用该提供者
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
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<Cat[]> {
return this.catsService.findAll();
}
}
3、模块
Nest 用它来组织应用程序结构,每个 Nest 应用程序至少有一个模块,即根模块。然后根模块包含各种功能模块,从而组织起应用程序的结构
3.1、功能模块
CatsController 和 CatsService 属于同一个应用程序域。 应该考虑将它们移动到一个功能模块下,即 CatsModule:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatService] // 每个导入CatsModule的模块都可以访问 CatsService
})
export class CatsModule {}
// app.module.ts 中引入即可
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class ApplicationModule {}
3.2、全局模块
由于Nest 将提供者封装在模块范围,故想使用一些诸如 Helper 之类很通用的提供者时必须导入封装它们的模块。这有时候很不方便,故可以使用全局模块:
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
@Global 装饰器使模块成为全局作用域,然后 CatsService 提供者可以在任意地方注入
3.3、动态模块
动态模块扩展了模块元数据。当您需要动态注册组件时,这个实质特性非常有用。
// database.module.ts
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
@Module({
providers: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
// app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
exports: [DatabaseModule],
})
export class ApplicationModule {}
4、中间件
中间件是一个在路由处理器之前被调用的函数,Nest中有两种写法:
4.1、@Injectable 中间件
必须实现 NestMiddleware 类的 use 方法
// 1、定义
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: Function) {
console.log('Request...');
next();
}
}
//2、使用
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class ApplicationModule implements NestModule { // 必须实现 NestModule 的 configure 方法
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST }
)
.forRoutes(CatsController);
}
}
4.2、纯函数中间件
中间件没有任何依赖关系时,我们可以考虑使用函数式中间件
// 1、定义
export function logger(req, res, next) {
console.log(`Request...`);
next();
};
// 2、使用(同上)
4.3、多中间件 & 全局中间件
// 多个中间件
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
// 全局中间件
const app = await NestFactory.create(ApplicationModule);
app.use(logger);
await app.listen(3000);
5、异常过滤器、管道、守卫、拦截器
5.1、异常过滤器
Nest 内置的异常层会处理应用程序中抛出的所有异常,若未捕获到,则返回500错误,能保证用户将收到友好的响应。
@Get()
async findAll() {
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'This is a custom message',
}, 403);
}
也就是说,我们只需要负责抛出异常即可,异常种类很多,Nest 内置了一些:
UnauthorizedException、NotFoundException、RequestTimeoutException、NotImplementedException等等。Nest还提供了自定义异常过滤器以及设置过滤器的作用范围(方法范围的,控制器范围的,也可以是全局范围的)
5.2、管道
管道的两个作用:1、数据验证;2、转换数据
语法上,管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口的 transform 方法
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value;
}
}
Nest 内置了 ValidationPipe和ParseIntPipe,使用ValidationPipe 时,需要同时安装:
npm i --save class-validator class-transformer
使用步骤:
1、在 dto 文件中 加入验证装饰器
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
readonly name: string;
@IsInt()
readonly age: number;
@IsString()
readonly breed: string;
}
2、建立 ValidationPipe 类
import { PipeTransform, Pipe, 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, 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);
}
}
3、应用 ValidationPipe
// 方法级别范围
@Post()
@UsePipes(ValidationPipe)
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
// 全局级别范围
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
// 如果想做依赖注入
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_PIPE,
useClass: CustomGlobalPipe,
},
],
})
export class ApplicationModule {}
5.3、守卫
1、守卫有一个单独的责任。它们确定请求是否应该由路由处理程序处理,守卫在每个中间件之后执行的,但在拦截器和管道之前。
2、语法上,守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口的canActivate方法。
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 validateRequest(request);
}
}
3、角色认证
守卫和管道、异常过滤器一样,都可以是方法级别、全局级别,也可以做依赖注入
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<string[]>('roles', context.getClass()); // 通过反射获取元数据
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
const hasRole = () => user.roles.some((role) => roles.includes(role)); // 从user.roles中判断该用户是否拥有该权限,从而实现角色认证
return user && user.roles && hasRole();
}
}
5.4、拦截器
一条请求会经历:中间件=>守卫=>拦截器=>请求开始=====请求完成=>拦截器=>中间件,从拦截器作用位置可以看出拦截器的功能:
1、在函数执行之前/之后绑定额外的逻辑
2、转换从函数返回的结果
3、转换从函数抛出的异常
4、根据所选条件完全重写函数 (例如, 缓存目的)
语法上,拦截器是一个使用 @Injectable() 装饰器的类。 拦截器应该实现 NestInterceptor 接口的intercept方法。
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`)),
);
}
}
网友评论