🎉 AI 项目里为什么需要 BFF?因为我实在不想在前端处理二进制流

1 阅读5分钟

写在前面:今天老师讲了一个我深有感触的概念——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

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 路由

app.get('/', (req, res) => {
    res.send('Hello World!');  // 一次性发送
});

2.3 流式输出的代理路由

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 完整的数据流

前端(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

// vite 工程,proxy 配置,代理一下请求,并转发出去
export default defineConfig({
    plugins: [vue()],
    server: {
        proxy: {
            '/api': {
                target: 'http://localhost:3000',
                changeOrigin: true,
            }
        }
    }
});

老师说:

"/api 标志请求后端 API 接口。不跨域了,但是 502——因为前端不会提供这个路由。Vite 拦截所有 /api 开头的请求,代理转发到 :3000。"

前端的请求路径:

前端请求:/api/stream(不跨域了,因为是同源)
  ↓ Vite 识别到 /api 开头
  ↓ 自动转发
BFF 实际接收:/stream(去掉了 /api 前缀)

Vite Proxy 就像一个"快递中转站"——你把包裹交给同一个小区的快递站(5173),它帮你转发到另一个小区的站点(3000)。


四、BFF + 流式输出,前端代码变得多简单?

4.1 有 BFF 之前

前端要直接处理:

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 之后

前端只需要:

fetch('/api/stream')
    .then(res => res.json())
    .then(data => {
        console.log(data);
    });

当然,如果是流式输出,前端还是要处理流。但至少 API Key 不用暴露了,请求地址更短了,后端转发逻辑可以集中管理了。


五、总结:BFF 是大前端的"保护层"

概念说明
BFFBackend For Frontend,为前端服务的后端
ExpressNode.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 配置解决转发问题。"

然后看着面试官满意的表情,心里默念:这波,又稳了。


本文所有代码示例均来自课堂学习资料,真实可运行。