拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Node+Express+MySQL脚手架:连接池、路由与避坑实践

Node+Express+MySQL脚手架:连接池、路由与避坑实践

简介:这是一份基于 Node.js、Express 和 MySQL 的快速开发脚手架,面向需要快速搭建后端服务的 Node 开发者,可解决项目初始化慢、目录结构混乱、重复配置等痛点,适合从零启动 RESTful API、管理系统或 Web 应用后端时直接复用。压缩包共含 31 个文件,其中 17 个 JS 文件承载核心业务逻辑,JSON 与 YAML 负责依赖与运行配置,Markdown 文档提供使用说明,HTML 文件可作为接口测试或示例页面,整体压缩包仅 37KB,轻量精炼,目前已有 31 人学习下载。脚手架预设了标准化目录结构,将数据库连接、SQL 操作、缓存读取、接口调用、实体映射等通用能力封装为独立模块,并内置统一基础配置与项目文档,开发者只需按业务需求修改扩展,即可快速得到稳定可维护的后端基础框架,显著缩短项目启动周期,也有助于团队保持一致的开发环境与代码风格。

1. 先聊清楚:这个脚手架到底解决什么问题

接手任何一个新后端服务,最磨人的往往不是业务逻辑,而是把环境、目录、连接池、路由这些“地基”重新铺一遍。node 装哪个版本、mysql 用什么认证方式、连接池怎么设、路由文件怎么挂,每换一台电脑就重来一次,而且每次都会在完全想不到的地方翻车。这套基于 node + express + mysql 的脚手架,就是把从零搭一个可维护后端的最小骨架打包成一份能直接解压使用的工程模板。

它解决的是“项目第一行代码之前”的那段空白:解压后你能得到一个带连接池、统一路由、建表脚本和错误处理的项目结构,改几个环境变量就能跑通第一个接口。适合刚转 node 后端的前端工程师,也适合想把项目从单文件 app.js 里拆出来的初中级开发者。真正值钱的不是那几十个文件,而是这些环境决策已经被提前做完了。

2. 脚手架目录为什么这样分:把“能跑”变成“能撑住业务”

2.1 从一个能跑的服务到能撑住业务的服务,差在哪

很多人第一次用 express 写接口,就是一个 app.js 从头写到尾:路由、查询、JSON 返回全堆在一起。三个接口以内很爽,十个接口之后开始痛苦,五十个接口之后就是灾难。这不是代码风格问题,是职责边界问题。express 本身不限制你怎么组织文件,它只提供路由和中间件机制,所以脚手架要解决的第一个问题,就是把“能跑”和“能撑住业务”之间的差距补上。

差距主要体现在三个方面:第一,数据库连接如果每个文件都自己建,连接数会失控,所以需要全局唯一的连接池;第二,路由如果散落在各个文件里手动挂载,排查接口时找不到入口,所以需要统一的路由注册中心;第三,错误处理如果没有兜底中间件,任何一个异步报错都会让进程直接退出,所以必须有一层全局错误捕获。这些不是业务功能,但缺了任何一个,项目都会在某个阶段被迫推倒重来。

我一般会把脚手架拆成“入口、配置、路由、数据访问、基础设施”五个层面。入口负责启动服务;配置负责读环境变量;路由负责定义 URL 映射;数据访问负责与 mysql 打交道;基础设施是中间件、错误处理、工具函数这类横切关注点。这样当你需要在已有项目里加新模块时,只需要往 routes 和 services 里各加一个文件,其他什么都不用动。

2.2 目录结构:按职责切分而不是按文件类型堆

这份脚手架的目录结构大致长这样:

project/ ├─ app.js ├─ package.json ├─ .env.example ├─ src/ │ ├─ config/ │ │ ├─ db.js │ │ └─ env.js │ ├─ routes/ │ │ ├─ index.js │ │ └─ user.js │ ├─ middlewares/ │ │ ├─ errorHandler.js │ │ └─ notFound.js │ ├─ services/ │ ├─ utils/ │ └─ app.js ├─ sql/ │ └─ schema.sql └─ scripts/ └─ check-db.js

注意这里出现了两个 app.js,一个是根目录的启动入口,一个是 src 下组装中间件和路由的应用实例。很多脚手架不区分这两者,导致测试时无法单独导入应用,只能真的把端口监听起来。拆开之后,你可以在不监听端口的情况下用 supertest 直接打请求,这是后续自动化测试的基础,也是这个目录结构最值得保留的设计。

routes 里 index.js 负责汇总所有业务路由模块,user.js 是一个具体业务的示例。services 目录在初始模板里是空的,但保留它的目的是明确约定:复杂业务逻辑不要写在路由里,下沉到 service 层。中大型项目里你还会加 controllers 和 models,但脚手架不应该一开始就把这些空目录全建出来,空目录没有约定意义,反而让人困惑。让目录跟着真实需求长出来,比一开始铺一堆空壳更合理。

2.3 最小可运行目录:拿到就能 start 的结构

package.json 是脚手架的启动开关。这里有几个关键点需要注意:入口字段要指向根目录的 app.js,scripts 里要提供 dev 和 start 两个命令,依赖只需要 express、mysql2、dotenv,开发依赖加一个 nodemon 就够了。不要在一开始引入 sequelize 或 typeorm,ORM 会掩盖 sql 本身的行为,等你需要排查慢查询时,黑匣子会更多。mysql2 是 mysql 官方驱动的高性能版本,下面会专门讲。

{ "name": "node-express-mysql-starter", "version": "1.0.0", "main": "app.js", "scripts": { "dev": "nodemon app.js", "start": "node app.js", "check-db": "node scripts/check-db.js" }, "dependencies": { "dotenv": "^16.3.1", "express": "^4.18.2", "mysql2": "^3.6.0" }, "devDependencies": { "nodemon": "^3.0.1" } }

这里把版本号写成了带 ^ 的区间,实际解压后安装时会拉取当前满足区间的最新兼容版本。之所以选这三个依赖,是因为它们覆盖了脚手架的最小闭环:express 提供 http 层能力,mysql2 提供连接池和预处理语句,dotenv 把配置从代码里剥离出来。不要小看 dotenv,很多人把数据库密码直接写在 db.js 里然后提交到仓库,这是最常见的生产事故源头。

安装依赖和启动服务的命令如下:

npm install cp .env.example .env npm run dev

cp 命令在 Windows 的 cmd 下可能不生效,可以改成 copy 或者在编辑器里手动复制。第一次跑起来后你会在终端看到“server running at http://127.0.0.1:3000”这样的输出,这就是脚手架活了的标志。如果你在本机已经装过 mysql 并建好了库,现在就可以试着在浏览器里访问一个示例接口了。

3. 用连接池把 mysql 接进来:避免 2002 和 SSL 两个经典报错

3.1 为什么不用 mysql 默认连接而用连接池

node 的 mysql 生态里有两套驱动:mysql 和 mysql2。mysql 是老牌驱动,但它的 API 更古老,默认不支持 Promise,需要手动包装才能配合 async/await 使用;mysql2 在兼容 mysql 的同时原生支持 Promise,并且提供了 prepared statement 缓存,同样的查询在高频场景下性能更好。所以脚手架里选 mysql2 是当前的主流做法。

直接用 createConnection 每次查询都新建一个连接,然后用完再关闭,在高并发场景下会频繁握手,mysql 服务端的线程数量会被快速打满。连接池的本质是维护一组长期存活的连接,请求来了从池子里借一条,用完了还回去。这个机制对新手来说是黑匣子,但你必须理解它的几个参数,否则连接池不仅不解决问题,还会成为新的故障源。

比较常见的错误是认为连接池能无限扛并发。实际上连接池是资源复用而不是资源扩张,当连接数达到 connectionLimit 时,新的请求会进入等待队列,等待时间由 connectTimeout 和 acquireTimeout 决定。如果你把连接池调得过大,mysql 端会先撑不住;调得过小,高峰期请求会排队超时。下面这套配置是经验值,适用于绝大多数中小业务。

3.2 连接池与 mysql2 配置:连接数、超时、字符集

src/config/db.js 是整个数据库访问层的地基,脚手架的启动自检也是围绕它做的。完整代码如下:

const mysql = require('mysql2/promise'); const dotenv = require('dotenv'); dotenv.config(); const pool = mysql.createPool({ host: process.env.DB_HOST || '127.0.0.1', port: Number(process.env.DB_PORT || 3306), user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'app', waitForConnections: true, connectionLimit: 10, queueLimit: 0, charset: 'utf8mb4', timezone: '+08:00', enableKeepAlive: true, keepAliveInitialDelay: 0 }); async function query(sql, params) { const [rows] = await pool.execute(sql, params); return rows; } module.exports = { pool, query };

逻辑说明:这里导出两个东西,pool 和 query。pool 是连接池实例,给那些需要事务或手动管理连接的场景使用;query 是封装好的便捷函数,业务代码里直接写 sql 和参数数组,函数内部用 pool.execute 执行,并把结果集的 rows 取出返回。注意 execute 使用的是预处理语句,参数通过占位符传入,这能有效防止 sql 注入。

参数说明:connectionLimit 控制池中最大连接数,10 对于单机开发环境足够;queueLimit 设为 0 表示等待队列不设上限,这样即使高峰期连接被占满,请求也只会等待而不会直接报错;charset 必须用 utf8mb4 而不是 utf8,否则存 emoji 和特殊符号会变成问号;timezone 设置成 +08:00 是为了让读取 DATETIME 时按北京时间解析,否则 node 默认按服务器本地时区读取,容易出现差 8 小时的问题。

下面这张表列出了每个参数的推荐范围和影响面,方便你按项目规模调整:

参数推荐值说明
connectionLimit5-20小项目 5,中项目 10-15,再大就要考虑读写分离了
queueLimit00 表示不限制排队长度,生产环境建议设一个上限
connectTimeout10000 默认建立连接的超时时间,网络差时可调大
charsetutf8mb4必须用这个,utf8 存不了 emoji
timezone+08:00解决 mysql 时间读取差 8 小时的问题
enableKeepAlivetrue避免连接被 mysql 服务端空闲回收

3.3 初始化数据库表:把建表语句放进 sql/schema.sql

脚手架不能只给代码,不给数据底座。sql/schema.sql 里应该有一份最小可运行的建库建表语句,让使用者在本地初始化出与代码配套的表结构。以下是我固定放在脚手架里的初始结构:

CREATE DATABASE IF NOT EXISTS app DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE app; CREATE TABLE IF NOT EXISTS user ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT '昵称', age TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '年龄,默认0', status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1正常 0禁用', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

逻辑说明:先建库,再切库,再建表。建库时指定了默认字符集为 utf8mb4,排序规则为 utf8mb4_unicode_ci,这样建出来的表如果不单独指定字符集也会继承这个规则。user 表里设置了一个默认值为 0 的 age 字段和一个默认值为 1 的 status 字段,用来演示默认值在 mysql 里如何工作:插入数据时不传这两个字段,会自动落为初始值。

说明一下:age 用 TINYINT UNSIGNED 就够,因为 255 已经是年龄的物理上限;created_at 和 updated_at 直接交给数据库维护,避免应用层时间不一致。你在自己的项目里加表时,尽量保持这个风格:主键无符号自增、字符串有明确长度、时间字段由数据库生成。这份 sql 文件不需要在应用启动时自动执行,因为自动执行建表在生产环境是个隐患,正确做法是在开发环境手动执行一次,生产环境由 DBA 或迁移工具接管。

4. 把 express 的入口做成路由注册中心:API 落地的最小写法

4.1 app.js 的职责边界与中间件顺序

很多新手会把 app.use 写在 listen 之后,这是完全错误的。listen 只是启动端口监听,它不参与请求处理链路的组装,所以必须在 listen 之前把所有中间件和路由都挂到 app 上。脚手架的 src/app.js 专门负责这件事,根目录的 app.js 只做一件事:引入它然后监听端口。这样分层后,写自动化测试时可以直接 require src/app.js,而不用真的启动服务。

中间件的挂载顺序极其重要。express 的请求处理是按顺序层层穿透的,如果 express.json() 放在路由之后,请求体还没解析,路由里读到 req.body 就是 undefined;如果错误处理中间件放在路由之前,它根本捕获不到任何错误。所以标准顺序是:内置解析中间件在最前,然后挂载路由,最后放 404 兜底和错误处理。这个顺序是无数人踩坑换来的经验,不要随意调整。

const express = require('express'); const routes = require('./src/routes'); const { notFound } = require('./src/middlewares/notFound'); const { errorHandler } = require('./src/middlewares/errorHandler'); const dotenv = require('dotenv'); dotenv.config(); const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use('/api', routes); app.use(notFound); app.use(errorHandler); module.exports = app;

逻辑说明:express.json() 解析 Content-Type 为 application/json 的请求体,express.urlencoded 解析表单格式的请求体,两者是绝大多数接口的基础前置;app.use('/api', routes) 把所有路由统一挂到 /api 前缀下,这样业务路由里写 /user,最终访问路径就是 /api/user;notFound 兜底未被匹配的路径,errorHandler 捕获所有同步异常和 next(err) 传过来的异步异常。

参数说明:urlencoded 的 extended: true 表示允许解析嵌套对象,这是默认推荐值。如果设置为 false,表单里的嵌套结构会被解析成字符串,容易导致参数丢失。注意这里没有再引入 morgan 或 cors 这类中间件,脚手架保持最小依赖,你需要跨域时再安装 cors 也不迟,不要一上来就把所有中间件全装上。

4.2 路由文件的注册写法:从单文件到业务模块

routes/index.js 是路由注册中心。所有业务路由模块在这里被汇总,然后一次性挂到根路由上。这样你在新增一个业务模块时,只需要两步:新建一个路由文件,然后在 index.js 里加一行 app.use。不要用 fs 自动扫描目录来加载路由,虽然省事,但会让路由加载顺序变得隐式化,出问题时很难定位。

const express = require('express'); const router = express.Router(); const userRoutes = require('./user'); router.use('/user', userRoutes); module.exports = router;

逻辑说明:index.js 创建了一个新的 Router 实例,把 userRoutes 挂载到 /user 路径下。因为 src/app.js 里已经把这份 index 挂到了 /api,所以最终 URL 是 /api/user/xxx。这种嵌套挂载的方式让你可以在不同层级控制前缀,比在每个业务路由里写完整路径更干净。

参数说明:这里暂时只注册了 userRoutes,实际项目里会有 order、product、auth 等多个路由文件,每个文件都是一个独立的 Router 实例。路由文件内部只关注自身业务,不关心整体路径前缀,这是解耦的核心。

4.3 一个查询接口的完整链路:从路由到查询到 JSON 返回

routes/user.js 演示了一个最简单的查询接口。它包含三层内容:定义路由路径、调用查询函数、封装响应格式。这三层都写在同一文件里,是为了让你在脚手架阶段能看到完整链路。

const express = require('express'); const router = express.Router(); const { query } = require('../config/db'); router.get('/list', async (req, res, next) => { try { const list = await query( 'SELECT id, name, age, status, created_at FROM user ORDER BY id DESC', [] ); res.json({ code: 0, data: list, message: 'ok' }); } catch (err) { next(err); } }); router.post('/add', async (req, res, next) => { try { const { name, age } = req.body || {}; if (!name) { res.status(400).json({ code: 1, message: 'name is required' }); return; } const result = await query( 'INSERT INTO user (name, age) VALUES (?, ?)', [name, age || 0] ); res.json({ code: 0, data: { id: result.insertId }, message: 'ok' }); } catch (err) { next(err); } }); module.exports = router;

逻辑说明:/list 接口执行一条 SELECT 语句,用 ORDER BY id DESC 让新用户排前面,返回数组便于前端直接渲染;/add 接口接收 body 里的 name 和 age,用预处理语句的 ? 占位符做插入,注意这里对 name 做了必填校验,但是别在这里做复杂校验,简单判断可以写在路由里,复杂规则应该下沉到 service 层。

参数说明:查询函数 query 的第二个参数是参数数组,即使没有参数也要传一个空数组,这是为了保持调用格式统一,避免漏传参数时出现难以排查的 undefined 错误;result.insertId 是 mysql2 在执行 INSERT 后返回的自增主键,这是数据库行为,无需应用层额外查询。返回格式统一为 code 加 0 表示成功,非 0 表示失败,这样前端可以统一做处理,不用每换一个接口就适配一种响应体。

5. 避坑清单:Node 版本、MySQL 8 认证与 Windows 下的六个老大难

5.1 npm.ps1 无法加载,因为在此系统上禁止运行脚本

现象:在 Windows PowerShell 里执行 npm install 直接报错,提示“无法加载文件 D:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本”。

原因:PowerShell 的默认执行策略是 Restricted,禁止运行任何 .ps1 脚本文件。npm 的 PowerShell 包装脚本被拦截了,cmd 里跑正常,但 PowerShell 里跑就报错。这个热词的搜索量极高,因为它卡住了几乎每个 Windows 新手的第一次 node 体验。

解决:以管理员身份打开 PowerShell,执行以下命令把当前用户的执行策略调整为可运行本地脚本:

Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

RemoteSigned 的含义是:本地创建的脚本可以运行,从网络下载的脚本必须有可信数字签名。这是安全性和便利性之间的平衡点,不要图省事直接用 Unrestricted,那是把系统防护直接关掉。改完之后重新打开终端再跑 npm install 就不会再撞墙了。

5.2 ERROR 2002 (HY000):Can't connect to local MySQL server through socket '/tmp/mysql.sock'

现象:通过命令行连接 mysql 时报 2002 错误,提示通过 socket 连接失败。这个报错在热词里出现过,是 mysql 连接问题里搜索量最高的一条之一。

原因:mysql 客户端在 Linux 和 macOS 下默认通过 Unix socket 连接本机 mysql,而不是 TCP 端口。当 mysql 服务没有启动、或者 socket 文件路径不是默认的 /tmp/mysql.sock 时,就会出现这个错误。注意我们的脚手架配置里 host 用的 127.0.0.1,而不是 localhost,这正是为了绕过 socket 连接直接走 TCP,所以脚手架本身不会触发这个坑。

解决:第一步确认 mysql 服务是否在运行,Ubuntu 下用 systemctl status mysql,macOS 下用 brew services list;第二步如果服务在运行仍然报错,检查 my.cnf 里的 socket 路径配置;第三步紧急绕过时用“mysql -h 127.0.0.1 -P 3306”强制走 TCP 连接。这个坑的核心教训是:代码里的 localhost 和 127.0.0.1 有本质区别,前者可能触发 socket 连接,后者一定走 TCP。

5.3 MySQL 8 认证插件与旧客户端不兼容

现象:node 应用启动后一直报握手失败或认证失败,但在命令行里用 root 密码却能正常登录。密码明明是对的,代码里就是连不上。

原因:MySQL 8.0 默认创建的用户使用 caching_sha2_password 认证插件,而一些老版本的客户端驱动或依赖不支持这个插件,导致认证流程无法完成。尤其是当你用了过时的 mysql 驱动而不是 mysql2 时,这个问题出现概率极高。这也是为什么脚手架强制要求用 mysql2,因为它从 v3 开始完整支持 caching_sha2_password。

解决:两种方案。第一种是升级到 mysql2 驱动,这也是推荐方案;第二种在建用户时显式指定老认证插件:“CREATE USER 'app'@'%' IDENTIFIED WITH mysql_native_password BY 'password'”加上 GRANT 语句授权。新项目一定要选第一种,因为 mysql_native_password 在 MySQL 8.0.34 之后已被标记为废弃,你不想在新项目里用一个注定被移除的兼容模式。

5.4 Node 版本与操作系统不兼容:升级 node 时把系统搞崩

现象:用官方独立安装包安装了某个新版本 node,双击安装到一半提示“操作系统版本过低”或者“该 node 版本不兼容此操作系统”,然后安装回滚。

原因:Node 的高版本对操作系统有最低版本要求,比如超高版本要求在 Windows 10 特定版本以上。直接覆盖式安装新版本 node,不仅可能安装不上,还会把原本可用的环境弄乱。热词里“node历史版本”“node升级 windows”“nvm安装”这些高频搜索都指向同一个核心需求:用版本管理器管理 node,而不是用安装包覆盖。

解决:先卸载当前 node,再安装 nvm-windows,然后通过 nvm 安装指定版本:

nvm install 18.18.2 nvm use 18.18.2 node -v

参数说明:18 系列是当前兼容性最好的 long-term support 版本,如果你的 mysql、express 都是新版本,选它比追最新版可靠。用 nvm 的好处是切回旧项目时可以“nvm use 16”,不用反复卸载重装。血泪经验:永远不要让生产环境的 node 版本跟本地漂移,装 nvm 是性价比最高的第一步。

5.5 Windows 下安装 mysql 模块编译失败,缺 Visual C++ 运行库

现象:npm install 时出现 node-gyp 编译错误,报错信息里能看到“Visual Studio”“windows-build-tools”或“vcxproj”等字样,最终安装失败。

原因:某些 npm 包需要从源码编译原生模块,而 Windows 下编译依赖 Visual C++ Build Tools 和 Python。mysql 官方驱动不需要编译,但如果你或某个间接依赖用了需要编译的 addon,就会触发这个坑。错误信息会让你误以为是 mysql 装不上,其实是本机缺 C++ 编译链。

解决:两个路径。第一,确认依赖清单里没有这类包,脚手架里 mysql2 和 express 都是纯 JS 实现,不会触发编译;第二,确实需要编译时,以管理员身份安装 windows-build-tools 或者直接安装 Visual Studio Build Tools 并勾选“C++ 桌面开发”工作负载。新手在 Windows 遇到编译错误,优先排查是不是缺了这套运行库,而不是反复删 node_modules 重装,重装一百次也解决不了编译链缺失的问题。

5.6 mysql 连接池耗尽与 SSL 连接错误

现象:应用运行一段时间后,接口开始间歇性超时,日志里出现“ETIMEDOUT”或“ER_SECURE_TRANSPORT_REQUIRED”,mysql 连接数被占满,或者 SSL 连接握手失败。

原因:连接池里的空闲连接被 mysql 服务端强制回收,但池没有及时感知到,导致下一次请求拿到的是已经断开的连接。另一种常见情况是 mysql 服务端要求 SSL 连接,而驱动未启用 SSL。这些问题在开发环境不常出现,因为开发环境连接量少,空闲回收周期长;一旦部署到服务器,网络环境和连接波动会让问题快速暴露。

解决:在 createPool 配置里加上 enableKeepAlive 和 ssl 相关的设置。keepAlive 让连接保持活性,避免被服务端静默断开;如果服务端要求 SSL,加上“ssl: { rejectUnauthorized: false }”可以完成加密握手,但注意这个只在内部网络使用,生产环境应该配置正式的 CA 证书。另外不要在请求代码里手动调用 pool.end(),那会关闭整个连接池,把它当作全局单例对待,交给进程生命周期管理就够了。

6. 验证与收尾:启动自检、环境变量与上线前的两个习惯

脚手架给你的是骨架,但骨架是否接对了,需要一次启动自检来验证。我习惯在 scripts 目录放一个 check-db.js,它做的事很简单:从连接池拿一条连接,执行 SELECT 1,成功则退出 0,失败则退出 1。npm run check-db 脚本与它关联,部署时把它放在应用启动之前,数据库没就绪时绝不启动业务进程。

const { pool } = require('../src/config/db'); async function check() { const [rows] = await pool.query('SELECT 1 AS ok'); if (rows[0].ok !== 1) { throw new Error('db check failed'); } console.log('db connection ok'); process.exit(0); } check().catch((err) => { console.error('db check failed:', err.message); process.exit(1); });

逻辑说明:整个脚本只做连通性验证,不做建表不做迁移。因为建表是 DBA 的职责、迁移是发布流程的职责,这里的自检只是保证进程不在“数据库不可用”的状态下空跑。SELECT 1 是数据库连通性检测的通用写法,它不依赖任何业务表,即使业务库被清空也能正常检测。

环境变量方面,.env.example 是脚手架提供给你复制的模板,实际使用时复制成 .env 并填上真实值。db.js 和 app.js 里的取值逻辑都用了“环境变量优先、默认值兜底”的写法,这意味着你在本机不配置也能跑,但上线前必须把 .env 配置完整。生产环境建议用 systemd 或 pm2 拉取环境变量,不要把 .env 文件传到服务器上裸奔。

pm2 start app.js --name app -i 1

升级到 node 更高版本之前,先在 nvm 里切过去跑一遍自检脚本,再用真实请求打一遍接口。我经历过太多次“本地好好的,测试环境起不来”的尴尬,后来养成的习惯是:任何环境变更都先跑 npm run check-db,再 pm2 restart。这套脚手架把数据库连接收敛到了单独文件里,所以验证路径非常短,这也是它能在十分钟内让人确认“这个环境是可用的”的原因。希望这套从连接池到路由到自检的流程能帮你少从零趟一遍泥,把时间花在业务本身。

本文还有配套的精品资源,点击获取

返回列表