「技术教程」Nestjs 与Prisma打包部署插图

前言

Nestjs和Prisma的打包部署教程很少,网上比较热门的一个就是利用webpack将Nestjs打包 ,它和前端的打包差不多,会把所有的依赖项打包进来,生成的dist目录,可以直接单独部署,而如果我们使用Nestjs提供的build命令打包,不改动它的情况下,它只会将开发目录中的ts文件,转换成js文件,丢入dist目录中,其他什么也不做,就是个ts转js。

这估计会让大部分人诧异,如果遵循官方这种方式,你在启动node服务的时候就必须使用dist目录下的main.js文件了。但是还要考虑环境变量等文件,我们下面细说。

Prisma的打包部署需要考虑两方面:一是需要同步数据库模型,也就是如何使用开发时生成的迁移文件?二是需要生成后端服务使用的client客户端代码。

本次教程也不会搞那么复杂,就是传统的node服务部署,不会使用webpack打包特殊处理,你有兴趣可以自己百度搜索对应关键词就行。

部署代码

虽然nestjs会生成dist目录,但是我们并不能直接使用它,其实还是和koa,express部署差不多,我们需要将开发的代码都丢入生产环境中,虽然有点强迫症,觉得没必要把开发的文件都上传,但是为了方便,这样做是目前最优解,我们通过git仓库将开发代码上传,然后在生产环境git拉取。

可以通过环境变量来做一个简单的环境区分,我们开发的代码不会上传.env.production文件,这个文件需要生产环境自己创建。

比如我们现在创建一个.env.production文件

# 模式
NODE_ENV="production"
# prisma
DATABASE_URL="mysql://xxx:xxx@localhost:3306/nest-blog"
# hash-saltOrRounds
HASH_SALT_OR_ROUNDS="12"
# token-secret
TOKEN_SECRET="xxxxx"
# upload-path
UPLOAD_PATH="uploads"

现在我们生产环境所需要的文件都有了,就开始安装依赖:

pnpm i

Nestjs 打包

我们在创建Nestjs项目的时候,官方预设了一个build命令。

package.json

{
  "scripts": {
    "build": "nest build"
  }
}

这个命令会生成dist目录,里面是ts转js后的文件。

然后官方其实还预设了一些start命令用于启动服务。

{
  "scripts": {
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main"
  }
}

nest start 命令是通过 CLI 工具启动应用程序的标准方式,它会根据nest-cli.jsontsconfig.json配置,来运行src/main.ts文件。

所以前三个命令其实都是用于开发用的。

其中start用于单次启动服务,临时服务之类的。

start:dev则是开发使用,它加入了watch用于动态更新。

start:debug则是加入了debug的dev模式。

第四个start:prod则是用于启动生产环境下的应用程序,所以他是通过node 启动的dist目录下main文件。

但是,这个命令有点不对,因为现在的nest打包后,dist目录下存在一个src目录,在src目录下才会有main.js文件,所以我们需要改动一下。

{
  "scripts": {
    "start:prod": "node dist/src/main"
  }
}

第二个问题是,node启动服务,如果指定NODE_ENV,那么通过process.env.NODE_ENV得到的就是一个undefined,这显然是不行的,因为之前我们的ConfigModule中就用到了它,所以我们还需要显式指定,为此我们需要一个依赖cross-env

这个之前应该已经安装了,没有的话自己按一下:

pnpm i cross-env

然后命令调整:

{
  "scripts": {
    "start": "cross-env NODE_ENV=development nest start",
    "start:dev": "cross-env NODE_ENV=development nest start --watch",
    "start:debug": "cross-env NODE_ENV=development nest start --debug --watch",
    "start:prod": "cross-env NODE_ENV=production node dist/src/main"
  }
}

简单的启动命令就完事,如果需要使用PM2来实现持久化,我推荐是使用宝塔面板来帮助我们操作,或者你自己安装PM2,然后在项目根目录配置一个ecosystem.config.js文件,内容示例:

module.exports = {
  apps : [{
    name: 'my-nest-app',         // 应用程序的名称
    script: 'dist/src/main.js',   // 应用程序的启动脚本
    instances: 'max',             // 启动尽可能多的实例
    exec_mode: 'cluster',         // 启动集群模式
    env: {
      NODE_ENV: 'production'      // 环境变量
    }
  }]
};

然后用PM2启动这个文件:

pm2 start ecosystem.config.js

不想配文件可以使用如下命令:

pm2 start node --name "my-nest-app" -- NODE_ENV=production -- dist/src/main.js

也是可以的。

但是如果使用宝塔,可视化相对来说对于非专业运维,确实减少了不少麻烦。

「技术教程」Nestjs 与Prisma打包部署插图2

可视化配置一下省事很多,还能直接配置域名相关处理。

到这里就完事了,但是还不能直接启动,因为prisma还没弄呢。

Prisma部署

nestjs的build打包跟prisma其实没啥太大关系,所以不要关心dist目录下的prisma,那个完全用不到。

首先我们要保证项目根目录是有prisma目录的,这个目录是prisma自己init的时候生成的,然后我们在调整表结构模型schema.prisma文件之后,都会运行prisma migrate dev生成迁移文件,这个非常重要,一定要有迁移文件。

在这之前,我贴一下package.json中我配置的几个快捷命令:

{
  "scripts": {
    "prisma:migrate": "dotenv -e .env.development prisma migrate dev",
    "prisma:reset": "dotenv -e .env.development prisma migrate reset",
    "prisma:deploy": "dotenv -e .env.production prisma migrate deploy",
    "prisma:generate": "dotenv -e .env.production prisma generate"
  }
}

这里用到了dotenv依赖,没有的话自己安装一下。

pnpn i dotenv 

我通过dotenv 指定了环境变量文件,其中prisma:deployprisma:generate是用于生产环境,deploy命令会基于迁移文件去同步数据库,比如字段更新,创建数据库之类的。

然后generate生成@prisma/client依赖于node_modules目录下。

所以每次部署更新,都需要运行这两个命令。

pnpm run prisma:deploy

pnpm run prisma:generate

这样之后我们才可以去运行Nestjs项目,才不会爆prisma相关的错误。

我们在运行deploy命令时可能会遇到一个异常,大概率是权限不足的问题,可以通过chmod +x的形式。

比如我就碰到了如下报错:

 dotenv -e .env.production prisma migrate deploy

Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": MySQL database "nest-blog" at "localhost:3306"

Error: Schema engine exited. Error: Command failed with EACCES: /www/wwwroot/www.kod.com/nest-blog/node_modules/.pnpm/@prisma+engines@5.8.0/node_modules/@prisma/engines/schema-engine-debian-openssl-3.0.x cli --datasource <REDACTED> can-connect-to-database
spawn /www/wwwroot/www.kod.com/nest-blog/node_modules/.pnpm/@prisma+engines@5.8.0/node_modules/@prisma/engines/schema-engine-debian-openssl-3.0.x EACCES
 ELIFECYCLE  Command failed with exit code 1.

然后我通过运行:

chmod +x /www/wwwroot/www.kod.com/nest-blog/node_modules/.pnpm/@prisma+engines@5.8.0/node_modules/@prisma/engines/schema-engine-debian-openssl-3.0.x

将对应文件提权,然后在运行deploy就行了。

简化命令

每次部署的时候,都需要先build打包,打包后运行prisma的deploygenerate才可以运行后端服务,每次都输好几次命令,非常麻烦,我们可以进行简化处理。

安装一个合并命令依赖:

pnpm i npm-run-all2 -D

配置如下:

package.json

{
  "scripts": {
        "build": "nest build",
        "prisma:deploy": "dotenv -e .env.production prisma migrate deploy",
        "prisma:generate": "dotenv -e .env.production prisma generate",
        "build:prod": "run-s build prisma:deploy prisma:generate"
    },
}

创建了build:prod命令,这个名字可以自己取,然后通过run-s顺序运行对应的命令即可。

之后每次项目更新,直接运行build:prod就可以一键完成打包,同步数据库模型,生成新的prisma客户端。