从0开始Typescript + Express + Sequelize + Swagger + PM2 + Docker 搭建部署后端服务
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
目录,用来存放源文件,项目目录结构如下:
核心目录就是:
- controllers
控制器,主要用来处理api逻辑
- models
模型,数据库表模型
- services
操作数据库的API
- databases
数据库相关配置和初始化
然后在这个基础上,我们还有一些辅助的目录
- config
用来获取外部传进来的环境变量或者配置的数据库参数
- exceptions
用来定义接口返回的JSON结构体
- interface
用来声明变量类型
- routes
用来暴露对外的API接口
- utils
作为工具函数的文件目录
- app.ts
用来构建整个app,将各种需要提前处理的集中处理
- index.ts
用来作为整个项目的入口文件
当前节点用到的插件
入口文件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,这样就不用,每次单独写了
安装依赖
```
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
到这里已经可以部署项目成功了,接着我们通过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
就可以了
八、项目中使用的插件
- 从0开始Typescript Express Sequelize Swagger PM2 Docker 搭建部署后端服务
- 如何从0设计开发一款JS-SDK
- 聊聊中后台产研一体化:引子
- 面向中后台复杂场景的低代码实践思路
- 聊聊中后台前端应用:上下文的那些事儿
- 聊聊中后台前端应用:业务中的组件体系
- 聊聊中后台前端应用:模块相关的一些事
- tomcat配置虚拟路径,可以解决实际开发中测试时前端访问后台电脑上的图片的问题
- 在微信框架模块中,基于Vue&Element前端的后台管理功能介绍
- 聊聊中后台前端应用:前言
- 【以模块化的思想开发中后台项目】第一章
- 【建议追更】以模块化的思想来搭建中后台项目
- 前端传给后端的xml字符串被后台过滤掉,数据库中只保存值的问题
- 中后台前端搭建经验总结-技术细节篇(一)
- 中后台前端搭建经验总结-技术细节篇(一)
- 中后台前端的 Serverless 还能这么玩
- 如何借助社区能力快速构建中后台生态,飞猪中后台的探索和实践