從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=http://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 就可以了

八、項目中使用的插件