阅读 109

Nest+GrqphQL+Prisma+React全栈开发(三)-Nest篇

在Nest+GrqphQL+Prisma+React全栈开发这个系列中,我将会带领着大家从零到一,一步一步简单学会使用这些技术栈,并实际开发一个实战应用。

这篇是第三篇文章,Nest篇。

  • 第一篇:GraphQL

  • 第二篇:Prisma

一、简介

Nestjs是一个高效的nodejs服务端应用开发框架,扩展性高,性能好,优点还包括原生支持TS,以及底层构建在强大的Http框架上,你可以自己选择是使用express或者fastify,以及支持模块化,微服务,装饰器语法,依赖注入(将自己的依赖的实例化交给外部容器控制)等等特性。

可以说非常强大好用。

二、结构

Nest主要有以下这几个部分组成:

  • 控制器

  • 提供者

  • 模块

  • 中间件

  • 异常过滤器

  • 管道

  • 守卫

  • 拦截器

  • 装饰器

下面我们一一来简单的介绍下这些模块:

(一)控制器

控制器负责处理传入的请求和向客户端返回响应,一个控制器包含多个路由。

/* cats.controller.ts */ import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common'; import { Response } from 'express'; @Controller('cats') export class CatsController {   @Post()   create(@Res() res: Response) {     res.status(HttpStatus.CREATED).send();   }   @Get()   findAll(@Res() res: Response) {     res.status(HttpStatus.OK).json([]);   } } 复制代码

(二)提供者

用 @Injectable() 装饰器注释的类,例如nest中的service, repositoryfactoryhelper等都可以被视为提供者。Nest中的依赖管理是通过依赖注入这个设计模式实现的,一般是通过控制器的构造函数注入的:

constructor(private readonly catsService: CatsService) {} 复制代码

也可以使用@Inject()基于属性注入:

import { Injectable, Inject } from '@nestjs/common'; @Injectable() export class HttpService<T> {   @Inject('HTTP_OPTIONS')   private readonly httpClient: T; } 复制代码

(三)模块

模块是具有 @Module() 装饰器的类。 @Module() 装饰器提供了元数据,Nest 用它来组织应用程序结构。每个 Nest 应用程序至少有一个模块,即根模块。根模块是 Nest 开始安排应用程序树的地方。

创建一个你自己的功能模块:

import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({   controllers: [CatsController],   providers: [CatsService], }) export class CatsModule {} 复制代码

然后在app.module.ts中导入:

import { Module } from '@nestjs/common'; import { CatsModule } from './cats/cats.module'; @Module({   imports: [CatsModule], }) export class ApplicationModule {} 复制代码

如果想要模块共享的话,只需在exports中导出:

exports: [CatsService] 复制代码

如果你想在任何地方导入相同的模块,可以设置为全局模块:

@Global() @Module({   ... }) export class CatsModule {} 复制代码

Nest还支持动态模块,这些模块可以动态注册和配置提供程序,使用forRoot进行配置:

@Module({  ... }) export class DatabaseModule {   static forRoot(entities = [], options?): DynamicModule {     const providers = createDatabaseProviders(options, entities);     return {       module: DatabaseModule,       providers: providers,       exports: providers,     };   } } 复制代码

(四)中间件

中间件是在路由处理程序 之前 调用的函数。Nest 中间件实际上等价于 express 中间件。

中间件有以下作用:

  • 对请求和响应对象进行更改。

  • 结束请求-响应周期。

  • 调用堆栈中的下一个中间件函数。

  • 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。

我们可以在函数中或在具有 @Injectable() 装饰器的类中实现自定义 Nest中间件,只需要实现 NestMiddleware 接口即可。

@Injectable() export class LoggerMiddleware implements NestMiddleware {   use(req: Request, res: Response, next: NextFunction) {     console.log('Request...');     next();   } } 复制代码

不过中间件的使用需要注意以下几点:

  • 中间件不能在 @Module() 装饰器中列出

  • 必须使用模块类的 configure() 方法来设置它们

  • 包含中间件的模块必须实现 NestModule 接口

@Module({   ... }) export class AppModule implements NestModule {   configure(consumer: MiddlewareConsumer) {     consumer       .apply(LoggerMiddleware)       .forRoutes('cats');   } } 复制代码

如果是多个中间件,可以这样写:

consumer.apply(cors(), helmet(), logger).forRoutes(CatsController); 复制代码

可以直接在app上直接注册全局中间件:

const app = await NestFactory.create(AppModule); app.use(logger); await app.listen(3000); 复制代码

(五)异常过滤器

处理整个应用程序中的所有抛出的异常。Nest提供了一系列继承自核心异常 HttpException 的可用异常:

  • BadRequestException

  • UnauthorizedException

  • NotFoundException

  • ForbiddenException

  • NotAcceptableException

  • RequestTimeoutException

  • ConflictException

  • GoneException

  • PayloadTooLargeException

  • UnsupportedMediaTypeException

  • UnprocessableException

  • InternalServerErrorException

  • NotImplementedException

  • BadGatewayException

  • ServiceUnavailableException

  • GatewayTimeoutException

不过我们也可以自定义异常过滤器,使用@Catch() 装饰器绑定所需的元数据到异常过滤器上,如果@Catch()参数列表为空,将会捕获每一个未处理的异常(不管异常类型如何)。

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,       });   } } 复制代码

绑定过滤器:

@Post() @UseFilters(new HttpExceptionFilter()) async create(@Body() createCatDto: CreateCatDto) {   throw new ForbiddenException(); } 复制代码

你也可以使用全局范围的过滤器:

async function bootstrap() {   const app = await NestFactory.create(AppModule);   app.useGlobalFilters(new HttpExceptionFilter());   await app.listen(3000); } bootstrap(); 复制代码

(六)管道

具有 @Injectable() 装饰器的类,负责对数据转换或者验证。Nest 自带八个开箱即用的管道,即

  • ValidationPipe

  • ParseIntPipe

  • ParseBoolPipe

  • ParseArrayPipe

  • ParseUUIDPipe

  • DefaultValuePipe

  • ParseEnumPipe

  • ParseFloatPipe

如果想要自定义管道,只应实现 PipeTransform 接口:

import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class ValidationPipe implements PipeTransform {   transform(value: any, metadata: ArgumentMetadata) {     return value;   } } 复制代码

使用@UsePipes()进行管道的绑定:

@Post() @UsePipes(new JoiValidationPipe(createCatSchema)) async create(@Body() createCatDto: CreateCatDto) {   this.catsService.create(createCatDto); } 复制代码

(七)守卫

使用 @Injectable() 装饰器的类,根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。

守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。

守卫必须实现一个canActivate()函数。此函数应该返回一个布尔值,指示是否允许当前请求。它可以同步或异步地返回响应:

  • 如果返回 true, 将处理用户调用。

  • 如果返回 false, 则 Nest 将忽略当前处理的请求。

例如实现一个授权守卫:

@Injectable() export class AuthGuard implements CanActivate {   canActivate(     context: ExecutionContext,   ): boolean | Promise<boolean> | Observable<boolean> {     const request = context.switchToHttp().getRequest();     return validateRequest(request);   } } 复制代码

使用也很简单:

@Controller('cats') @UseGuards(RolesGuard) export class CatsController {} 复制代码

也可以设置全局守卫:

const app = await NestFactory.create(AppModule); app.useGlobalGuards(new RolesGuard()); 复制代码

我们还可以为守卫增加反射器,获取不同路由的不同元数据,以此做出决策。

先使用@SetMetadata()设置元数据:

@Post() @SetMetadata('roles', ['admin']) async create(@Body() createCatDto: CreateCatDto) {   this.catsService.create(createCatDto); } 复制代码

使用反射器获取设置的元数据:

@Injectable() export class RolesGuard implements CanActivate {   constructor(private reflector: Reflector) {}   canActivate(context: ExecutionContext): boolean {     const roles = this.reflector.get<string[]>('roles', context.getHandler());     if (!roles) {       return true;     }     const request = context.switchToHttp().getRequest();     const user = request.user;     return matchRoles(roles, user.roles);   } } 复制代码

(八)拦截器

使用 @Injectable() 装饰器注解的类,可以转换、扩展、重写函数,以及在函数之前之后绑定额外逻辑。

每个拦截器都有 intercept() 方法,它接收2个参数。 第一个是 ExecutionContext 实例(与守卫完全相同的对象)。第二个参数是 CallHandler``返回一个 Observable`。

@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) export class CatsController {} 复制代码

绑定全局拦截器:

const app = await NestFactory.create(ApplicationModule); app.useGlobalInterceptors(new LoggingInterceptor()); 复制代码

(九)装饰器

Nest 是基于装饰器这种语言特性而创建的,你还可以自定义自己的装饰器。

例如我们可以自定义一个参数装饰器:

import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const User = createParamDecorator((data: unknown, ctx: ExecutionContext) => {   const request = ctx.switchToHttp().getRequest();   return request.user; }); 复制代码

然后使用它:

@Get() async findOne(@User() user: UserEntity) {   console.log(user); } 复制代码

Nest 还可以聚合多个装饰器。例如,假设您要将与身份验证相关的所有装饰器聚合到一个装饰器中。这可以通过以下方法实现:

import { applyDecorators } from '@nestjs/common'; export function Auth(...roles: Role[]) {   return applyDecorators(     SetMetadata('roles', roles),     UseGuards(AuthGuard, RolesGuard),     ApiBearerAuth(),     ApiUnauthorizedResponse({ description: 'Unauthorized"' })   ); } 复制代码

使用聚合装饰器:

@Get('users') @Auth('admin') findAllUsers() {} 复制代码

三、使用

$ npm i -g @nestjs/cli  $ nest new nest-demo 复制代码

使用nest的cli将会创建一个最简单的新项目目录,在src目录中文件组织如下:

src  ├── app.controller.spec.ts  ├── app.controller.ts  ├── app.module.ts  ├── app.service.ts  └── main.ts //应用程序入口文件 复制代码

main.ts内容如下:

/* main.ts */ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() {   const app = await NestFactory.create(AppModule);   await app.listen(3000); } bootstrap(); 复制代码

可以看到主要就是使用NestFactory创建了一个应用实例,监听了3000端口。


作者:青火_
链接:https://juejin.cn/post/7068925905926062110


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐