前言
最近有个需求需要基于前端技术栈实现一套中间层API接口,用于处理由前端维护的一套JSON
配置文件。
经过一番查找后,最终选择了nest.js
这个框架,由于它支持AOP编程,与SpringBoot
的写法较为相似,可以将SpringBoot
那套架构思想应用过来,这对于我这个全干工程师(懂亿点点Java)来说就非常友好了😁
经过3天的学习与折腾,终于搭建了一套我比较满意的架构,本文就跟大家分享下我的架构方案,欢迎各位感兴趣的开发者阅读本文。
写在前面
本文所讲内容会涉及到TypeScript
,如果你对它还不够理解,请先移步:TypeScript中文文档学习下,入个门🤓。
- 本文完整项目代码移步:nest-project
- 本文中所安装的依赖包要求你的node版本必须在
14.16.0
及以上。
你可以使用node版本管理控制器
n
来管理你的node版本,你可以使用npm install -g n
来安装它。安装完成后,你只需使用
n 版本号
即可安装并切换到对应版本的node了。macos下使用可能需要使用sudo n 版本号
。例如:n 14.16.0
。有关
n
的更多使用方法请移步:n-github
环境搭建
在nest官网中,它提供了三种搭建方式:
- 使用CLI安装
- 使用Git安装
- 手动创建
这三种安装方式都比较简单,感兴趣的开发者可自行查阅文档来了解学习。为了锻炼大家的动手能力,本文不采用上述方法来搭建项目,我们将从0开始使用yarn初始化一个空项目,然后安装nest的相关依赖包。
注意:如果你已经搭建好了环境,请跳过此章节,前往下一个章节:项目架构。
初始化一个空项目
本文使用yarn来初始化项目,如果你没有安装的话需要先使用npm来安装下,命令如下:
npm install --global yarn
安装完成后,可以使用命令:yarn --version
来验证下是否安装成功,如果成功你会看到如下所示的输出:
image-20220111215750509
接下来,我们创建一个名为nest-project
的空文件夹,在终端进入这个文件夹,使用命令:yarn init
来初始化一个项目,如下所示,根据自己的需要填写即可,带括号的部分可以不填写保持默认,直接回车即可。
image-20220111222505312
随后,我们打开这个项目,文件夹中只有一个package.json文件,内容如下所示:
{ "name": "nest-project", "version": "1.0.0", "main": "index.js", // 这个可以删除,不需要这个字段 "author": "likai", "license": "MIT", "private": true }
上述内容就是我们刚才在终端所选择的,因此你也可以自己创建一个空文件,创建这个json文件,写上对应的配置,达到相同的结果。
安装nest依赖包
我们打开刚才创建的package.json文件,添加如下所示的字段:
{ "dependencies": { "@nestjs/common": "^8.1.1", "@nestjs/core": "^8.1.1", "@nestjs/platform-express": "^8.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.4.0" }, "devDependencies": { "@nestjs/cli": "^8.1.3", "@nestjs/schematics": "^8.0.4", "@types/express": "^4.17.13", "@types/node": "^16.11.1", "supertest": "^6.1.6", "ts-loader": "^9.2.6", "ts-node": "^10.3.0", "tsconfig-paths": "^3.11.0", "tslib": "^2.3.0", "typescript": "^4.4.4", "webpack": "5.0.0" } }
随后,我们打开终端,进入项目目录,执行yarn install
命令,成功后的界面如下所示:
image-20220111225541175
安装代码规范依赖包
本文采用eslint和prettier来规范代码,对此不了解的开发者请移步我的另一篇文章:独立使用ESLint+Prettier对代码进行格式校验。
接下来,我们打开前面所创建的package.json
文件,在devDependencies
对象中添加下述代码:
{ "devDependencies": { "eslint": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^3.3.1", "prettier": "^2.2.1", "@typescript-eslint/eslint-plugin": "^4.18.0", "@typescript-eslint/parser": "^4.18.0", } }
添加完成后,执行yarn install
就完成了依赖包的引入。
添加启动命令
安装完所有依赖后,接下来我们在package.json中添加6个运行脚本,用于项目的启动与打包构建,如下所示:
- prebuild 移除dist目录
- build 打包项目
- start 启动项目
- start:dev 启动项目(支持热更新)
- start:debug 以debugger模式启动项目(支持断点调试)
- start:prod 启动打包后的项目
{ "scripts": { "prebuild": "rimraf dist", "build": "nest build", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main" } }
添加配置文件
接下来,我们还需要在项目根目录添加nest、eslint、prettier等配置文件,如下所示:
- .editorconfig统一不同操作系统之间的代码格式相关问题的配置文件
- .eslintrc.js eslint的配置文件
- .gitignore git提交时需要忽略的文件
- .prettierrc.json prettier的配置文件
- nest-cli.json nest的配置文件
- tsconfig.json typescript的配置文件
- tsconfig.build.json 项目打包时ts文件的相关处理配置文件
具体的文件内容,点击上方蓝色字体可直接跳转到GitHub中对应的文件。
项目架构
本章节将跟大家下分享我的项目架构,首先在项目根目录创建src
文件夹,所有项目代码将存放在此目录下。
本章节节对应的的完整项目代码移步:nest-project
控制层
这一层用于处理客户端传入的请求以及向客户端返回响应,所有的请求映射都会在这一层来实现。每个请求会对应一个控制器,一个控制器中可以有多个子方法用于处理同类型的不同操作。
举例说明
接下来,我们在src目录下创建controller
文件夹,在其目录下新建一个AppController.ts
文件。
我们从一个例子入手:
- 处理
/home/setTitle
的post
请求,它的参数在http body中 - 处理
/home/getTitle
的get
请求,它的参数在请求url中
实现代码
翻阅官方文档后,我们就可以写出如下所示的代码:
import { Body, Controller, Get, Query, Post } from "@nestjs/common"; @Controller("home") export class AppController { @Post("setTitle") setTitle(@Body() data: { id: number; title: string }): { code: number; data: null | string; msg: string; } { // 客户端传入的数据 console.log(data); // 返回给客户端的数据 return { code: 0, data: null, msg: "post方法调用成功" }; } @Get("getTitle") getTitle(@Query("id") id: number): { code: number; data: string; msg: string; } { console.log("客户端传入的数据", id); return { code: 0, data: null, msg: "get方法调用成功" }; } }
我们来看下上述代码中各个装饰器的作用:
@Controller
用于标识此文件是一个控制器,它接受一个参数,此处我写了home,代表所有/home
的请求都会进到这里。@Post
用于处理post格式的请求,它也接受一个参数,此处我写了setTitle,代表/home/setTitle
的post请求会进到这里。@Body
用于获取http body中的数据@Query
用于获取请求url中的数据
在nest文档中,它提供的装饰器还有很多,可以应付各种开发场景,详情请移步:控制器- request。
服务层
服务层用于处理具体的业务逻辑,当我们收到客户端的请求后,取出参数编写具体的业务代码。
举例说明
接下来,我们在src目录下创建service
文件夹,在其目录下新建一个AppService.ts
文件。
举个例子:
- 写一个方法,根据id来做一些事情,做完后返回操作结果。
实现代码
查阅文档后,我们知道了需要使用@Injectable()
来装饰这个类,代码如下所示:
import { Injectable } from "@nestjs/common"; @Injectable() export class AppService { public setTitle(id: string): { code: number; data: null | string; msg: string; } { // 根据id做一些事情,此处省略 console.log(id); // 返回操作结果 return { code: 0, data: null, msg: "设置成功" }; } }
做完上述操作后,我们还需要改造下AppController
,在constructor
中引入我们刚才创建好的service,部分代码如下所示:
export class TextAttributeController { constructor(private readonly appService: AppService) {} @Post("setTitle") setTitle(){ // 此处省略了较多代码,这里的重点是演示如何调用我们刚才写好的方法 return this.appService.setTitle(); } }
一个service类中会有很多方法,我们会根据控制层的映射建立与之对应的处理方法
,这样就可以让控制层更专心的处理它的分内之事,提升代码可读性。
接口层
这一层用于声明每个service类中都有哪些方法,可以很大程度提升代码的可读性。如果没有这一层,当service中的方法越来越多时,代码也会特别长,想快速找到某个方法,将会变得很费时。
举例说明
接下来我们在src目录下创建interface
文件夹,在其目录下新建一个AppInterface.ts
文件。
举个例子,我们需要在声明5个方法,分别如下所示:
- getTitle
- getName
- getAge
- setName
- setTitle
实现代码
在TypeScript中用interface
关键字来声明一个接口,那么上述例子转换为代码后就如下所示:
export interface AppInterface { getTitle(): string; getName(): string; getAge(): string; setName(): string; setTitle(): string; }
做完上述操作后,我们还需要改造下service层的代码,让其实现这个接口,部分代码如下所示:
@Injectable() export class AppService implements AppInterface { getAge(): string { return ""; } getName(): string { return ""; } // 其他方法省略 }
在TypeScript中,我们使用
implements
关键字来实现一个接口。