-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
让我们用Nestjs来重写一个CNode(上) #18
Comments
dto和interface你都会使用吗? |
@CHEN-DONG 后面实战会讲到他们怎么使用以及坑 |
现在文件目录结构也有些纠结 |
interceptor 和 middleware 怎么理解呢?两个都是在controller之外,比如打印日志的话,两个都能做,那区别是什么呢?感觉interceptor比middleware能做的更多,更符合nest的整体风格,middleware更像是为了兼容express做的 |
@marsprince 拦截器是在响应之前,响应之后执行。它常用功能就是打印响应日志,缓存数据,转化响应数据,响应超时判断。 中间件是在请求结束就立即执行了,你说的兼容express也差不多,需要封装,不能直接使用。可以参考这里nest-middlewares。 他们注册位置区别:
他们共同点是全局都不能使用依赖注入,只能在私有注册才能使用依赖注入,并且不能用
合适的人做合适的事, |
那这样看来,中间件的功能,除了对请求对象进行modify,添加session,csrf等一些全局性东西,其他的拦截器都能实现并且更好维护 |
Thank you |
我遇到一个问题,我在存储和修改数据时都需要获取当前登陆用户,我现在能想到的办法就说在控制器的每个方法下都获取当前用户,然后进行处理后传给service,我觉得很笨。能否在dto中或者entity中去获取请求呢?有其他的方法吗? |
@iliuyt 服务是处理控制器业务逻辑,你当前http上下文从控制器上面获取的,手动传递给服务去处理。这是很正常操作流程。还有一种方式就是如何在服务中访问当前http上下文 |
"中间件是在请求结束就立即执行了,你说的兼容express也差不多,需要封装,不能直接使用。可以参考这里nest-middlewares。" nest 的中间件执行不是在请求开始的时候么?而且这里的中间件拿不到控制器处理后的响应数据吧,在响应后面的是 interceptor next() 后,是这样吧? |
背景
在本文中,我将使用
Nest.js
构建一个CNode。为什么这篇文章?我喜欢
NodeJs
,虽然我的NodeJs
水平一般。但我还是用它来记录一下我学习过程。最近,我发现了Nest.js框架,它有效地解决了Nodejs项目中的一个难题:体系结构。
Nest
旨在提供开箱即用的应用程序,可以轻松创建高度可测试,可扩展,松散耦合且易于维护的应用程序。Nest.js
将TypeScript
引入Node.js
中并基于Express
封装。所以,我想用Nest.js
尝试写一个CNode。(ps:目前CNode采用Egg编写)我没有找到关于这个话题的快速入门,所以我会给你我的实践,你可以轻松地扩展到你的项目。本文的目的不是介绍Nest.js。对于那些不熟悉Nest.js的人:它是构建Node.js Web应用程序的框架。尽管Node.js已经包含很多用于开发Web应用程序的库,但它们都没有有效地解决最重要的主题之一:体系结构。
现在,请系好安全带,我们要发车了。
什么是 Nest
Nest
是一个强大的Node web
框架。它可以帮助您轻松地构建高效、可伸缩的应用程序。它使用现代JavaScript
,用TypeScript
构建,结合了OOP
(面向对象编程)和FP
(函数式编程)的最佳概念。它不仅仅是另一个框架。你不需要等待一个大的社区,因为
Nest
是用非常棒的、流行的知名库——Express
和socket.io
构建的!这意味着,您可以快速开始使用框架,而不必担心第三方插件。作者Kamil Myśliwiec初衷:
Nest 核心概念
Nest的核心概念是提供一种体系结构,它帮助开发人员实现层的最大分离,并在应用程序中增加抽象。
架构概览
Nest
采用了ES6
和ES7
的特性(decorator
,async/await
)。如果想使用它们,需要用到Babel
或TypeScript
进行转换成es5
。Nest
默认使用的是TypeScript,也可以直接使用JavaScript
,不过那样就没什么意义了。模块 Module
使用
Nest
,您可以很自然地将代码拆分为独立的和可重用的模块。Nest
模块是一个带有@Module()
装饰器的类。这个装饰器提供元数据,框架使用元数据来组织应用程序结构。每个
Nest
应用都有一个根模块,通常命名为AppModule
。根模块提供了用来启动应用的引导机制。 一个应用通常会包含很多功能模块。像
JavaScript
模块一样,@Module
也可以从其它@Module
中导入功能,并允许导出它们自己的功能供其它@Module
使用。 比如,要在你的应用中使用nest
提供的mongoose
操作功能,就需要导入MongooseModule
。把你的代码组织成一些清晰的功能模块,可以帮助管理复杂应用的开发工作并实现可复用性设计。 另外,这项技术还能让你使用动态加载,
MongooseModule
就是使用这项技术。@Module
装饰器接受一个对象,该对象的属性描述了模块:providers
Nest
注入器实例化的服务,可以在这个模块之间共享。controllers
imports
exports
providers
里的服务。@Module
为一个控制器集声明了编译的上下文环境,它专注于某个应用领域、某个工作流或一组紧密相关的能力。@Module
可以将其控制器和一组相关代码(如服务)关联起来,形成功能单元。怎么组织一个模块结构图
AppModule 根模块
在
Nest
中,模块默认是单例的,因此可以在多个模块之间共享任何提供者的同一个实例。共享模块毫不费力。整体看起来比较干净清爽,这也是我在
Angular
项目中一直使用的模块划分。如果你有更好建议,欢迎和我一起交流改进。
控制器 Controller
控制器负责处理客户端传入的请求参数并向客户端返回响应数据,说的通俗点就是路由
Router
。为了创建一个基本的控制器,我们使用
@Controller
装饰器。它们将类与基本的元数据相关联,因此Nest
知道如何将控制器映射到相应的路由。@Controller
它是定义基本控制器所必需的。@Controller('Router Prefix')
是类中注册的每个路由的可选前缀。使用前缀可以避免在所有路由共享一个公共前缀时重复使用自己。控制器是一个比较核心功能,所有的业务都是围绕它来开展。
Nest
也提供很多相关的装饰器,接下来一一介绍他们,这里只是简单说明,后面实战会介绍他们的使用。请求对象表示HTTP请求,并具有请求查询字符串、参数、HTTP标头等属性,但在大多数情况下,不需要手动获取它们。我们可以使用专用的
decorator
,例如@Body()
或@Query()
,它们是开箱即用的。下面是decorator
与普通Express
对象的比较。先说方法参数装饰器:
@Request()
Express
的req
,也可以简写@req
@Response()
Express
的res
,也可以简写@res
@Next()
Express
的next
@Session()
Express
的req.session
@Param(param?: string)
Express
的req.params
@Body(param?: string)
Express
的req.body
@Query(param?: string)
Express
的req.query
@Headers(param?: string)
Express
的req.headers
先说方法装饰器:
@Post()
Express
的Post
方法@Get()
Express
的Get
方法@Put()
Express
的Put
方法@Delete()
Express
的Delete
方法@All()
Express
的All
方法@Patch()
Express
的Patch
方法@Options()
Express
的Options
方法@Head()
Express
的Head
方法@Render()
Express
的res.render
方法@Header()
Express
的res.header
方法@HttpCode()
Express
的res.status
方法,可以配合HttpStatus
枚举以上基本都是控制器装饰器,一些常用的HTTP请求参数需要使用对应的方法装饰器和参数来配合使用。
关于返回响应数据,
Nest
也提供2种解决方案:直接返回一个
JavaScript
对象或数组时,它将被自动解析为JSON
。当我们返回一个字符串时,Nest
只发送一个字符串,而不尝试解析它。默认情况下,响应的状态代码总是200
,但
POST
请求除外,它使用201
。可以使用@HttpCode(HttpStatus.xxxx)
装饰器可以很容易地改变这种行为。我们可以使用库特定的响应对象,我们这里可以使用@res()修饰符在函数签名中注入该对象,
res.status(HttpStatus.CREATED).send()
或者res.status(HttpStatus.OK).json([])
等Express
的res
方法。注意:禁止同时使用这两种方法,如果2个都使用,那么会出现这个路由不工作的情况。如果你在使用时候发现路由不响应,请检查有没有出现混用的情况,如果是正常情况下,推荐第一种方式返回。
关于控制器异常处理,在后面过滤器讲解。
服务与依赖注入 Provider Dependency injection
服务是一个广义的概念,它包括应用所需的任何值、函数或特性。狭义的服务是一个明确定义了用途的类。它应该做一些具体的事,并做好。
Nest
把控制器和服务区分开,以提高模块性和复用性。通过把控制器中和逻辑有关的功能与其他类型的处理分离开,你可以让控制器类更加精简、高效。 理想情况下,控制器的工作只管申明装饰器和响应数据,而不用顾及其它。 它应该提供请求和响应桥梁,以便作为视图(由模板渲染)和应用逻辑(通常包含一些模型的概念)的中介者。
控制器不需要定义任何诸如从客户端获取数据、验证用户输入或直接往控制台中写日志等工作。 而要把这些任务委托给各种服务。通过把各种处理任务定义到可注入的服务类中,你可以让它可以被任何控制器使用。 通过在不同的环境中注入同一种服务的不同提供商,你还可以让你的应用更具适应性。
Nest
不会强制遵循这些原则。它只会通过依赖注入让你能更容易地将应用逻辑分解为服务,并让这些服务可用于各个控制器中。控制器是服务的消费者,也就是说,你可以把一个服务注入到控制器中,让控制器类得以访问该服务类。
那么服务就是提供者,基本上,几乎所有事情都可以看作是提供者—服务、存储库、工厂、助手等等。它们都可以通过构造函数注入依赖关系,这意味着它们可以彼此创建各种关系。
在
Nest
中,要把一个类定义为服务,就要用@Injectable
装饰器来提供元数据,以便让Nest
可以把它作为依赖注入到控制器中。同样,也要使用
@Injectable
装饰器来表明一个控制器或其它类(比如另一个服务、模块等)拥有一个依赖。 依赖并不必然是服务,它也可能是函数或值等等。依赖注入(通常简称 DI)被引入到
Nest
框架中,并且到处使用它,来为新建的控制器提供所需的服务或其它东西。注入器是主要的机制。你不用自己创建
Nest
注入器。Nest
会在启动过程中为你创建全应用级注入器。该注入器维护一个包含它已创建的依赖实例的容器,并尽可能复用它们。
提供者是创建依赖项的配方。对于服务来说,它通常就是这个服务类本身。你在应用中要用到的任何类都必须使用该应用的注入器注册一个提供商,以便注入器可以使用它来创建新实例。
关于依赖注入,前端框架
Angular
应该是最出名的,可以看这里介绍。自定义服务
我们不光可以使用
@Injectable()
来定义服务,还可以使用其他三种方式:value
、class
、factory
。这个和Angular一样,默认
@Injectable()
来定义服务就是class
。使用
value
:使用
class
:使用
factory
:如果我们
provide
注册名不是一个服务怎么办,是一个字符串key
,也是很常用的。要用选择的自定义字符串
key
,您必须告诉Nest,需要用到@Inject()
装饰器,就像这样:还有一个循环依赖的坑,后面实战会介绍怎么避免和解决这个坑。
中间件 Middleware
中间件是在路由处理程序之前调用的函数。中间件功能可以访问请求和响应对象,以及应用程序请求-响应周期中的下一个中间件功能。下一个中间件函数通常由一个名为
next
的变量表示。在Express
中的中间件是非常出名的。默认情况下,
Nest
中间件相当于表示Express
中间件。和Express
中间件功能类似,中间件功能可以执行以下任务next()
将控制权传递给下一个中间件函数。否则,请求将被挂起。简单理解
Nest
中间件就是把Express
中间件进行了包装。那么好处就是只要你想用中间件,可以立马搜索Express
中间件,拿来即可使用。是不是很方便。Nest
中间件要么是一个函数,要么是一个带有@Injectable()
装饰器的类。类应该实现NestMiddleware
接口,而函数却没有任何特殊要求。怎么使用,有两种方式:
过滤器 Exception filter
异常过滤器层负责在整个应用程序中处理所有抛出的异常。当发现未处理的异常时,最终用户将收到适当的用户友好响应。
默认显示响应
JSON
信息使用底层过滤器
HttpException 接受2个参数:
{status: 状态码,error:错误消息}
每次写这么多很麻烦,那么过滤器也支持扩展和定制快捷过滤器对象。
就可以直接使用了:
是不是,方便很多了。
Nest
给我们提供很多这样快捷常用的HTTP状态错误:异常处理程序基础很好,但有时你可能想要完全控制异常层,例如,添加一些日志记录或使用一个不同的
JSON
模式基于一些选择的因素。前面说了,Nest
给我们内置返回响应模板,这个不能接受的,我们要自定义怎么办了,Nest
给我们扩展空间。它返回是一个
Express
的方法response
,来定制自己的响应异常格式。怎么使用,有四种方式:
@UseFilters()
装饰器里面使用,作用当前这条路由的响应结果@UseFilters()
装饰器里面使用,作用当前控制器路由所有的响应结果useGlobalFilters
,作用整个项目。过滤器这种比较通用推荐全局注册。管道 Pipe
管道可以把你的请求参数根据特定条件验证类型、对象结构或映射数据。管道是一个纯函数,不应该从数据库中选择或调用任何服务操作。
定义一个简单管道:
管道是用
@Injectable()
装饰器注释的类。应该实现PipeTransform
接口,具体代码在transform
实现,这个和Angular
很像。Nest
处理请求数据验证,在数据不正确时可以抛出异常,使用过滤器来捕获。Nest
为我们内置了2个通用的管道,一个数据验证ValidationPipe
,一个数据转换ParseIntPipe
。使用
ValidationPipe
需要配合class-validator class-transformer
,如果你不安装它们 ,你使用ValidationPipe
会报错的。怎么使用,有四种方式
@Body()
装饰器里面使用,只作用当前body这个参数@UsePipes()
装饰器里面使用,作用当前这条路由所有的请求参数@UsePipes()
装饰器里面使用,作用当前控制器路由所有的请求参数useGlobalPipes
,作用整个项目。这个管道比较通用推荐全局注册。那么
createUserDto
怎么玩了,后面实战教程会讲解,这里不展开。ParseIntPipe
使用也很简单,就是把一个字符串转换成数字。也是比较常用的,特别是你的id是字符串数字的时候,用get
,put
,patch
,delete
等请求,有id时候特别好用了。还可以做分页处理,后面实战中用到,具体在讲解。
守卫 Guard
守卫可以做权限认证,如果你没有权限可以拒绝你访问这个路由,默认返回
403
错误。定义一个简单管道:
守卫是用
@Injectable()
装饰器注释的类。应该实现CanActivate
接口,具体代码在canActivate
方法实现,返回一个布尔值,true就表示有权限,false抛出异常403错误。这个写法和Angular
很像。怎么使用,有两种方式
@UseGuards()
装饰器里面使用,作用当前控制器路由所有的请求参数useGlobalGuards
,作用整个项目。如果你不做权限管理相关的身份验证操作,基本用不上这个功能。不过还是很有用抽象功能。我们这个实战项目也会用到这个功能。
拦截器 Interceptor
拦截器是一个比较特殊强大功能,类似于AOP面向切面编程,前端编程中也尝尝使用这样的技术,比如各种http请求库都提供类似功能。有名的框架
Angular
框架HTTP模块。有名的库有老牌的jquery
和新潮的axios
等。定义一个简单拦截器:
拦截器是用
@Injectable()
装饰器注释的类。应该实现NestInterceptor
接口,具体代码在intercept
方法实现,返回一个Observable
,这个写法和Angular
很像。拦截器可以做什么:
怎么使用,有三种方式
@UseInterceptors()
装饰器里面使用,作用当前路由,还可以传参数,需要特殊处理,写成高阶函数,也可以使用依赖注入。@UseInterceptors()
装饰器里面使用,作用当前控制器路由,这个不能传参数,可以使用依赖注入useGlobalInterceptors
,作用整个项目。拦截器可以做很多功能,比如缓存处理,响应数据转换,异常捕获转换,响应超时跑错,打印请求响应日志。我们这个实战项目也会用到这个功能。
总结
模块是按业务逻辑划分基本单元,包含控制器和服务。控制器是处理请求和响应数据的部件,服务处理实际业务逻辑的部件。
中间件是路由处理Handler前的数据处理层,只能在模块或者全局注册,可以做日志处理中间件、用户认证中间件等处理,中间件和express的中间件一样,所以可以访问整个request、response的上下文,模块作用域可以依赖注入服务。全局注册只能是一个纯函数或者一个高阶函数。
管道是数据流处理,在中间件后路由处理前做数据处理,可以控制器中的类、方法、方法参数、全局注册使用,只能是一个纯函数。可以做数据验证,数据转换等数据处理。
守卫是决定请求是否可以到达对应的路由处理器,能够知道当前路由的执行上下文,可以控制器中的类、方法、全局注册使用,可以做角色守卫。
拦截器是进入控制器之前和之后处理相关逻辑,能够知道当前路由的执行上下文,可以控制器中的类、方法、全局注册使用,可以做日志、事务处理、异常处理、响应数据格式等。
过滤器是捕获错误信息,返回响应给客户端。可以控制器中的类、方法、全局注册使用,可以做自定义响应异常格式。
中间件、过滤器、管道、守卫、拦截器,这是几个比较容易混淆的东西。他们有个共同点都是和控制器挂钩的中间抽象处理层,但是他们的职责却不一样。
全局管道、守卫、过滤器和拦截器和任何模块松散耦合。他们不能依赖注入任何服务,因为他们不属于任何模块。
可以使用控制器作用域、方法作用域或辅助作用域仅由管道支持,其他除了中间件是模块作用域,都是控制器作用域和方法作用域。
管道、过滤器、拦截器守卫都有各自的具体职责。拦截器和守卫与模块结合在一起,而管道和过滤器则运行在模块区域之外。管道任务是根据特定条件验证类型、对象结构或映射数据。过滤器任务是捕获各种错误返回给客户端。管道不是从数据库中选择或调用任何服务的适当位置。另一方面来说,拦截器不应该验证对象模式或修饰数据。如果需要重写,则必须由数据库调用服务引起。守卫决定了哪些路由可以访问,它接管你的验证责任。
那你肯定最关心他们执行顺序是什么:
我们来看2张图,
请求返回响应结果:
请求返回响应异常:
Hello World
学习一门语言一门技术都是从
Hello World
开始,我们也是从零到Hello World
开启学习Nest
之旅准备必备开发环境和工具
推荐
nvm
来管理nodejs
版本,根据自己电脑下载对应版本吧。vs code
推荐插件:(其他插件自己随意)Nest相关资源
nest-cli
nest-cli
是一个nest
项目脚手架。为我们提供一个初始化模块,可以让我们快速完成Hello World
功能。安装
常用命令:
new(简写:n) 构建新项目
generate(简写:g) 生成文件
创建一个users服务文件
必须
在项目根目录
下创建,(默认创建在src/)。(不能在当前文件夹里面创建,不然会自动生成xxx/src/xxx。吐槽:这个没有Angular-cli智能)优先
新建模块,不然创建的非模块以外的服务,控制器等就会自动注入更新到上级的模块里面info(简写:i) 打印版本信息
打印当前系统,使用nest核心模块版本,供你去官方提交issues
nest内置功能
目前
Nest.js
支持express
和fastify
, 对fastify
不熟,本文选择express
。核心模块
可选模块
构建项目
nest-cnode
其中提交的你的
description
, 初始化版本version
, 作者author
, 以及一个package manager
选择node_modules
安装方式npm
或者yarn
。我们打开浏览器,访问
http://localhost:3000
,您应该看到一个页面,上面显示Hello World
文字。我们上篇已经到此为止,请看我们下篇项目实战--Nest-CNode
The text was updated successfully, but these errors were encountered: