从0开始Typescript + Express + Sequelize + Swagger + PM2 + Docker 搭建部署后端服务

语言: CN / TW / HK

theme: smartblue

最新项目需要,就搭建了Node后端服务,记录下整个流程,这里不会太深入的说明每个插件的使用,仅对流程进行说明,具体可以查看对应工具的官网。

项目环境: - OS: m1、 - node: 17.9 - Dcoekr Image: node:18-alpine - 部署环境:centos

一、初始化项目

1、新建项目

express-demo npm init 2、安装必要的依赖

yarn add typescript ts-node @types/node @types/express cross-env nodemon -D yarn add express 3、配置tsconfig.json, 常规配置 json { "compilerOptions": { "target": "es2017", "module": "commonjs", //通过commonjs处理模块化 "rootDir": "./", "outDir": "./dist", "esModuleInterop": true, "baseUrl": "src", "strict": true, "strictPropertyInitialization": false // 不用严格要求值的初始化 }, "exclude": ["node_modules"] } 4、在项目下新建src目录,用来存放源文件,项目目录结构如下:

image.png

核心目录就是: - controllers 控制器,主要用来处理api逻辑 - models 模型,数据库表模型 - services 操作数据库的API - databases 数据库相关配置和初始化

然后在这个基础上,我们还有一些辅助的目录 - config 用来获取外部传进来的环境变量或者配置的数据库参数 - exceptions 用来定义接口返回的JSON结构体 - interface 用来声明变量类型 - routes 用来暴露对外的API接口 - utils 作为工具函数的文件目录 - app.ts 用来构建整个app,将各种需要提前处理的集中处理 - index.ts 用来作为整个项目的入口文件

当前节点用到的插件

  • nodemon通过检测到目录中的文件更改时自动重新启动节点,在开发时保持热更新
  • cross-env用来通过命令行设置环境变量,区分开发环境和生产环境

入口文件index.ts, 我们用来引入路由和启动服务

```ts import App from './app' import monitorRouter from './routes/monitor.route'

const app = new App([new monitorRouter()]) app.listen() ```

app.ts,当app实例化的时候,要连接数据库、初始化路由、中间件、文档等,这里先定义好方法

``ts class App { app: express.Application port: number = 3000 constructor(routers: Routes[]){ this.app = express(); this.connectToDatabase() this.initializeMiddlewares() this.initializeRoutes(routers) this.initializeSwagger() } // 连接数据库 private connectToDatabase() { DB.sequelize.sync({ force: false }); } // 初始化中间件 private initializeMiddlewares() { this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); } // 初始化路由 private initializeRoutes(routes: Routes[]) { routes.forEach(route => { this.app.use('/', route.router); }); } // 初始化接口文档 private initializeSwagger() { // 生成文档路由 this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); } // 启动服务 public listen(){ this.app.listen(this.port, () => { console.log(TypeScript with Express http://localhost:${this.port}/`); }); } }

export default App ```

二、初始化路由

路由类,这样就在index.ts中引入路由实例化,所有的路由就生效了,然后我们看每个路由对应的都是controller的方法,只要实现这些方法就可以了

```ts import { Router } from 'express'; import MonitorsController from '../controllers/monitor.controller'; import { Routes } from '../interfaces/routes.interface';

class MonitorRoute implements Routes { public path = '/v1/monitor'; public router = Router(); public monitorsController = new MonitorsController();

constructor() { this.initializeRoutes(); }

private initializeRoutes() { this.router.get(${this.path}, this.monitorsController.getMonitor); this.router.post(${this.path}, this.monitorsController.createMonitor); this.router.post(${this.path}/:id, this.monitorsController.getMonitorById); } }

export default MonitorRoute; ```

三、初始化数据库

yarn add sequelize mysql2 安装两个依赖,一个操作数据库的orm,一个是数据库驱动。

databases中,我们初始化数据库

new Sequelize.Sequelize(database, user, password, { dialect: 'mysql', host: DB_HOST, port: DB_PORT } as any);

四、配置区分生产环境和预发环境

package.json下新增scripts命令,配置项目以不同的环境变量启动

"start": "cross-env NODE_ENV=development nodemon src/index.ts", "start:prod": "cross-env NODE_ENV=pruduction nodemon src/index.ts", 然后在项目根目录下新建.env.development.local文件,配置变量

```.env PORT = 3000

DB_HOST = localhost DB_PORT = 3306 DB_USER = root DB_PASSWORD = 12345678 DB_DATABASE = stark 安装`dotenv`可以读取各种环境变量 yarn add dotenv 在`config/index.ts`中,读取环境变量,在其他地方共享 import { config } from 'dotenv'; config({ path: .env.${process.env.NODE_ENV || 'development'}.local }); export { PORT } = process.env ``` 之后根据业务需要写controller和service就可以了

五、引入swagger

如图所示,可以自动生成API,这样就不用,每次单独写了 image.png 安装依赖 ``` yarn add swagger-jsdoc swagger-ui-express

yarn add @types/swagger-jsdoc @types/swagger-ui-express -D 配置swagger文档,读取对应的yaml文件,生成对应的路由,然后在项目初始化的时候执行该函数ts private initializeSwagger() { const options = { swaggerDefinition: { info: { title: 'REST API', version: '1.0.0', description: 'Example docs', }, }, apis: ['swagger*.yaml'], };

const specs = swaggerJSDoc(options);
this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

} ``` yaml配置文件:配置文档路由和Modal(可以直接通过注释生成,需要自行查看文档)

```yaml tags: - name: monitors description: monitors

paths:

[GET] monitors

/v1/monitor: get: tags: - monitors summary: Find All monitors responses: 200: description: "OK" 500: description: "Server Error"

definitions

definitions: monitors: type: object required: - msg properties: msg: type: string description: error message column: type: number description: error column ```

六、配置PM2启动服务

在部署项目之前,需要先编译成js,这里我们使用swc(通过 rust 实现的 babel:swc,一个将 ES6 转化为 ES5 的工具)当然也可以配置webpack啥的。

yarn add @swc/cli @swc/core -D

在根目录下新建文件配置文件.swcrc,这里附一份配置,具体的内容可以查看文档

js { "jsc": { "parser": { "syntax": "typescript", "tsx": false, "dynamicImport": true, "decorators": true }, "transform": { "legacyDecorator": true, "decoratorMetadata": true }, "target": "es2017", "externalHelpers": false, "keepClassNames": true, "loose": false, "minify": { "compress": false, "mangle": false }, "baseUrl": "src", "paths": { "@/*": ["*"] } }, "module": { "type": "commonjs" } }

package.json中配置scripts命令

"build": "swc src -d dist --source-maps --copy-files", 执行yarn build,就可以看到根目录下生成了dist目录,就是解析后的js文件,要部署的也是这个文件

接着我们使用pm2启动项目

yarn add global pm2 因为pm2运行时肯定要区分生产环境和预发环境,所以我们需要给pm2增加配置文件.ecosystem.config.js

js module.exports = { apps: [ { name: 'monitor', // pm2 start App name script: 'dist/index.js', autorestart: true, // auto restart if process crash watch: false, // files change automatic restart ignore_watch: ['node_modules', 'logs'], // ignore files change max_memory_restart: '1G', // restart if process use more than 1G memory merge_logs: true, // if true, stdout and stderr will be merged and sent to pm2 log output: './logs/access.log', // pm2 log file error: './logs/error.log', // pm2 error log file env_test: { PORT: 3000, NODE_ENV: 'development', DB_HOST: "localhost", DB_PORT: 3306, DB_USER: "root", DB_PASSWORD: 12345678, DB_DATABASE: "stark" }, env_production: { // environment variable PORT: 3000, NODE_ENV: 'production', DB_HOST: "localhost", DB_PORT: 3306, DB_USER: "root", DB_PASSWORD: "12345", DB_DATABASE: "monitor" } } ] }; 执行命令pm2 start ecosystem.config.js --env test

image.png

到这里已经可以部署项目成功了,接着我们通过Docker部署一下

七、Docker构建镜像并部署

```Dockerfile FROM node:18-alpine as common-build-stage

COPY . ./app

WORKDIR /app

RUN npm i -g pm2 --registry=https://registry.npm.taobao.org && yarn add production

EXPOSE 3000

FROM common-build-stage as production-build-stage

ENV NODE_ENV production

CMD ["pm2-runtime", "start", "ecosystem.config.js", "--env", "production"] `` 这里使用pm2-runtime,是因为如果pm2的话,Docker监听不到服务的运行,就会退出,所以这里pm2官方给出了pm2-runtime`来解决这个问题

docker build -t demo --platform linux/amd64 --target production-build-stage -f Dockerfile .

我们要构建的镜像最终是要部署到centos上的,但是m1下打包的无法兼容,所以增加参数--platform linux/amd64 就可以了

八、项目中使用的插件