Comece de 0 Typescript + Express + Sequelize + Swagger + PM2 + Docker para construir e implantar serviços de back-end

Para as necessidades do projeto mais recente, o serviço de back-end do Node foi criado para registrar todo o processo. Não explicarei o uso de cada plug-in em detalhes aqui, mas apenas explicarei o processo. Para obter detalhes, você pode consultar o site oficial do ferramenta correspondente.

Ambiente do projeto:

  • OS: m1、
  • nó: 17,9
  • Imagem Dcoekr: node:18-alpine
  • Ambiente de implantação: centos

Primeiro, o projeto de inicialização

1. Novo projeto

express-demo
npm init
复制代码

2. Instale as dependências necessárias

yarn add typescript ts-node @types/node @types/express cross-env nodemon -D
yarn add express
复制代码

3. Configure tsconfig.json, configuração geral

{
    "compilerOptions": {
      "target": "es2017",
      "module": "commonjs", //通过commonjs处理模块化
      "rootDir": "./",
      "outDir": "./dist",
      "esModuleInterop": true,
      "baseUrl": "src",
      "strict": true,
      "strictPropertyInitialization": false // 不用严格要求值的初始化
    },
    "exclude": ["node_modules"]
}
复制代码

4. Crie um novo diretório no projeto srcpara armazenar os arquivos de origem. A estrutura de diretórios do projeto é a seguinte:

image.png

O diretório principal é:

  • controllersControlador, usado principalmente para lidar com a lógica da API
  • modelsmodelo, modelo de tabela de banco de dados
  • servicesAPI para manipular o banco de dados
  • databasesConfiguração e inicialização relacionadas ao banco de dados

Então, com base nisso, também temos alguns diretórios auxiliares

  • configUsado para obter variáveis ​​de ambiente passadas externamente ou parâmetros de banco de dados configurados
  • exceptionsUsado para definir a estrutura JSON retornada pela interface
  • interfaceUsado para declarar tipos de variáveis
  • routesUsado para expor a interface de API externa
  • utilsDiretório de arquivos como função de utilidade
  • app.tsEle é usado para construir todo o aplicativo e centralizar todos os tipos de processamento que precisam ser processados ​​​​com antecedência
  • index.tsUsado como o arquivo de entrada para todo o projeto

Plugins usados ​​pelo nó atual

  • nodemon mantém atualizações quentes durante o desenvolvimento reiniciando automaticamente o nó quando detecta alterações de arquivo em um diretório
  • cross-env é usado para definir variáveis ​​de ambiente através da linha de comando, distinguir o ambiente de desenvolvimento do ambiente de produção

Arquivo de entrada index.ts, usamos para introduzir rotas e iniciar serviços

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

const app = new App([new monitorRouter()])
app.listen()
复制代码

app.ts, quando o aplicativo for instanciado, para se conectar ao banco de dados, inicializar roteamento, middleware, documentos, etc., defina o método aqui primeiro

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
复制代码

2. Inicialize a rota

Classe de roteamento, para que a instanciação da rota seja introduzida em index.ts, todas as rotas tenham efeito, e então vemos que cada rota corresponde ao método do controlador, desde que esses métodos sejam implementados

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;
复制代码

3. Inicialize o banco de dados

yarn add sequelize mysql2
复制代码

Instale duas dependências, uma é o orm que opera o banco de dados e a outra é o driver do banco de dados.

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文件,配置变量

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文件,生成对应的路由,然后在项目初始化的时候执行该函数

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(可以直接通过注释生成,需要自行查看文档)

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,这里附一份配置,具体的内容可以查看文档

{
  "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

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构建镜像并部署

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 就可以了

八、项目中使用的插件

おすすめ

転載: juejin.im/post/7120074311926284296