在 Web3(区块链/去中心化应用)的开发中,BFF(Backend For Frontend)同样扮演着非常关键的角色。虽然 Web3 提倡去中心化、前端直接通过 RPC 节点连接区块链,但完全去中心化的架构在用户体验(UX)和复杂业务场景下往往是灾难性的。
以下是 Web3 中常见的 BFF 应用场景,以及基于 Node.js (TypeScript) 的具体代码实现。
一、 Web3 中哪些场景会用到 BFF?
1. 链上与链下数据聚合(最核心场景)
- 痛点:在 NFT 交易市场或 DeFi 首页,前端需要展示用户的资产。这需要调用链上智能合约获取 Token 余额(通过 RPC),同时去关系型数据库(MySQL/MongoDB)获取用户的个人资料、头像、关注列表,还要去 IPFS 获取 NFT 的元数据。如果让前端直接发 4-5 个请求,页面加载会极慢。
- BFF 的作用:BFF 在服务端同时请求 RPC 节点、IPFS 和传统数据库,将数据聚合成一个干净的 JSON 一并返回给前端。
2. 多链数据聚合与适配
- 痛点:全链(Omnichain)应用需要同时展示用户在 Ethereum、Polygon、Arbitrum 等多条链上的资产。每个链的 RPC 节点不同,前端直接管理 4、5 个 RPC 连接不仅代码臃肿,还容易遭遇节点的速率限制(Rate Limit)。
- BFF 的作用:BFF 作为代理,在后台并发请求各条链,过滤并统一数据格式,前端只需要对接一个统一的 BFF 接口。
3. 高频数据缓存与降低 RPC 成本
- 痛点:公共 RPC 节点(如 Infura、Alchemy)是按请求次数计费的。如果前端直接高频轮询链上数据(如最新的流动性池价格),会导致天价的 RPC 账单,且节点响应慢。
- BFF 的作用:BFF 引入 Redis 缓存。对于不经常变动的链上数据(如 NFT 元数据、代币基本信息),或者高频访问的数据,BFF 缓存 5-10 秒,大幅降低 RPC 成本并秒级响应前端。
4. 气体费(Gas Fee)预估与中继交易(Gasless/Meta-Transaction)
- 痛点:为了让用户体验更好(如无 Gas 体验),通常采用元交易(Meta-Transaction),即用户只需在前端用钱包签名签名,由项目方出 Gas 费帮用户把交易推上链。
- BFF 的作用:前端将签名发送给 BFF,BFF 验证签名后,调用后端的私钥管理服务(KMS)或中继器(Relayer),替代用户将交易广播至区块链。
二、 具体代码实现
下面是一个典型的 Web3 BFF 实现。
场景 :前端需要一个接口 GET /api/user-dashboard/:address,展示用户的 ETH 余额(链上) 、NFT 持有数量(链上) 以及 平台内测积分(链下数据库)。
我们使用 Node.js + Express + ethers.js 来编写这个 BFF 层。
1. 环境准备
bash
npm init -y
npm install express ethers dotenv cors
npm install --save-dev typescript @types/express @types/node ts-node
2. BFF 代码实现 (server.ts)
typescript
import express, { Request, Response } from 'express';
import { ethers } from 'ethers';
import cors from 'cors';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
// 初始化以太坊 RPC 提供商(可以使用 Infura, Alchemy 或公共节点)
const INFURA_RPC_URL = process.env.RPC_URL || 'https://cloudflare-eth.com';
const provider = new ethers.JsonRpcProvider(INFURA_RPC_URL);
// 模拟的链下传统数据库查询函数
async function getOffChainPoints(address: string): Promise<number> {
// 实际开发中这里会是:await db.query('SELECT points FROM users WHERE ...')
return new Promise((resolve) => {
setTimeout(() => {
resolve(1250); // 假设该用户在平台有 1250 链下积分
}, 100);
});
}
// 简化的 ERC-721 (NFT) 合约 ABI,仅需 balanceOf 方法
const nftAbi = [
"function balanceOf(address owner) view returns (uint256)"
];
const NFT_CONTRACT_ADDRESS = '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D'; // 比如 Bored Ape Yacht Club 合约
/**
* BFF 聚合接口
* 聚合:1. 原生 ETH 余额 2. 特定 NFT 持有量 3. 链下系统积分
*/
app.get('/api/user-dashboard/:address', async (req: Request, res: Response): Promise<void> => {
const { address } = req.params;
// 校验以太坊地址格式
if (!ethers.isAddress(address)) {
res.status(400).json({ error: 'Invalid Ethereum address' });
return;
}
try {
console.time(`FetchData-${address}`);
// 使用 Promise.all 并发请求链上和链下数据,极大缩短响应时间
const [ethBalance, nftBalance, offChainPoints] = await Promise.all([
// 1. 查询链上 ETH 余额
provider.getBalance(address),
// 2. 查询链上特定 NFT 的持有数量
(async () => {
try {
const nftContract = new ethers.Contract(NFT_CONTRACT_ADDRESS, nftAbi, provider);
const balance = await nftContract.balanceOf(address);
return Number(balance);
} catch (err) {
console.error("Fetch NFT balance failed:", err);
return 0; // 降级处理:获取失败则返回 0
}
})(),
// 3. 查询链下 Web2 数据库中的积分
getOffChainPoints(address)
]);
console.timeEnd(`FetchData-${address}`);
// 4. 数据裁剪与格式化,只返回前端需要的最精简数据
const dashboardData = {
address: address,
assets: {
eth: ethers.formatEther(ethBalance), // 将 Wei 转换为 ETH 单位 (String)
nftCount: nftBalance
},
rewards: {
platformPoints: offChainPoints
},
updatedAt: new Date().toISOString()
};
res.json(dashboardData);
} catch (error) {
console.error('BFF Error:', error);
res.status(500).json({ error: 'Internal Server Error fetching Web3 data' });
}
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Web3 BFF Server running on port ${PORT}`);
});
3. 前端(如 React/Vue)调用该 BFF 的数据格式
如果没有 BFF,前端需要自己写 ethers.js、连接 RPC、处理 Wei 转换、调用多个 API。而现在有了 BFF,前端只需极其简单的一行 fetch:
json
// GET /api/user-dashboard/0x71C...
{
"address": "0x71C7656EC7ab88b098defB751B7401B5f6d6376F",
"assets": {
"eth": "1.452",
"nftCount": 3
},
"rewards": {
"platformPoints": 1250
},
"updatedAt": "2026-05-15T13:30:00.000Z"
}
三、 进阶:Web3 BFF 的进化(GraphQL 与 Subgraph)
在实际的大型 Web3 项目中,BFF 往往还会结合 GraphQL 或 The Graph (Subgraph)。
- The Graph 解决了链上历史数据(Events)难以直接通过 RPC 高效查询的痛点(它将链上数据索引到了传统的 GraphQL 数据库中)。
- 此时的 BFF 架构 通常是:前端 →\rightarrow→ Node.js BFF (Apollo Server) →\rightarrow→ 同时向 The Graph (链上历史数据) 、RPC (链上实时数据) 和 Web2 数据库 (链下数据) 发起请求并融合成 GraphQL 节点。