Node.js 中间层我维护了两年,这周终于摊牌了——成本账单算完我人傻了

看到 "Node.js 中间层退潮" 上热榜的时候,我正在改 BFF 层的一个跨域 bug。缘,妙不可言 😂

背景

先交代一下,我在一个 20 人的创业团队做全栈,两年前入职第一件事就是搭 Node.js 中间层。

当时的理由很充分:

scss 复制代码
前端:React SPA
中间层:Node.js (Express) ← 我搭的
后端:Java 微服务 × 6

老板说前端不能直接调 Java,原因有三:

  1. 接口格式不统一------有的返回 {code, data},有的返回 {status, result}
  2. 跨域问题------6 个微服务 6 个域名
  3. 前端需要做接口聚合------一个页面要调 3-4 个接口拼数据

听起来确实需要一个 BFF 对吧?

我当时也这么想的。

两年后的真实状态

来看看我的 BFF 层现在长什么样:

bash 复制代码
src/
├── routes/          # 87 个路由文件
├── controllers/     # 87 个 controller(一一对应)
├── services/        # 92 个 service
├── middleware/       # 15 个中间件
├── utils/           # 各种 helper
└── config/          # 3 套环境配置

87 个路由。你没看错。

其中有多少是"有意义的聚合逻辑"?我数了一下:12 个

剩下的 75 个在干嘛?

javascript 复制代码
// 真实代码(脱敏过),占 BFF 总代码量的 85%
router.get('/api/users/:id', async (req, res) => {
  try {
    const result = await javaService.get(`/user-service/users/${req.params.id}`);
    res.json({ code: 0, data: result.data });
  } catch (err) {
    res.json({ code: -1, message: err.message });
  }
});

透传。纯透传。

85% 的代码就是把前端的请求原封不动转发给 Java,再把 Java 的响应原封不动还给前端。连参数校验都没做(Java 那边做了)。

我维护了两年的"中间层",本质上是个超大号的 nginx proxy_pass

算一笔账

这才是让我下定决心的原因。

服务器成本

BFF 层跑了 3 台 2C4G 的云服务器(高可用嘛),每台 ¥280/月:

ini 复制代码
BFF 服务器: ¥280 × 3 = ¥840/月
PM2 进程管理: 包含在内
Nginx + SSL: 包含在内
监控告警(Datadog): ¥200/月
──────────────────────
合计: ≈¥1,040/月

人力成本

这个才是大头:

markdown 复制代码
我每周花在 BFF 上的时间: ≈10 小时
  - 后端新增接口 → 我同步加 BFF 路由: 4h
  - Bug 修复 & 维护: 3h
  - 升级依赖 / 安全补丁: 2h
  - 写测试(虽然覆盖率一直上不去): 1h

按我的时薪折算: ≈¥1,500/周 = ¥6,000/月

一个月 ¥7,000+,维护一个"高级转发器"。

而且这还不算隐性成本------每次后端改了接口,我得同步改 BFF。经常出现「后端改了字段名忘了告诉我,前端页面挂了半天才发现」的情况。

这周动手了

热榜那篇文章让我下定了决心。我列了个清单:

1. 75 个纯透传路由 → Nginx 反向代理直接搞定

nginx 复制代码
# 原来 87 个 Node.js 路由文件 → 现在 15 行 nginx 配置
location /api/user-service/ {
    proxy_pass http://java-user-service:8080/;
    proxy_set_header X-Real-IP $remote_addr;
}

location /api/order-service/ {
    proxy_pass http://java-order-service:8081/;
    proxy_set_header X-Real-IP $remote_addr;
}

# ...6 个微服务全部直连

是的就这么简单。CORS?Nginx 加几行 header。认证?Java 那边本来就有鉴权中间件。

砍掉 BFF 的第一刀,省了 85% 的代码量。

2. 12 个聚合接口 → 前端用 Promise.all 自己聚合

typescript 复制代码
// 以前:BFF 聚合三个接口
// GET /api/dashboard → 内部调 user + order + statistics

// 现在:前端直接并发请求
const [user, orders, stats] = await Promise.all([
  fetch('/api/user-service/me'),
  fetch('/api/order-service/recent'),
  fetch('/api/stat-service/dashboard'),
]);

有人会说"这不就是把复杂度推给前端了吗?"

对,但前端框架现在处理这个太轻松了。React Query / SWR + 几行代码,还自带缓存和错误重试。比我手写的 BFF 聚合逻辑健壮得多。

3. 接口格式不统一?写个前端拦截器

typescript 复制代码
// axios 拦截器统一格式,10 行代码解决
api.interceptors.response.use((response) => {
  const data = response.data;
  // Java 服务 A: {code, data}
  // Java 服务 B: {status, result}
  return {
    success: (data.code === 0 || data.status === 'ok'),
    data: data.data || data.result,
    message: data.message || data.error,
  };
});

以前用 BFF 做的"格式统一",一个拦截器就搞定了。

重构结果

花了两天时间,结果:

makefile 复制代码
代码变化:
  删除: 87 个路由文件 + 92 个 service + 所有 BFF 代码
  新增: 15 行 Nginx 配置 + 1 个前端拦截器 + 3 个 Promise.all 聚合

服务器变化:
  关掉: 3 台 BFF 云服务器
  省钱: ¥1,040/月

人力变化:
  我每周在 BFF 上的时间: 10h → 0h
  后端改接口不用通知我了: priceless 😂

部署流程:
  之前: 前端 + BFF + 后端 = 3 个部署流水线
  现在: 前端 + 后端 = 2 个部署流水线

但是!不是所有中间层都该删

说几个真正需要 Node.js 中间层的场景:

1. SSR / SSG --- Next.js、Nuxt 这类框架本身就是 Node.js 中间层,这个有真实价值

2. 复杂的 BFF 编排 --- 如果你的聚合逻辑涉及条件分支、错误回退、事务补偿,前端处理确实吃力

3. AI API 代理 --- 这个是我新发现的场景,说来有点讽刺

我删掉 BFF 之后一个月,团队开始往产品里加 AI 功能。结果发现又需要一个"中间层"了:

javascript 复制代码
// 又特么需要代理了 😂
// 前端不能直接调 AI API(密钥暴露 + CORS)
router.post('/api/ai/chat', async (req, res) => {
  const result = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: req.body.messages,
  });
  res.json(result);
});

不过这次我学聪明了------没有自己搭 Node 服务,而是用了 API 聚合平台做代理。类似 ofox.ai 这种,统一一个 endpoint 调不同的模型,前端只需要配一个 base_url 就行,连 Node 中间层都省了。

typescript 复制代码
// 前端直接调(API Key 通过后端环境变量注入到 SSR 页面)
const client = new OpenAI({
  apiKey: process.env.NEXT_PUBLIC_AI_KEY,
  baseURL: 'https://api.ofox.ai/v1',
  dangerouslyAllowBrowser: true, // 仅限内部工具
});

当然如果是面向用户的产品,密钥肯定还是得走后端。但对内部工具来说,这种方案省了一整个服务。

给还在纠结的人几个建议

先审计你的中间层代码------数一下有多少路由是纯透传的。如果超过 60%,认真考虑砍掉。

别因为"万一以后需要"而保留------YAGNI 原则。我在 BFF 层留了两年的"预留扩展点",一个都没用上。

分步来------不用一口气全砍。我是先灰度切了 10 个低风险接口,观察一周没问题,再切剩下的。

计算真实成本------不只是服务器费用。你的时间才是最贵的。每周花在同步 BFF 路由上的时间,完全可以用来做更有价值的事。

考虑团队现状------如果你的后端接口真的很乱、没有统一规范,BFF 层可能还是必要的过渡方案。但目标应该是推动后端统一,而不是用 BFF 永远兜底。

最后

Node.js 中间层不是原罪。无脑加中间层才是。

两年前我搭 BFF 的时候确实解决了实际问题,但随着项目发展,它的维护成本已经远超收益。

热榜说的"从前端救星到成本噩梦",我的体会是:它确实当过救星,但别让它变成你的舒适区。

该摊牌的时候就摊牌吧 ✌️


全栈开发一枚,最近在折腾去中间层化。下一篇可能会写 Nginx 反向代理的完整配置方案,感兴趣的朋友关注一波 👀

相关推荐
None3211 天前
【NestJs】使用Winston+ELK分布式链路追踪日志采集
javascript·node.js
Dilettante2581 天前
这一招让 Node 后端服务启动速度提升 75%!
typescript·node.js
Mr_li2 天前
NestJS 集成 TypeORM 的最优解
node.js·nestjs
UIUV2 天前
node:child_process spawn 模块学习笔记
javascript·后端·node.js
前端付豪3 天前
Nest 项目小实践之注册登陆
前端·node.js·nestjs
天蓝色的鱼鱼3 天前
Node.js 中间层退潮:从“前端救星”到“成本噩梦”
前端·架构·node.js
codingWhat3 天前
uniapp 多地区、多平台、多环境打包方案
前端·架构·node.js
小p3 天前
nodejs学习: 服务器资源CPU、内存、硬盘
node.js
Mr_li3 天前
手摸手,教你如何优雅的书写 NestJS 服务配置
node.js·nestjs