- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
导读
在 Node.js + Express 项目中,"应用"与"服务器"是两条很容易被混写在一起的逻辑:路由声明、中间件挂载、端口监听常常挤在同一个文件里。本指南基于 nodebestpractices 仓库"项目结构"章节的官方实践,系统讲解如何将API 声明(app)与网络配置(server,即端口、协议等)彻底分离,并借助这种分离实现不发起真实网络请求的"进程内测试",从而获得更快的测试执行、可靠的代码覆盖率指标以及更灵活的多环境部署能力。读完本文,你将掌握 Express 应用/服务器分层拆分的标准写法、/bin/www启动入口的组织方式,以及基于 supertest 的进程内测试完整范式。
为什么必须分离 "app" 与 "server"
最新版 Express 生成器(express-generator)自带了一个值得长期保留的工程实践:API 的声明与网络相关的配置(端口、协议等)完全解耦。这对应仓库中的最佳实践文档 separateexpress.basque.md(其多语言版本见 separateexpress.chinese.md、separateexpress.french.md、separateexpress.japanese.md、separateexpress.russian.md)。
这样拆分的收益是结构性的,而非单纯的"代码整洁":
- 进程内(in-process)测试成为可能:API 应用对象(
app)本身只是一个"请求处理函数",不需要绑定端口即可被测试框架直接驱动。测试不再需要拉起真实端口、发出真实网络调用,从而大幅缩短测试执行时间,并能直接获取代码覆盖率指标。 - 同一份 API 可部署到多种网络环境:因为端口、协议等网络参数被抽离到独立入口,同一个
app可以在本地开发、CI 冒烟、灰度集群、生产环境等不同网络条件下复用,只需调整入口配置。 - 更好的关注点分离(Separation of Concerns):路由/中间件/业务逻辑与网络 I/O 边界清晰,代码结构更干净,也更容易被审查与维护。
第一层:API 声明放在 app.js / app.ts
应用的职责是"描述 API 长什么样":挂载哪些中间件、注册哪些路由。它不应该知道自己监听在哪个端口、走 HTTP 还是 HTTPS。
// app.js —— 只声明 API,不启动监听 const app = express(); app.use(bodyParser.json()); app.use("/api/events", events.API); app.use("/api/forms", forms);对应 TypeScript 写法结构完全一致,仅增加类型标注(const app = express();后同样通过app.use(...)挂载中间件与路由模块)。这里有几个值得注意的设计点:
- 中间件与路由全部以
app.use()显式注册,形成一条可读的"装配清单",这是 Express 应用层的核心行为; - 路由被组织成模块(如
events.API、forms),app.js只负责挂载而不负责实现,这与仓库中"按组件拆分结构"的实践一脉相承(见 breakintcomponents.md); app.js中绝不出现app.listen()或端口常量——这正是它与"服务器层"的分界线。
作为反面对照,可以观察仓库内 Docker 示例的写法 app.ts,其中app.listen(3000, ...)被直接写在应用文件里:
app.get('/', (req, res) => { res.send('Hello World!') }) app.listen(3000, () => { console.log('Navigate to http://localhost:3000'); });这种写法在极简 demo 中可行,但一旦项目需要测试或部署到多环境,listen内嵌应用文件的弊端就会暴露:无法在测试中复用同一个app,端口也无法通过环境变量灵活切换。这正是"分离实践"要解决的问题。
第二层:服务器网络声明放在 /bin/www
服务器层只做一件事:把网络参数(端口、协议、HTTP 服务器)与应用对象组装起来并启动。在 Express 生成器约定的结构中,这层位于bin/www。
JavaScript 版本
const app = require('../app'); const http = require('http'); // 从环境变量获取端口,并存入 Express const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); // 创建 HTTP 服务器 const server = http.createServer(app);TypeScript 版本
import app from '../app'; import http from 'http'; // 从环境变量获取端口,并存入 Express const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); // 创建 HTTP 服务器 const server = http.createServer(app);逐行拆解这段"启动样板"的工程含义:
| 代码 | 作用与含义 |
|---|---|
require('../app')/import app from '../app' | 引用第一层构建好的 API 应用对象,服务器层对业务零感知 |
normalizePort(process.env.PORT \|\| '3000') | 端口优先取环境变量PORT,未设置时回退到默认值3000;normalizePort通常由 Express 生成器在bin/www中定义,负责把字符串端口解析为合法数值,并校验非法输入(若传入非数字则返回NaN以便后续报错退出) |
app.set('port', port) | 把端口以 Express 应用属性形式暂存,供后续server.listen(port)及状态输出使用 |
http.createServer(app) | 以app作为请求处理函数创建原生 HTTP 服务器——app本质上就是一个(req, res) => {}形态的函数,这正是它能被createServer直接接收、也能被测试框架直接调用的根本原因 |
这层之后通常还会紧跟server.listen(port)并监听error与listening事件(生成器默认实现),用于处理端口占用、优雅输出启动地址等。关键点在于:所有网络决策都被收敛在bin/www这一个入口文件内。
第三层:用 supertest 实现进程内(in-process)测试
分离结构最大的红利,是可以用流行的测试包supertest直接对app发起"假的 HTTP 请求",全程不发起真实网络调用、不占用端口。
JavaScript 版本
const request = require('supertest'); const app = express(); app.get('/user', (req, res) => { res.status(200).json({ name: 'tobi' }); }); request(app) .get('/user') .expect('Content-Type', /json/) .expect('Content-Length', '15') .expect(200) .end((err, res) => { if (err) throw err; });TypeScript 版本
import * as request from "supertest"; const app = express(); app.get('/user', (req: Request, res: Response) => { res.status(200).json({ name: 'tobi' }); }); request(app) .get('/user') .expect('Content-Type', /json/) .expect('Content-Length', '15') .expect(200) .end((err: Error) => { if (err) throw err; });这个测试模式的核心价值在于:
request(app)直接注入应用对象:supertest 在进程内部把请求交给 Express 处理链,等价于一个"零端口"的端到端调用;- 链式断言
expect:可以同时校验响应头(Content-Type、Content-Length)、状态码(200)与响应体,断言失败时通过end(err => { if (err) throw err })把错误抛给测试框架,保证用例失败可观测; - 与测试金字塔呼应:它恰好支撑了仓库"至少编写 API(组件)测试"的实践(见 README.md 中 Testing 章节),让 API 层测试无需依赖真实网络与真实端口。
需要说明:Content-Length: '15'是示例中{"name":"tobi"}序列化后的精确字节数,实际项目中更常见的做法是只断言Content-Type与状态码,再单独校验响应体内容。
与其他最佳实践的协同
"app 与 server 分离"并非孤立技巧,它与仓库中的多条实践互相咬合:
- 中间件隔离测试:既然 app 与网络解耦,中间件也可以进一步脱离完整应用单独测试——见 test-middlewares.md,其思路是用
node-mocks-http构造{req, res}假对象直接调用中间件并断言statusCode,与 supertest 的进程内测试哲学一致。 - 端口策略:仓库建议"生产环境固定端口、测试环境随机端口"(见 randomize-port)。这与本实践天然互补——因为端口被收拢在
bin/www,测试完全可以绕过它、直接把app交给 supertest,从而彻底规避端口冲突。 - 测试命名与结构:为每个测试用例采用三段式命名与 AAA 结构(3-parts-in-name.md、aaa.md),能让进程内测试套件更易维护。
- 分层与组件化:app 层只做装配、业务下沉到组件,可参考 createlayers.md 与 breakintcomponents.md,本实践正是"层"的边界在 Express 入口处的落地。
落地建议与小结
在真实项目中应用这套实践的推荐步骤:
- 把中间件与路由装配收敛到
app.js/app.ts,不在此文件出现任何listen或端口常量; - 新建
bin/www(或server.js)承载端口解析、app.set('port', ...)、http.createServer(app)与listen,端口一律从process.env.PORT读取并设置默认值; - 测试文件直接
require('../app')并交给 supertest 做进程内断言,不再关心端口与网络; - 部署脚本只需配置
PORT环境变量即可切换监听环境,同一份app走遍开发、CI 与生产。
一句话总结:"应用描述 API,服务器决定网络"——这一条来自 nodebestpractices 的架构实践,用最小代价换来可测试性、可移植性与清晰的关注点边界,是任何 Express 项目都值得从第一天开始遵守的基线规范。
- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
相关推荐
styled-components 服务端样式无法被客户端领养(unadoptable server styles)的开发警告:成因、触发范围与正确修复方式
styled components 服务端样式无法被客户端领养(unadoptable server styles)的开发警告:成因、触发范围与正确修复方式 本
文档教程后端Koel技术架构深度解析:前后端分离的最佳实践
Koel技术架构深度解析:前后端分离的最佳实践 本文深入分析了Koel音乐流媒体服务器的技术架构,重点探讨了其采用前后端分离架构的最佳实践。Koel后端基于La
音视频后端前端在 Raspberry Pi 上使用 Grove VL53L0X 飞行时间距离传感器实现水果质检触发(IoT-For-Beginners 制造项目实战)
在 Raspberry Pi 上使用 Grove VL53L0X 飞行时间距离传感器实现水果质检触发(IoT For Beginners 制造项目实战) 本指南
文档教程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考