写在前面:今天老师讲了一个我深有感触的概念------BFF(Backend For Frontend,为前端服务的后端)。上节课我们学流式输出,前端直接调 DeepSeek 的 API,结果要处理 ReadableStream、TextDecoder、while 循环......代码又臭又长。老师说:"这些脏活累活,交给 BFF 层干啊!" 前端只负责展示,后端代理转发的逻辑丢给 Node.js 的 BFF 层。听完我大喊:这才是工程化!
一、BFF 是什么?为前端"打工"的后端
1.1 为什么需要 BFF?
老师说:
"前端(Vue/React)→ Node(BFF)→ 后端(Java)。"
传统三层架构:
前端(Vue/React) → 后端(Java/Go)→ 数据库
加上 BFF 的四层架构:
前端(Vue/React) → BFF(Node.js) → 后端(Java/Go) → 数据库
BFF 就是"为前端服务的后端"。 它不是替代真正的后端,而是在前端和后端之间加了一层"翻译官"。
1.2 BFF 适合做什么?
| 适合 BFF 做的事 | 不适合 BFF 做的事 |
|---|---|
| 代理 LLM 请求(隐藏 API Key) | 连接数据库、CRUD |
| 处理流式输出的二进制流 | 复杂的业务逻辑 |
| 格式化数据、适配前端需求 | 高安全性、高并发的核心业务 |
| 轻量级转发、聚合多个接口 | 事务管理、分布式处理 |
老师说:
"大前端工程师自己写常见的 Node 服务,来达成自身的需求。"
BFF 是"大前端"的护城河------前端工程师自己就能搞定的后端活儿。
二、BFF 实战:Express 搭建代理服务器
2.1 初始化服务器
看 server.mjs:
javascript
import * as dotenv from 'dotenv';
import express from 'express';
dotenv.config({
path: ['.env.local', '.env'],
});
const app = express();
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
老师用的是 Express------Node.js 最常用且最简单的开发框架。
2.2 普通的 GET 路由
javascript
app.get('/', (req, res) => {
res.send('Hello World!'); // 一次性发送
});
2.3 流式输出的代理路由
javascript
app.get('/stream', async (req, res) => {
const { prompt } = req.query;
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VITE_DEEPSEEK_API_KEY}`,
},
body: JSON.stringify({
model: 'deepseek-v4-flash',
stream: true,
messages: [{ role: 'user', content: prompt }]
})
});
console.log(response.body); // ReadableStream
});
BFF 在这里做了一件非常重要的事:
前端不需要知道 API Key 了。API Key 放在 BFF 层,前端只发请求到 BFF,BFF 带上 Key 去调 LLM。
这就是 安全性的提升------API Key 不会暴露在前端代码里。
2.4 完整的数据流
bash
前端(5173):fetch('/api/stream?prompt=...')
↓
Vite 代理:vite.config.js 中 proxy 配置
↓
BFF(3000):/api → /stream 路由 → 转发到 DeepSeek API
↓
DeepSeek API:流式返回数据
↓
BFF 处理流数据 → 返回给前端
↓
前端展示
老师说:
"前端 fetch → Node(BFF)→ LLM 服务器。前端业务非常复杂,二进制流对象,解码,解析 data------各种情况。抽象一下,放到大前端 BFF 层,Node 里面,前端简洁,降低难度。"
三、跨域问题:Vite Proxy 来解决
3.1 什么是跨域?
老师说:
"只要域名、端口、协议(http/https)不同,fetch 等请求的时候,跨域,同源策略。"
前端运行在 http://localhost:5173,BFF 运行在 http://localhost:3000。
端口不同,浏览器就会拦截跨域请求。
3.2 Vite 的 Proxy 方案
看 vite.config.js:
javascript
// vite 工程,proxy 配置,代理一下请求,并转发出去
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
}
}
}
});
老师说:
"
/api标志请求后端 API 接口。不跨域了,但是 502------因为前端不会提供这个路由。Vite 拦截所有/api开头的请求,代理转发到:3000。"
前端的请求路径:
bash
前端请求:/api/stream(不跨域了,因为是同源)
↓ Vite 识别到 /api 开头
↓ 自动转发
BFF 实际接收:/stream(去掉了 /api 前缀)
Vite Proxy 就像一个"快递中转站"------你把包裹交给同一个小区的快递站(5173),它帮你转发到另一个小区的站点(3000)。
四、BFF + 流式输出,前端代码变得多简单?
4.1 有 BFF 之前
前端要直接处理:
javascript
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
headers: { 'Authorization': `Bearer ${apiKey}` },
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
// ...while 循环读流......
4.2 有 BFF 之后
前端只需要:
javascript
fetch('/api/stream')
.then(res => res.json())
.then(data => {
console.log(data);
});
当然,如果是流式输出,前端还是要处理流。但至少 API Key 不用暴露了,请求地址更短了,后端转发逻辑可以集中管理了。
五、总结:BFF 是大前端的"保护层"
| 概念 | 说明 |
|---|---|
| BFF | Backend For Frontend,为前端服务的后端 |
| Express | Node.js 最常用的 Web 开发框架 |
| dotenv | 读取 .env 文件的环境变量 |
| 跨域 | 不同端口/域名/协议的请求被浏览器拦截 |
| Vite Proxy | 代理转发前端请求到 BFF 后端 |
| API Key 保护 | Key 放在 BFF 层,前端不知道 |
BFF 的核心价值三句话:
- 隐藏敏感信息(API Key)。
- 降低前端复杂度(流式处理丢给 BFF)。
- 解耦前后端(前端不直接调第三方 API)。
写在最后
今天最大的收获,是理解了 BFF 在 AI 项目中的角色。以前觉得前端直接调 API 就行了,现在知道了------流式输出的二进制流处理太复杂了,API Key 放在前端也太危险了。BFF 就是用来"挡在前面前面"的------脏活累活它干,安全问题它扛。
下次面试官问你:"BFF 层在 AI 项目中有什么作用?"
你可以淡定地说:
"BFF(Backend For Frontend)在前端和后端之间加了一层中继。在 AI 项目中有三个核心作用:① 隐藏 API Key ------前端发请求到 BFF,BFF 带上 Key 去调 LLM,Key 不暴露在浏览器端;② 降低前端复杂度 ------流式输出的 ReadableStream 处理、数据解析在 BFF 层完成,前端只负责展示;③ 跨域代理 ------通过 Vite Proxy 将 /api 请求转发到 BFF 的对应端口,避免前端跨域问题。实现上可以用 Express 快速搭建 BFF 服务,一行 proxy 配置解决转发问题。"
然后看着面试官满意的表情,心里默念:这波,又稳了。
本文所有代码示例均来自课堂学习资料,真实可运行。