Node.js 中间层退潮:从“前端救星”到“成本噩梦”

如果你和我一样,是2016年前后入行的前端,一定记得那个热血沸腾的年代。

那时候,前端圈最响亮的口号是:"Node.js是前端的后端"。我们兴奋地讨论BFF、大前端,仿佛看到了前端工程师的未来------不再被后端牵着鼻子走,自己掌控整个数据链路

我也是在那时候,第一次用Node.js搭起了BFF层。那种"前端也能写后端"的掌控感,至今难忘。

然而最近两年,风向变了。

很多中大厂都在悄悄"回退"Node.js中间层。有的把逻辑收归后端,有的切到Serverless,有的干脆砍掉整个BFF。曾经自豪的技术栈,怎么就成了"成本中心"?

今天,想站在咱们前端的视角,聊聊这场"退潮"背后的真实故事。

一、当年我们为什么对BFF如此着迷?

因为,我们真的受够了

回想一下没有BFF的日子有多痛苦:

产品经理说:"详情页需要展示用户昵称、订单金额、商品列表。"

你打开接口文档,发现要调三个接口:/user/info/order/detail/product/list。三个接口调完,还要自己拼数据。

javascript 复制代码
// 没有BFF时,前端要自己聚合数据
async function getOrderPage(orderId) {
  // 串行调用三个接口
  const user = await fetch(`/api/user/info?userId=123`);
  const order = await fetch(`/api/order/detail?orderId=${orderId}`);
  const products = await fetch(`/api/product/list?orderId=${orderId}`);
  
  // 手动拼数据
  return {
    userName: user.name,
    orderAmount: order.amount,
    productList: products
  };
}

更崩溃的是,App端要的字段和Web端不一样。后端说:"你们前端能不能统一一下?"

你心里一万只羊驼跑过:"明明是你接口设计不合理,怪我咯?"

BFF给了我们"全栈"的尊严

Node.js BFF的出现,像是给前端打开了一扇窗。

javascript 复制代码
// BFF层:数据聚合、裁剪、适配
router.get('/web/order/detail', async (ctx) => {
  // 并行调用,性能更好
  const [user, order, products] = await Promise.all([
    fetchUser(ctx.query.userId),
    fetchOrder(ctx.query.orderId),
    fetchProducts(ctx.query.orderId)
  ]);
  
  // 为Web端定制返回格式
  ctx.body = {
    userInfo: { name: user.name, avatar: user.avatar },
    orderInfo: { amount: order.amount, status: order.status },
    productList: products.map(p => ({ id: p.id, name: p.name, price: p.price }))
  };
});

// 为App端返回精简数据
router.get('/app/order/detail', async (ctx) => {
  // 同样的数据来源,不同的返回结构
});
  • 后端继续提供原子接口,保持他们所谓的"纯洁"
  • 我们在Node层做聚合、裁剪、适配
  • 前端只调Node层,拿到的就是"刚刚好"的数据

更重要的是,不用再求后端改接口了

字段名不对?Node层改一下。缺少数据?Node层调个新接口。响应太慢?Node层加个缓存。

javascript 复制代码
// 后端接口字段名不合理?BFF层一键改写
const user = await fetchUser(userId);
// 后端返回的是 user_name,前端要的是 userName
return { userName: user.user_name };

那种"自己说了算"的感觉,太爽了。

二、蜜月期过后,我们开始尝到苦果

但架构是有代价的,只是这个代价,当时我们没算清楚。

运维噩梦:第一个周末被叫起来修服务器的滋味

我记得特别清楚,那是2019年的一个周六早上。

手机突然狂震,群里炸了:线上订单页打不开了。我迷迷糊糊爬起来,登录服务器,发现Node进程挂了。重启,又挂。再看,内存泄露。

bash 复制代码
# 前端不熟悉的运维命令
top # 看CPU
free -m # 看内存
tail -f /var/log/nginx/error.log # 看nginx日志
journalctl -u node-app # 看系统日志

那天我在电脑前蹲了四个小时,查日志、看监控、dump内存快照...最后发现是一个第三方SDK有bug。

作为一个前端,我擅长的是CSS布局、组件通信、状态管理。服务器的负载均衡、内存监控、日志采集,这些我根本不熟。

但因为是"前端负责的BFF",出了问题,只能自己扛。

重复劳动:每个项目都在写一样的代码

后来公司扩张,业务线越来越多。每条线都要BFF,于是我们建了一套又一套。

打开代码库,惊人的相似:

javascript 复制代码
// 业务线A的BFF
router.get('/a/order/detail', async (ctx) => {
  const data = await fetchData();
  return { code: 0, data };
});

// 业务线B的BFF
router.get('/b/order/detail', async (ctx) => {
  const data = await fetchData(); // 几乎一样的逻辑
  return { code: 0, data };
});

// 业务线C的BFF
router.get('/c/order/detail', async (ctx) => {
  const data = await fetchData(); // 又一遍
  return { code: 0, data };
});

这种重复劳动,本质上是在浪费我们前端的价值。我们本该花时间研究组件复用、性能优化、用户体验,结果天天在写重复的数据聚合代码。

"数据对不上"的锅,永远是我们背

最憋屈的是扯皮的时候。

前端调BFF接口,返回的数据缺字段。产品问:谁的问题?

后端说:"我接口返回了,你自己去看。" BFF说:"我透传了,没动过。" 最后查出来,是后端某个服务升级,字段名改了。

javascript 复制代码
// 后端悄悄改了字段名,BFF层还在用旧的
// 后端返回:{ nickname: '张三' }
// BFF层还在用:user.name
// 前端收到:undefined

但沟通成本已经花了,时间已经耽误了,项目已经延期了。

三、杀死BFF的,不是后端,是新技术

如果说内部问题是"慢性病",那新技术的出现,就是对BFF的"降维打击"。

Serverless:终于不用半夜修服务器了

我第一次接触Serverless,是帮朋友搞一个小程序。

javascript 复制代码
// 云函数版本的BFF
exports.main = async (event, context) => {
  const { userId, orderId } = event.query;
  
  // 一样的聚合逻辑
  const [user, order] = await Promise.all([
    fetchUser(userId),
    fetchOrder(orderId)
  ]);
  
  return { user, order };
};

不用买机器、不用配nginx、不用考虑扩缩容。写完代码,serverless deploy,完事。出问题了?看日志,改代码,再部署。全程不用碰服务器

而且成本低得惊人。以前BFF服务器7x24小时运行,半夜没人访问也在烧钱。Serverless按调用次数计费,低流量时期几乎不花钱。

javascript 复制代码
// 传统BFF:一直运行
app.listen(3000, () => {
  console.log('server running'); // 半夜也在运行
});

// Serverless:按需启动
exports.handler = async (event) => {
  // 有请求才执行,执行完就销毁
  return { statusCode: 200, body: 'hello' };
};

GraphQL:让前后端"吵架"变少了

GraphQL刚出来时,我们觉得它不就是BFF的另一种形式吗?但用了一段时间才发现,最大的改变是:前后端终于有了一份清晰的"契约"

graphql 复制代码
# 前端声明要什么
query {
  order(id: "123") {
    amount
    status
    user {
      name
      avatar
    }
    products {
      name
      price
    }
  }
}
javascript 复制代码
// GraphQL resolver:聚合逻辑还在,但契约更清晰了
const resolvers = {
  Order: {
    user: (order) => fetchUser(order.userId),
    products: (order) => fetchProducts(order.id)
  }
};

以前调BFF接口,返回什么全靠看代码、靠猜。用GraphQL,前端明确声明要哪些字段,返回的数据结构是强类型的,IDE里还有智能提示。

后端终于"开窍"了

这几年,后端也在变化。

javascript 复制代码
// 以前:后端坚持原子接口
GET /user/123
GET /orders?userId=123
GET /products?orderId=456

// 现在:后端提供聚合接口
GET /web/profile?userId=123
// 返回:{ user: {...}, recentOrders: [...], favoriteProducts: [...] }

后端团队也开始重视文档、规范字段命名、保证数据契约的稳定性。前端对BFF的依赖,自然就降低了。

四、但我们真的做错了吗?

写到这里,可能会觉得BFF是一个"错误的选择"。

但我想说:在那个时间点,BFF就是最好的解。

BFF解决了当时最痛的三个问题:

  1. 不用调N个接口了:一次请求,拿到所有数据
  2. 不同端可以定制数据了:Web、App、小程序各取所需
  3. 不用求后端改接口了:我们自己能改

对于我们前端来说,BFF给了我们更大的话语权和自主权。它让我们从"切图仔"变成了"能掌控数据链路的人"。

这段经历,也让我们学会了后端思维:缓存、并发、熔断、限流...这些知识,现在依然在用。

五、今天,我们前端该怎么玩?

如果你问我,现在要不要学Node.js中间层,我的答案是:要学,但不是以前那种玩法。

优先拥抱Serverless

javascript 复制代码
// 传统BFF
const app = require('express')();
app.get('/api/order', async (req, res) => {
  // 业务逻辑
});
app.listen(3000);

// Serverless版本
exports.handler = async (event) => {
  // 同样的业务逻辑
  return { statusCode: 200, body: JSON.stringify(data) };
};

除非有特殊需求,否则优先用云函数。运维成本几乎为零,咱们前端可以真正专注于业务逻辑。

学GraphQL,但别只会写resolver

javascript 复制代码
// 理解GraphQL的设计思想
type User {
  id: ID!
  name: String!
  orders: [Order!]!
}

type Order {
  id: ID!
  amount: Float!
  status: String!
}

Schema优先、强类型契约、按需查询------这些思想,会让你对"前后端协作"有更深的理解。

把BFF当"学习后端思维"的跳板

即使以后不用BFF了,那段经历也是宝贵的。你学会了如何处理并发、如何设计缓存、如何做服务熔断、如何排查线上问题。

javascript 复制代码
// 这些能力依然有用
Promise.all([fetchA(), fetchB(), fetchC()]); // 并发控制
node --inspect-brk app.js // 调试技巧

这些能力,会让你成为"更懂后端"的前端,在协作中更有话语权。

六、写在最后

技术的世界,没有永恒的真理,只有不断变化的语境。

BFF从崛起到回落,不是一个失败的故事,而是一个成长的印记。它见证了前端从"切图"到"全栈"的探索,也见证了架构演进的必然规律。

对于我们每个亲身经历过的人来说,重要的是:不要停留在过去的荣光里,也不要否定曾经的探索

保持学习,保持思考,保持对新技术的好奇。

这才是我们前端最宝贵的品质。


如果你也经历过BFF的起起落落,欢迎在评论区聊聊你的故事。

相关推荐
货拉拉技术2 小时前
如何用 AI 做业务级 Code Review
前端·agent·前端工程化
李剑一2 小时前
前端圈子又出新东西了,大幅提升解析速度。尤雨溪推荐,但我不太推荐
前端
青屿ovo2 小时前
Vue前端页面版本检测解决方案
前端·vue.js
front_2 小时前
React Hook介绍
前端
HashTang2 小时前
【AI 编程实战】第 12 篇:从 0 到 1 的回顾 - 项目总结与 AI 协作心得
前端·uni-app·ai编程
狂炫冰美式2 小时前
把手从键盘上抬起来:AI 编程的 3 个不可逆阶段
前端·后端·ai编程
codingWhat3 小时前
uniapp 多地区、多平台、多环境打包方案
前端·架构·node.js
HelloReader3 小时前
从 Tauri 2.0 Beta 升级到 2.0 Release Candidate Capabilities 权限前缀与内置 Dev Server 网络策略变
前端
RoyLin3 小时前
20 行代码,构建 Claude Code 核心能力
架构·agent