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
, repository
, factory
, helper
等都可以被视为提供者。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