给前端加一层 BFF:Node 中间层的第一步
写前端写久了,你大概率遇到过这些场景:
- 同一个页面要调 3~4 个后端服务,字段名都不一样。
- 为了兼容多个客户端(H5、小程序、App),接口被搞得非常「通用」,前端要做一堆字段转换。
- 某些数据其实可以缓存,但后端坚持不做,只能前端自己「临时记一下」。
这个时候,很多团队会在前后端之间加一层 BFF(Backend For Frontend)。
这篇文章以一个小例子来说明:什么是 BFF、为什么要用、第一步怎么做。
1. 什么是 BFF?
一句话解释:
BFF 是介于前端和后端业务服务之间的一层「适配器」,为特定前端提供定制化接口。
传统架构:
浏览器 / 小程序 ---> 各个后端服务
引入 BFF 后:
浏览器 / 小程序 ---> BFF ---> 各个后端服务
BFF 做的事情包括但不限于:
- 聚合多个后端接口;
- 做一些轻量的业务组合逻辑;
- 做权限校验、请求限流;
- 做缓存(比如热门数据);
- 为不同客户端做「视图模型」适配。
常用技术栈:Node.js + Koa/Express/Nest 等。
2. 一个具体例子:用户资产概览
假设我们有一个前端页面「用户资产总览」,需要展示:
- 用户基本信息(来自
user-service); - 用户账户列表(来自
account-service); - 用户最近 5 笔交易(来自
trade-service)。
2.1 没有 BFF 时的做法
前端直接请求多个后端服务:
GET /user-service/api/user/profile
GET /account-service/api/account/list
GET /trade-service/api/trade/latest?limit=5
前端需要:
- 处理不同的返回格式;
- 做错误合并处理(任何一个接口失败都要考虑);
- 处理多次往返延迟。
2.2 有 BFF 后的做法
前端只请求 BFF:
GET /bff/api/dashboard/overview
BFF 内部:
// 示例用 Express
const express = require('express');
const axios = require('axios');
const app = express();
app.get('/bff/api/dashboard/overview', async (req, res) => {
const userId = req.query.userId;
try {
const [userRes, accountRes, tradeRes] = await Promise.all([
axios.get('http://user-service/api/user/profile', { params: { userId } }),
axios.get('http://account-service/api/account/list', { params: { userId } }),
axios.get('http://trade-service/api/trade/latest', { params: { userId, limit: 5 } }),
]);
res.json({
code: 0,
data: {
user: userRes.data.data,
accounts: accountRes.data.data,
trades: tradeRes.data.data,
},
});
} catch (err) {
// 统一错误处理
console.error(err);
res.status(500).json({
code: 500,
message: 'INTERNAL_ERROR',
});
}
});
app.listen(3000, () => {
console.log('BFF listening on port 3000');
});
前端拿到的数据结构清晰统一:
{
"code": 0,
"data": {
"user": { /* ... */ },
"accounts": [ /* ... */ ],
"trades": [ /* ... */ ]
}
}
好处:
- 页面加载时只需要一次 HTTP 往返;
- 接口字段由 BFF 统一适配前端需求;
- 错误处理逻辑集中在 BFF。
3. BFF 的典型职责
3.1 字段转换与视图模型
后端返回的数据往往更偏「领域模型」,前端则需要更贴合 UI 的「视图模型」。
示例:
// user-service 返回
{
"userId": "u_123",
"userName": "张三",
"gmtCreate": 1599800000000
}
// BFF 转成视图模型
{
"id": "u_123",
"name": "张三",
"registerTime": "2020-09-11 10:00:00"
}
这样前端可以用更统一的字段名,不必到处写格式化逻辑。
3.2 权限与鉴权
用户信息、角色信息往往来自 Token / SSO 系统。
- BFF 可以在进入路由前统一解析 Token;
- 把
userId、roles、permissions注入到请求上下文; - 再根据这些信息去调用下游服务。
伪代码:
app.use(authMiddleware);
function authMiddleware(req, res, next) {
const token = req.headers['authorization'];
// 验证 token...
req.user = { id: 'u_123', roles: ['ADMIN'] };
next();
}
这样每个业务路由里就可以访问 req.user,不用每次重新解析。
3.3 简单缓存
对于某些读多写少、变化不频繁的数据,可以在 BFF 层做些缓存:
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 60 }); // 60s 缓存
app.get('/bff/api/configs', async (req, res) => {
const cacheKey = 'config_all';
const cached = cache.get(cacheKey);
if (cached) {
return res.json({ code: 0, data: cached, fromCache: true });
}
const remote = await axios.get('http://config-service/api/configs');
cache.set(cacheKey, remote.data.data);
res.json({ code: 0, data: remote.data.data, fromCache: false });
});
注意:
- BFF 缓存适合小数据量、短期缓存;
- 不要把所有东西都往缓存里塞,容易失控。
4. BFF 的一些实践经验
4.1 保持「薄而专一」
BFF 不应该变成另一个「巨型后端系统」,而应:
- 做轻量的聚合、转换;
- 尽量不承载复杂的业务规则(那些应该在后端领域服务里)。
一句话:BFF 更偏「视图层服务」,不是业务大脑。
4.2 与前端紧密演进
BFF 的改动节奏往往要跟前端保持一致:
- 新页面要上线 → 先在 BFF 加好接口 → 再接前端;
- 接口字段变更 → 优先在 BFF 做兼容 → 再慢慢调整前端。
有些团队甚至会把 BFF 代码和前端放在同一个仓库,用 Monorepo 管理,这样可以:
- 在同一个 PR 里改前端 + BFF;
- 保证版本一致性。
4.3 日志与监控
BFF 是前端的「最近一跳服务」,日志和监控非常关键:
- 打印清晰的访问日志(谁、在哪个接口、耗时);
- 对下游服务超时、错误情况做统计;
- 监控 BFF 自身错误率和性能。
例如简单的日志中间件:
app.use(async (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`[${req.method}] ${req.originalUrl} ${res.statusCode} - ${duration}ms`);
});
next();
});
5. 什么时候适合上 BFF?
个人经验是:当你同时满足以下 2~3 条时,就可以认真考虑:
- 有多个前端客户端(Web、App、小程序等)。
- 前端需要从多个后端服务取数据,并且组合逻辑越来越复杂。
- 经常需要为某个前端做特定改造,但又不方便动后端服务。
- 前端团队有一定的 Node 能力,能维护一层中间服务。
如果只是一个小项目、一个后端、几个简单接口,BFF 反而会增加复杂度,没有必要。
6. 小结
BFF 的核心价值可以概括成三点:
- 为前端屏蔽后端复杂性:聚合多个服务、统一字段、做简单逻辑。
- 为不同前端提供定制接口:H5、App、小程序可以有不同视图模型。
- 在前端和后端之间增加一层「缓冲区」:方便做灰度、缓存、限流、埋点等。
如果你已经在维护一个稍微复杂点的前端项目,建议可以从一个非常小的 BFF 开始:
- 先挑一个页面,把原本多个接口合成一个;
- 再慢慢把字段转换、鉴权、缓存等能力补上;
- 等团队习惯了这套模式,再考虑更系统化的架构。
BFF 不一定要一上来就「高大上」,从一个简单的 Node 应用开始就很好。