好的 API 设计是前后端协作的契约,好的 BFF 层是产品体验的放大器。 当移动端需要精简字段、桌面端需要丰富关联、管理后台需要批量操作时,直接在数据源层做妥协会让所有人都痛苦——BFF 的存在是为了让每个客户端都能获得「刚好需要」的数据结构。
一、RESTful API 设计规范 1.1 HTTP 方法与资源命名 方法 操作 幂等 示例 GET 读取 ✅ GET /users / GET /users/123POST 创建 ❌ POST /usersPUT 全量更新 ✅ PUT /users/123PATCH 部分更新 ✅ PATCH /users/123DELETE 删除 ✅ DELETE /users/123
资源命名:
✅ /users — 集合
✅ /users/123 — 单个资源
✅ /users/123/orders — 子资源
✅ /users/123/posts?status=published&page=2&limit=20 — 查询参数
❌ /getUsers — 动作不要放在 URL 中
❌ /user/123 — 集合用复数
❌ /users/123/delete — 用 DELETE 方法
1.2 响应格式 {
"data" : {
"id" : "u-123" ,
"name" : "Alice" ,
"email" : "alice@example.com" ,
"createdAt" : "2026-01-15T08:30:00Z"
},
"meta" : {
"page" : 1 ,
"pageSize" : 20 ,
"total" : 156 ,
"totalPages" : 8
},
"links" : {
"self" : "/users/u-123" ,
"orders" : "/users/u-123/orders"
}
}
{
"error" : {
"code" : "USER_NOT_FOUND" ,
"message" : "用户不存在" ,
"details" : {
"userId" : [ "未找到 ID 为 u-999 的用户" ]
}
}
}
1.3 分页策略 方式 适用 示例 优点 缺点 Offset 传统列表 ?page=2&limit=20简单 深页性能差、数据漂移 Cursor 实时/无限滚动 ?cursor=eyJpZCI6MTIzfQ==无漂移、性能好 无法跳页 Seek 顺序数据 ?after_id=123&limit=20简单+可靠 仅适合单一排序
1.4 版本控制 URL 路径(推荐):
GET /v1/users
GET /v2/users // 破坏变更时递增
请求头(可选):
Accept: application/vnd.api+json; version=2
不要这样:
/api/v1.2.3/users — 细版本无意义
完全不做版本 — 无法演进
二、OpenAPI:API 为契约 2.1 OpenAPI 3.1 规范 # openapi.yaml
openapi : 3.1.0
info :
title : My API
version : 1.0.0
paths :
/users/{id}:
get :
operationId : getUser
parameters :
- name : id
in : path
required : true
schema :
type : string
responses :
'200' :
description : User found
content :
application/json :
schema :
$ref : '#/components/schemas/User'
'404' :
description : User not found
components :
schemas :
User :
type : object
required : [ id, name, email]
properties :
id :
type : string
format : uuid
name :
type : string
minLength : 1
maxLength : 100
email :
type : string
format : email
createdAt :
type : string
format : date-time
2.2 自动化工具链 # 1. 从代码生成 OpenAPI(后端)
# Go: swag, oapi-codegen
# Node.js: @nestjs/swagger, tsoa
# Python: FastAPI(原生生成)
# 2. 从 OpenAPI 生成客户端(前端)
npx openapi-typescript https://api.example.com/openapi.json -o src/api.ts
# 3. 生成 Mock Server
npx @stoplight/prism-cli mock openapi.yaml
# 4. 生成文档
npx redocly build-docs openapi.yaml -o docs.html
三、GraphQL:类型驱动的查询 3.1 Schema 设计 type User {
id : ID !
name : String !
email : String !
avatar : String
posts ( page : Int , limit : Int ): PostConnection !
createdAt : DateTime !
}
type Post {
id : ID !
title : String !
content : String !
author : User !
tags : [ String !]!
publishedAt : DateTime
}
type PostConnection {
edges : [ Post !]!
pageInfo : PageInfo !
}
type PageInfo {
hasNextPage : Boolean !
endCursor : String
}
type Query {
user ( id : ID !): User
users ( filter : UserFilter , page : Int , limit : Int ): [ User !]!
me : User
}
type Mutation {
createUser ( input : CreateUserInput !): User !
updateUser ( id : ID !, input : UpdateUserInput !): User !
}
input CreateUserInput {
name : String !
email : String !
}
3.2 查询示例 # 前端只请求需要的字段
query GetUserWithPosts ( $userId : ID !, $page : Int ) {
user ( id : $userId ) {
id
name
email
posts ( page : $page , limit : 5 ) {
edges {
id
title
publishedAt
}
pageInfo {
hasNextPage
}
}
}
}
3.3 N+1 问题与 DataLoader // ❌ N+1:查 100 个用户 → 发 100 次查询
const users = await db . users . findMany ();
for ( const user of users ) {
user . posts = await db . posts . findMany ({ where : { authorId : user . id } });
}
// ✅ DataLoader:批量 + 缓存
import DataLoader from 'dataloader' ;
const postLoader = new DataLoader ( async ( userIds ) => {
const posts = await db . posts . findMany ({
where : { authorId : { in : userIds } }
});
// 按 userId 分组返回
return userIds . map ( id => posts . filter ( p => p . authorId === id ));
});
// 解析器中自动批量
const resolvers = {
User : {
posts : ( user ) => postLoader . load ( user . id )
}
};
3.4 GraphQL vs REST 维度 GraphQL REST 数据获取 前端决定字段 后端决定响应结构 请求数 单次聚合 多次请求(或过度获取) 缓存 需要自定义 HTTP 缓存原生支持 学习曲线 较陡 平缓 工具链 Apollo/URQL/Relay fetch/axios/SWR 适用 复杂关联数据、多客户端 简单 CRUD、CDN 缓存
四、tRPC:端到端类型安全 4.1 核心理念 「你的 API 路由就是 TypeScript 类型,前后端共享同一份类型定义。」
// server/router.ts(后端)
import { initTRPC } from '@trpc/server' ;
import { z } from 'zod' ;
const t = initTRPC . create ();
export const appRouter = t . router ({
user : t.router ({
getById : t.procedure
. input ( z . object ({ id : z.string (). uuid () }))
. query ( async ({ input }) => {
return db . user . findById ( input . id );
}),
create : t.procedure
. input ( z . object ({
name : z.string (). min ( 1 ). max ( 100 ),
email : z.string (). email ()
}))
. mutation ( async ({ input }) => {
return db . user . create ( input );
}),
}),
});
export type AppRouter = typeof appRouter ;
// client/trpc.ts(前端)
import { createTRPCReact } from '@trpc/react-query' ;
import type { AppRouter } from '../server/router' ;
export const trpc = createTRPCReact < AppRouter >();
// 使用
function UserProfile ({ userId } : { userId : string }) {
const { data } = trpc . user . getById . useQuery ({ id : userId });
// TypeScript 自动推导 data 类型 = User | undefined
// 错误的字段名会编译报错!
return < div >{ data ? . name }</ div >;
}
4.2 tRPC 优势 优势 说明 零类型声明 无需手写 DTO/Interface/API 契约 自动填充 IDE 自动补全所有路由和参数 运行时校验 Zod 自动校验输入 轻量传输 JSON RPC over HTTP,无 Schema 传输开销 React Query 集成 自动缓存、重试、乐观更新
4.3 tRPC 适用场景 ✅ 适用:
- 全 TypeScript 栈(Node.js + React/Vue)
- 内部项目、快速迭代
- 对类型安全有极高要求
❌ 不适用:
- 多语言后端(Go/Python/Java)
- 对外开放 API(第三方消费)
- 需要 CDN 缓存的读接口
五、BFF 架构设计 5.1 为什么需要 BFF 问题:一个后端数据源,多个前端客户端
移动端 App → 需要精简字段、减少嵌套
桌面 Web → 需要丰富关联、实时更新
管理后台 → 需要批量操作、复杂筛选
第三方开放平台 → 需要严格权限、 Rate Limit
直接暴露后端服务 → 所有客户端耦合在一起,互相制约
5.2 BFF 职责边界 BFF(Backend for Frontend)职责:
├── API 聚合 — 一次请求合并多个后端服务
├── 数据裁剪 — 只返回前端需要的字段
├── 格式转换 — GraphQL → REST / Protocol Buffers → JSON
├── 协议适配 — gRPC-Web ↔ gRPC
├── 认证转发 — 校验 Token,转发用户身份
├── 缓存策略 — 按客户端定制缓存
├── 错误处理 — 统一错误格式
└── 限流/降级 — 按客户端保护后端
BFF 不负责:
❌ 业务逻辑(应保持薄)
❌ 数据持久化(交给后端服务)
❌ 复杂事务(通过 Saga 模式在后端处理)
5.3 BFF 技术选型 场景 技术 说明 Node.js 全栈 Express/Fastify + tRPC 类型安全、快速开发 前端团队维护 Next.js API Routes / Edge 前后端一体部署 高性能要求 Go / Rust 低延迟、高吞吐 已有后端团队 GraphQL Gateway(Apollo) 由后端团队维护 BFF
5.4 Next.js Edge BFF 示例 // app/api/users/[id]/route.ts
import { NextRequest , NextResponse } from 'next/server' ;
export async function GET (
request : NextRequest ,
{ params } : { params : { id : string } }
) {
const token = request . headers . get ( 'authorization' );
// 并行聚合多个后端服务
const [ user , orders , preferences ] = await Promise . all ([
fetch ( ` ${ USER_SERVICE } /users/ ${ params . id } ` , { headers : { Authorization : token } }),
fetch ( ` ${ ORDER_SERVICE } /users/ ${ params . id } /orders?limit=5` ),
fetch ( ` ${ PREF_SERVICE } /users/ ${ params . id } /preferences` )
]);
const [ userData , ordersData , prefData ] = await Promise . all ([
user . json (), orders . json (), preferences . json ()
]);
// 裁剪和聚合:前端只需要这些
return NextResponse . json ({
id : userData.id ,
name : userData.name ,
avatar : userData.avatar ,
recentOrders : ordersData.map (( o : any ) => ({
id : o.id ,
total : o.total ,
status : o.status
})),
theme : prefData.theme ,
language : prefData.language
});
}
六、前后端协作模式 6.1 API First API First 流程:
1. 产品确定需求
2. 前后端共同设计 OpenAPI 契约
3. 用 Prism/MockServer 启动 Mock
4. 前端基于 Mock 开发 UI
5. 后端按契约实现
6. 契约测试验证一致性
7. 联调 → 上线
6.2 契约测试 // 用 Pact 做消费者驱动的契约测试
import { Pact } from '@pact-foundation/pact' ;
const provider = new Pact ({
consumer : 'Frontend' ,
provider : 'UserService'
});
// 前端定义期望
await provider . addInteraction ({
state : 'user exists' ,
uponReceiving : 'a request for user 123' ,
withRequest : { method : 'GET' , path : '/users/123' },
willRespondWith : {
status : 200 ,
body : {
id : '123' ,
name : 'Alice' ,
email : 'alice@example.com'
}
}
});
// 后端验证是否满足契约
// pact-verifier --provider-base-url http://localhost:8080 --pact-broker-base-url ...
七、选型决策树 API 范式选型:
是否全 TypeScript 栈 + 内部项目?
├── 是 → tRPC ✅
│ └── 需要第三方消费?
│ └── 是 → 加 OpenAPI 导出
│
├── 否 → 继续
│ 客户端是否需要精确控制字段?
│ ├── 是 → GraphQL
│ │ └── N+1 严重? → DataLoader + 分页优化
│ └── 否 → REST
│ ├── CDN 缓存重要? → REST + HTTP Cache(推荐)
│ └── 简单 CRUD? → REST + OpenAPI
│
多客户端复杂需求?
└── 是 → BFF 层
├── 前端团队维护 → Next.js Edge / tRPC
└── 后端团队维护 → GraphQL Gateway / Go BFF
八、API 设计 Checklist 检查项 说明 使用资源名词,非动作 /users 非 /getUsersHTTP 方法语义正确 GET 安全幂等,POST 创建,PUT/PATCH 更新 状态码准确 200/201/204/400/401/403/404/422/500 错误信息机器可读 { code, message, details }分页支持 cursor > offset(实时数据) 版本控制 URL 路径 /v1/ 文档自动 OpenAPI + 生成器 输入校验 Schema 校验(Zod/Joi) 限流 Rate Limit 标头 监控 响应时间、错误率、.usage
参考与延伸阅读 继续阅读
探索更多技术文章 浏览归档,发现更多关于系统设计、工具链和工程实践的内容。