跳到主要内容

RESTful API 设计

问题​

什么是 RESTful API?如何设计符合规范的 API 接口?

面试速答版

什么是 RESTful API? 一句话:用 HTTP 方法操作资源 (URL) 的 API 风格:

  • 资源用名词,URL 表示「操作什么」(/users/123),HTTP 方法表示「怎么操作」(GET/POST/PUT/DELETE)。
  • 六大约束里前端最常感知的是:无状态(每个请求自带凭证,服务端不存上下文)和统一接口。
  • 不是非要严格 REST,业界更多是「类 REST」,配合状态码 + JSON 响应就够用。

设计规范要点有哪些? 几个易踩坑的点:

  • URL 用名词复数 + 不带动词:/users/123/posts 而不是 /getUserPosts?id=123。
  • 嵌套不超过 2 层,更深的层级靠 query 参数表达:/comments?postId=456。
  • HTTP 方法语义正确:PUT 是全量替换且幂等,PATCH 是部分更新不幂等。
  • 状态码要用对:200 成功、201 新建成功、204 删除成功、400 参数错、401 未登录、403 无权限、404 资源不存在、409 冲突、422 校验失败、429 限流。
  • 版本走 URL (/api/v1/users) 最直观,比 Header 方式好排查。

RESTful 的局限是什么?

  • 多次往返:拿一个页面要的数据可能要打 3-5 个接口。
  • 过度获取/获取不足:服务端返回字段固定,前端要么浪费带宽要么再发请求。
  • 复杂关系不好表达:图状数据用 REST 拼 URL 很别扭。
  • 所以才有了 GraphQL 和 BFF。

答案​

RESTful API 是一种基于 REST(Representational State Transfer,表现层状态转换)架构风格的 API 设计规范,使用 HTTP 协议进行通信。


REST 核心原则​

原则说明
客户端-服务端分离前后端独立演进
无状态每次请求包含所有信息
可缓存响应可标记为可缓存
统一接口一致的接口设计
分层系统支持代理、网关

HTTP 方法与 CRUD​

方法操作幂等安全示例
GET查询✅✅获取用户列表
POST创建❌❌创建新用户
PUT全量更新✅❌更新整个用户
PATCH部分更新❌❌更新用户名
DELETE删除✅❌删除用户
幂等性

幂等性指多次执行相同操作,结果不变。GET、PUT、DELETE 是幂等的,POST 不是。


URL 设计规范​

资源命名​

// ✅ 推荐:使用名词复数
GET /users // 获取用户列表
GET /users/123 // 获取单个用户
POST /users // 创建用户
PUT /users/123 // 更新用户
DELETE /users/123 // 删除用户

// ❌ 不推荐:使用动词
GET /getUsers
GET /getUserById
POST /createUser
POST /deleteUser

资源层级​

// 嵌套资源
GET /users/123/posts // 用户的文章列表
GET /users/123/posts/456 // 用户的某篇文章
POST /users/123/posts // 创建用户文章

// 但嵌套不宜过深(最多 2 层)
// ❌ 不推荐
GET /users/123/posts/456/comments/789

// ✅ 推荐:使用查询参数
GET /comments/789
GET /comments?postId=456&userId=123

查询参数​

// 分页
GET /users?page=1&pageSize=20
GET /users?offset=0&limit=20

// 排序
GET /users?sort=created_at&order=desc
GET /users?sortBy=-createdAt // 减号表示降序

// 过滤
GET /users?status=active&role=admin

// 字段选择
GET /users?fields=id,name,email

// 搜索
GET /users?q=john
GET /users?search=john

状态码规范​

成功响应​

状态码含义使用场景
200OKGET、PUT、PATCH 成功
201CreatedPOST 创建成功
204No ContentDELETE 成功

客户端错误​

状态码含义使用场景
400Bad Request请求参数错误
401Unauthorized未登录
403Forbidden无权限
404Not Found资源不存在
409Conflict资源冲突
422Unprocessable参数校验失败
429Too Many Requests限流

服务端错误​

状态码含义使用场景
500Internal Error服务端异常
502Bad Gateway网关错误
503Service Unavailable服务不可用

响应格式设计​

成功响应​

// 单个资源
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}

// GET /users/123
{
"code": 0,
"message": "success",
"data": {
"id": 123,
"name": "John",
"email": "john@example.com"
}
}

// 列表资源(带分页)
interface PaginatedResponse<T> {
code: number;
message: string;
data: {
items: T[];
total: number;
page: number;
pageSize: number;
};
}

// GET /users?page=1&pageSize=20
{
"code": 0,
"message": "success",
"data": {
"items": [
{ "id": 1, "name": "John" },
{ "id": 2, "name": "Jane" }
],
"total": 100,
"page": 1,
"pageSize": 20
}
}

错误响应​

interface ErrorResponse {
code: number;
message: string;
errors?: Array<{
field: string;
message: string;
}>;
}

// 400 Bad Request
{
"code": 40001,
"message": "Validation failed",
"errors": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "password", "message": "Password too short" }
]
}

版本控制​

方式对比​

方式示例优点缺点
URL 路径/v1/users直观、易缓存URL 冗长
查询参数/users?version=1灵活易被忽略
请求头Accept: application/vnd.api.v1+json优雅不直观
// 推荐:URL 路径版本
const API_V1 = '/api/v1';
const API_V2 = '/api/v2';

// GET /api/v1/users
// GET /api/v2/users

安全实践​

认证方式​

// 1. Bearer Token
fetch('/api/users', {
headers: {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIs...'
}
});

// 2. API Key
fetch('/api/users?api_key=xxx');

// 3. Cookie(适合同域)
fetch('/api/users', {
credentials: 'include'
});

请求签名​

// 防止请求篡改
interface SignedRequest {
timestamp: number;
nonce: string;
signature: string; // HMAC-SHA256(params + secret)
}

限流策略​

// 响应头中返回限流信息
// X-RateLimit-Limit: 100
// X-RateLimit-Remaining: 95
// X-RateLimit-Reset: 1609459200

TypeScript 封装​

// 通用 API 请求封装
interface RequestConfig {
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
url: string;
params?: Record<string, any>;
data?: any;
headers?: Record<string, string>;
}

async function request<T>(config: RequestConfig): Promise<ApiResponse<T>> {
const { method, url, params, data, headers } = config;

// 构建 URL
let fullUrl = url;
if (params) {
const searchParams = new URLSearchParams(params);
fullUrl = `${url}?${searchParams}`;
}

const response = await fetch(fullUrl, {
method,
headers: {
'Content-Type': 'application/json',
...headers
},
body: data ? JSON.stringify(data) : undefined
});

if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}

return response.json();
}

// 使用示例
interface User {
id: number;
name: string;
email: string;
}

// 获取用户列表
const users = await request<User[]>({
method: 'GET',
url: '/api/v1/users',
params: { page: 1, pageSize: 20 }
});

// 创建用户
const newUser = await request<User>({
method: 'POST',
url: '/api/v1/users',
data: { name: 'John', email: 'john@example.com' }
});

常见面试问题​

Q1: RESTful API 的设计原则?​

答案:

  1. 使用名词复数:/users 而非 /user
  2. HTTP 方法语义化:GET 查询、POST 创建、PUT 更新、DELETE 删除
  3. 正确的状态码:200 成功、201 创建、400 参数错误、401 未认证、404 不存在
  4. 无状态:每次请求包含所有必要信息
  5. 版本控制:/api/v1/users
  6. 统一响应格式:成功和错误都有一致的结构

Q2: PUT 和 PATCH 的区别?​

答案:

区别PUTPATCH
更新方式全量替换部分更新
幂等性幂等非幂等
请求体完整资源仅修改字段
// PUT 全量更新
PUT /users/123
{
"name": "John",
"email": "john@example.com",
"age": 30,
"address": "..."
}

// PATCH 部分更新
PATCH /users/123
{
"name": "John Doe" // 只更新 name
}

Q3: 什么是幂等性?哪些方法是幂等的?​

答案:

幂等性指同一操作执行多次,结果与执行一次相同。

方法幂等原因
GET✅只读操作
PUT✅全量替换,结果相同
DELETE✅删除后再删除,资源仍不存在
POST❌每次创建新资源
PATCH❌可能依赖当前状态

Q4: 如何处理批量操作?​

答案:

// 方案1:批量端点
POST /users/batch
{
"users": [
{ "name": "John" },
{ "name": "Jane" }
]
}

// 方案2:批量删除
DELETE /users?ids=1,2,3

// 方案3:批量更新
PATCH /users
{
"ids": [1, 2, 3],
"data": { "status": "active" }
}

Q5: API 如何实现分页?​

答案:

// 方案1:页码分页
GET /users?page=2&pageSize=20

// 响应
{
"data": [...],
"meta": {
"total": 100,
"page": 2,
"pageSize": 20,
"totalPages": 5
}
}

// 方案2:游标分页(大数据量推荐)
GET /users?cursor=eyJpZCI6MTAwfQ&limit=20

// 响应
{
"data": [...],
"meta": {
"nextCursor": "eyJpZCI6MTIwfQ",
"hasMore": true
}
}

游标分页优势:

  • 避免深分页性能问题
  • 数据一致性更好
  • 适合无限滚动场景

相关链接​