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的起起落落,欢迎在评论区聊聊你的故事。

相关推荐
xiaofeichaichai2 小时前
Webpack
前端·webpack·node.js
Thecozzy2 小时前
线上 Bug 排查与修复实录
架构
鹏大师运维2 小时前
为什么信创电脑装软件总提示“软件包架构不匹配”?
linux·运维·架构·国产化·麒麟·deb·统信uos
问心无愧05132 小时前
ctf show web入门111
android·前端·笔记
唐某人丶3 小时前
模型越来越强,我们还需要 Agent 工程吗?—— 从价值重估到 Harness 实践
前端·agent·ai编程
智码看视界3 小时前
现代Web开发基础:全栈工程师的起航点
前端·后端·c5全栈
JS菌3 小时前
手写一个 AI Agent 全栈项目:从沙箱执行到子智能体的完整实现
前端·人工智能·后端
excel4 小时前
HLS TS 文件损坏的元凶:Git 提交与拉取
前端
Aphasia3114 小时前
https连接传输流程
前端·面试