初识 Nest.js
Nest.js官网介绍:
Nest (NestJS) 是一个用于构建高效、可扩展的
Node.js
服务器端应用程序的开发框架。它利用JavaScript
的渐进增强的能力,使用并完全支持TypeScript
(仍然允许开发者使用纯 JavaScript 进行开发),并结合了 OOP (面向对象编程)、FP (函数式编程)和 FRP (函数响应式编程)。在底层,Nest 构建在强大的 HTTP 服务器框架上,例如 Express (默认),并且还可以通过配置从而使用 Fastify !
Nest 在这些常见的 Node.js 框架 (Express/Fastify) 之上提高了一个抽象级别,但仍然向开发者直接暴露了底层框架的 API。这使得开发者可以自由地使用适用于底层平台的无数的第三方模块。
上面这段话刚开始并不能完全理解, 但是简单可以解读出来Nest.js的几个特点:
- 原生支持TypeScript的框架
- 可以基于
Express
也可以选择fastify
, 如果你对Express
非常熟练, 直接用它的API也是没问题的
至于其他看不懂,就暂时放一边, 因为不影响我们入门,后面深入学习后会再来分析。
为什么选择nest.js?
它通过灵活使用控制反转、依赖注入和面向切面编程等设计理念,极大的规范了大型应用的架构,降低了模块之间的耦合度,从而提升了应用的开发效率。在 NodeJS 的世界里,也存在一个全面借鉴 Spring 设计思想的框架
nest与egg简单对比
- 都是为企业级框架和应用而生
- Egg.js基于Koa,Nest.js基于express
- Egg.js和Nest.js都是按照约定进行开发,Egg相比Nest约定更标准
- 面向对象方面,Nest.js优于Egg.js
Egg特性:
- 高度可扩展的插件
- 内置多进程管理
- 基于Koa,性能优异
- 框架稳定,测试覆盖率高
Nest特性
- 依赖注入容器
- 模块化封装
项目创建
首先确定你已经安装了Node.js
, Node.js
安装会附带npx
和一个npm
包运行程序。要创建新的Nest.js
应用程序,请在终端上运行以下命令:
npm i -g @nestjs/cli // 全局安装Nest nest new project-name // 创建项目
执行完创建项目, 会初始化下面这些文件, 并且询问你要是有什么方式来管理依赖包:
如果你有安装yarn
,可以选择yarn
,能更快一些,npm
在国内安装速度会慢一些
接下来按照提示运行项目:
Nest.js
要求 Node.js
(>= 10.13.0,v13 除外), 如果你的Node.js
版本不满足要求,可以通过nvm
包管理工具安装符合要求的Node.js
版本项目结构
进入项目,看到的目录结构应该是这样的:
这里简单说明一下这些核心文件:
src ├── app.controller.spec.ts ├── app.controller.ts ├── app.module.ts ├── app.service.ts ├── main.ts
app.controller.ts |
单个路由的基本控制器(Controller) |
app.controller.spec.ts |
针对控制器的单元测试 |
app.module.ts |
应用程序的根模块(Module) |
app.service.ts |
具有单一方法的基本服务(Service) |
main.ts |
应用程序的入口文件,它使用核心函数 NestFactory 来创建 Nest 应用程序的实例。 |
第一个接口
前面我们已经启动了服务, 那我们怎么查看呢, 首先就是找到入口文件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();
内容比较简单, 使用Nest.js
的工厂函数NestFactory
来创建了一个AppModule
实例,启动了 HTTP 侦听器,以侦听main.ts
中所定义的端口。
我们打开浏览器访问http://localhost:3000
地址:
这里看到的Hello World
就是接口地址http://localhost:9080
返回的内容
说明Nest.js
创建项目默认就给写了一个接口例子,那就通过这个接口例子来看,我们应该怎么实现一个接口。
前边看到mian.ts
中也没有别的文件引入, 只有AppModule
, 打开src/app.module.ts
:
AppModule
是应用程序的根模块,根模块提供了用来启动应用的引导机制,可以包含很多功能模块。
.mudule
文件需要使用一个@Module()
装饰器的类,装饰器可以理解成一个封装好的函数,其实是一个语法糖,@Module()
装饰器接收四个属性:providers
、controllers
、imports
、exports
。
- providers:
Nest.js
注入器实例化的提供者(服务提供者),处理具体的业务逻辑,各个模块之间可以共享(注入器的概念后面依赖注入部分会讲解); - controllers:处理http请求,包括路由控制,向客户端返回响应,将具体业务逻辑委托给providers处理;
- imports:导入模块的列表,如果需要使用其他模块的服务,需要通过这里导入;
- exports:导出服务的列表,供其他模块导入使用。如果希望当前模块下的服务可以被其他模块共享,需要在这里配置导出;
AngularJS
、Spring
和Nest.js
都是基于控制反转
原则设计的,而且都使用了依赖注入的方式来解决解耦问题app.module.ts
中,看到它引入了app.controller.ts
和app.service.ts
使用@Controller
装饰器来定义控制器, @Get
是请求方法的装饰器,对getHello
方法进行修饰, 表示这个方法会被GET请求调用。
从上面,我们可以看出使用@Injectable
修饰后的 AppService
, 在AppModule
中注册之后,在app.controller.ts
中使用,我们就不需要使用new AppService()
去实例化,直接引入过来就可以用。
至此,对于http://localhost:3000/
接口返回的Hello World
逻辑就算理清楚了, 在这基础上我们再详细的学习一下Nest.js
中的路由使用。
路由装饰器
Nest.js
中没有单独配置路由的地方,而是使用装饰器。Nest.js
中定义了若干的装饰器用于处理路由。
@Controller
如每一个要成为控制器的类,都需要借助@Controller
装饰器的装饰,该装饰器可以传入一个路径参数,作为访问这个控制器的主路径:
对app.controller.ts
文件进行修改
通过@Controller("api")
修改这个控制器的路由前缀为api
, 此时可以通过
HTTP方法处理装饰器
@Get
、@Post
、@Put
等众多用于HTTP方法处理装饰器,经过它们装饰的方法,可以对相应的HTTP请求进行响应。同时它们可以接受一个字符串或一个字符串数组作为参数,这里的字符串可以是固定的路径,也可以是通配符。现在我们来两个简单的例子
app.service
全局路由前缀
除了上面这些装饰器可以设置路由外, 我们还可以设置全局路由前缀, 比如给所以路由都加上/api
前缀。此时需要修改main.ts
之前在Controller声明的地址就不用写了,到此我们认识了Controller
、Service
、Module
、路由以及一些常用的装饰器
介绍几个nest-cli
提供的几个有用的命令
//语法 nest g [文件类型] [文件名] [文件目录]
创建模块
nest g mo posts
创建一个 posts模块,文件目录不写,默认创建和文件名一样的posts
目录,在posts
目录下创建一个posts.module.ts
执行完命令后,我们还可以发现同时在根模块app.module.ts
中引入PostsModule
这个模块,也在@Model
装饰器的inports
中引入了PostsModule
创建控制器
nest g co posts
此时创建了一个posts控制器,命名为posts.controller.ts
以及一个该控制器的单元测试文件
执行完命令, 文件posts.module.ts
中会自动引入PostsController
,并且在@Module
装饰器的controllers
中注入。
创建服务类
nest g service posts
创建app.service.ts
文件,并且在app.module.ts
文件下,@Module
装饰器的providers
中注入注入
其实nest-cli
提供的创建命令还有很多, 比如创建过滤器、拦截器和中间件等,可以去官网查看命令
接口格式统一
一般开发中是不会根据HTTP
状态码来判断接口成功与失败的, 而是会根据请求返回的数据,里面加上code
字段首先定义返回的json格式:
{ "code": 0, "message": "OK", "data": { } }
请求失败时返回:
"code": -1, "message": "我错了", "data": {} }
拦截错误请求
首先使用命令创建一个过滤器:
nest g filter core/filter/http-exception
过滤器代码实现:
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); // 获取请求上下文 const response = ctx.getResponse(); // 获取请求上下文中的 response对象 const status = exception.getStatus(); // 获取异常状态码 // 设置错误信息 const message = exception.message ? exception.message : `${status >= 500 ? 'Service Error' : 'Client Error'}`; const errorResponse = { data: {}, message, code: 200, }; // 设置返回的状态码, 请求头,发送错误信息 response.status(200); response.header('Content-Type', 'application/json; charset=utf-8'); response.send(errorResponse); } }
最后需要在main.ts
中全局注册
async function bootstrap() { const app = await NestFactory.create(AppModule); app.setGlobalPrefix('api'); // 设置全局路由前缀 app.useGlobalFilters(new HttpExceptionFilter()) await app.listen(3000); }
这样对请求错误就可以统一的返回了,返回请求错误只需要抛出异常即可,比如之前的:
throw new HttpException('抛出', 200);
接下来对请求成功返回的格式进行统一的处理,可以用Nest.js
的拦截器来实现。
拦截成功的返回数据
首先使用命令创建一个拦截器:
nest g interceptor core/interceptor/transform
拦截器代码实现:
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; import { map, Observable } from 'rxjs'; @Injectable() export class TransformInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( map((data) => { return { data, code: 200, msg: '请求成功', }; }), ); } }
最后和过滤器一样,在main.ts
中全局注册:
async function bootstrap() { const app = await NestFactory.create(AppModule); app.setGlobalPrefix('api'); // 设置全局路由前缀 app.useGlobalInterceptors(new TransformInterceptor()) app.useGlobalFilters(new HttpExceptionFilter()) await app.listen(3000); }
过滤器和拦截器实现都是三部曲:创建 > 实现 > 注册
,还是很简单的
总结
至此我们Nest.js
快速上手入门就告一段落了,文章从项目如何搭建,到实现简单的CRUD
,再到统一接口格式、完成接口参数验证