还不会设计 RESTful API 吗?这些最佳实践你必须掌握!

480 阅读9分钟

1. 什么是 RESTful API?

RESTful API 是一种基于 HTTP 协议的接口设计风格,核心思想是将资源通过 URL 来表示,并使用 HTTP 动词(GET, POST, PUT, DELETE 等)对资源进行操作。每个 URL 都代表了某种资源,而 HTTP 请求方法则定义了客户端对该资源的操作。

2. RESTful API 的最佳实践

2.1 HTTP 动词和操作匹配:详细介绍

在 RESTful API 设计中,HTTP 动词应该与资源的操作相匹配。这种设计不仅直观,也能提升 API 的可维护性和一致性。让我们通过几个常见的动词来详细讲解并展示相关的示例。

1. GET:用于获取资源

GET 请求是用来从服务器读取资源的,具有只读性质。它不会对服务器端的资源进行任何修改。

  • 特点:幂等性(即多次调用不会对资源状态产生影响),只负责获取数据。
  • 常见使用场景:获取列表数据、查询单个资源的详细信息。

示例

# 获取所有用户
GET /users

# 获取指定 ID 的用户
GET /users/{id}

返回示例

[  { "id": 1, "name": "Alice" },  { "id": 2, "name": "Bob" }]

常见错误:将获取数据的操作放到 POST 中,这会让 API 变得难以理解。

# 错误示例:用 POST 来获取用户数据
POST /getUsers/{id}

2. POST:用于创建资源

POST 方法用于在服务器上创建新资源。通常会将客户端提供的数据发送给服务器,并在服务器端创建一个新的实体。

  • 特点:非幂等(多次调用会导致多个资源被创建)。
  • 常见使用场景:提交表单、上传数据、创建新记录。

示例

# 创建一个新用户
POST /users

请求体示例:

{
  "name": "Charlie",
  "email": "charlie@example.com"
}

返回结果示例(通常会返回创建的资源及其 ID 或者 ID):

{
  "id": 3,
  "name": "Charlie",
  "email": "charlie@example.com"
}

常见错误:在创建资源时,使用 GET 或其他动词,这样的设计不符合 REST 语义。


3. PUT:用于更新整个资源

PUT 请求用于更新现有资源。它一般用于整体替换,即客户端提供的数据会替换服务器端资源的全部内容。

  • 特点:幂等性(多次相同的请求不会对服务器的资源状态产生额外影响)。
  • 常见使用场景:更新已有的完整资源。

示例

# 更新指定用户信息
PUT /users/{id}

请求体示例:

{
  "id": 3,
  "name": "Charlie Updated",
  "email": "charlie_new@example.com"
}

返回结果示例:

{
  "id": 3,
  "name": "Charlie Updated",
  "email": "charlie_new@example.com"
}

常见错误:使用 POST 来进行资源更新操作,这可能导致语义不清晰。


4. PATCH:用于部分更新资源

PATCH 请求与 PUT 类似,但不同的是,PATCH 只用于部分更新资源。它允许你更新资源的某些字段,而不需要提供整个对象。

  • 特点:不要求幂等,但最好保持幂等性(即同样的 PATCH 请求重复执行后结果相同)。
  • 常见使用场景:部分更新某个资源的一部分信息。

示例

# 部分更新用户信息
PATCH /users/{id}/email

请求体示例:

{
  "email": "charlie_updated@example.com"
}

返回结果示例( 也可以只返回成功信息或指定修改的内容信息 ):

{
  "id": 3,
  "name": "Charlie",
  "email": "charlie_updated@example.com"
}

常见错误:在需要部分更新时,误用 PUT,可能导致需要提供整个对象,而不是仅仅修改特定字段。


5. DELETE:用于删除资源

DELETE 请求用于删除资源。它会让服务器移除指定的资源。

  • 特点:幂等性(多次执行 DELETE 请求对资源状态没有额外影响,如果资源已经被删除,多次删除不会再改变状态)。
  • 常见使用场景:删除数据库中的记录、移除文件等。

示例

# 删除指定用户
DELETE /users/{id}

返回示例(通常只返回状态码 204 无内容,表示删除成功。部分系统返回 200 操作成功 也是可以的):

HTTP/1.1 204 No Content

常见错误:有时候开发者会使用 GET 或 POST 来执行删除操作,这样会导致 API 不够语义化,无法直观理解。


6. 相关注意事项

  • 幂等性GETPUTDELETE 应该是幂等的,意味着多次相同的请求不会对资源的状态造成不同的影响,而 POST 通常不具有幂等性。

  • 错误处理:每个操作都应考虑可能的错误情况,并返回适当的 HTTP 状态码。比如:

    • 404 Not Found:找不到资源
    • 400 Bad Request:请求体有误
    • 500 Internal Server Error:服务器端错误

2.2 合理设计 URL 路径

URL 应该反映资源的层次关系,并且使用名词而非动词。例如:

# 正确的资源路径设计
GET /product/{id}/reviews

# 错误的设计方式,路径中包含动词
GET /getProductReviews/{productId}
  • 使用 RESTful 风格的路径,路径中的名词应尽量使用复数形式,例如 /users/orders
  • 如果有层次关系,可以用路径嵌套表示。例如 /products/{id}/reviews 代表获取某个商品的评论。

常见错误:  

  • 一些开发者在 URL 设计中加入动词,像 /getUserInfo/deleteUser 这样的设计并不符合 RESTful 风格。路径本身应该是名词,而具体的动作由 HTTP 动词来表达。
  • 复杂的路径结构 GET /company/{companyId}/department/{deptId}/employee/{empId} 可以通过查询参数简化为 GET /employees?company={companyId}&department={deptId}

2.3 状态码与错误处理

使用正确的状态码是 API 设计中容易被忽视的一环。每个状态码都具有明确的语义,返回适当的状态码可以让客户端更轻松地理解 API 的执行结果。

最佳实践

  • 200 OK:请求成功(常用于 GET 请求)。
  • 201 Created:资源创建成功(用于 POST 请求)。
  • 204 No Content:删除成功,没有返回内容。
  • 400 Bad Request:请求有误,通常是客户端问题(如参数不合法)。
  • 401 Unauthorized:需要认证,通常用于用户没有权限访问资源的情况。
  • 404 Not Found:资源不存在。
  • 500 Internal Server Error:服务器端出错。

示例代码

// Express.js 中的错误处理示例
app.get('/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  res.status(200).json(user);
});

在处理错误时,尽可能给出详细的错误信息,帮助客户端理解问题。例如,返回 404 错误时,除了状态码,还可以在响应体中添加额外的信息,说明哪个部分出错了。

常见错误:  返回错误状态码时使用了 200,导致客户端误以为操作成功。

// 错误示例:错误时仍然返回 200 状态码
res.status(200).json({ error: 'User not found' });

我的理解: 这个点确实很容易忽略,我也没遵守,哈哈哈!但是如果遵守,前端能很容知道 API 为什么操作方式,并且提供给用户一个更好的提示!

2.4 数据格式与响应结构

RESTful API 通常使用 JSON 作为数据传输格式。响应的结构应保持一致,便于客户端解析和处理。

最佳实践

  1. 保持一致的响应格式:无论请求成功还是失败,响应的结构应该尽量一致。
  2. 避免嵌套过深:过于复杂的嵌套结构会增加客户端解析的负担。( 坑 :政府项目的省市区结构一般返回的Json数据有几MB,并不是说嵌套太多,而是掺杂了太多无用信息,相信看过政府项目的都懂 )

示例代码

{
  "data": {
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com"
  },
  "errors": null
}

API 响应中,统一的结构不仅能够提高可读性,还能让前端更方便地处理数据。例如,约定 data 始终包含主要数据,errors 用于错误信息,可以简化客户端的逻辑。

2.5 版本控制

API 随着业务需求的增加会不断迭代,因此在发布新版本时要考虑对旧版本的支持。常用的做法是在 URL 中引入版本号。

示例代码

# v1 版本 API
GET /api/v1/users/{id}

# v2 版本 API
GET /api/v2/users/{id}

通过在 URL 中添加版本号,可以保证新旧版本的 API 能够同时存在,给开发者留出迁移的时间和空间。版本控制还能避免因改动 API 而导致客户端出现问题。

2.6 安全性与认证

在实际的 API 开发中,安全性是必须考虑的。为了防止未授权的访问,需要对每个请求进行身份验证。常见的认证方式包括:JWT(JSON Web Token)、OAuth 2.0、API Key 等。

示例代码

// 基于 JWT 的认证中间件
const jwt = require('jsonwebtoken');

app.use((req, res, next) => {
  const token = req.headers['authorization'];
  
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  jwt.verify(token, 'secretKey', (err, decoded) => {
    if (err) {
      return res.status(401).json({ error: 'Failed to authenticate token' });
    }
    
    req.userId = decoded.id;
    next();
  });
});
  • 所有敏感信息必须通过 HTTPS 传输。
  • 在 URL 中避免暴露敏感信息(如 API Key 或 Token),可以通过 HTTP 头传递。

常见错误:  有些开发者在开发过程中忽略了认证,导致 API 过于开放,存在安全隐患。

2.7 文档与可发现性

完整且易于理解的 API 文档是开发者与使用者之间的桥梁。文档应该包括 API 的各个功能、使用方法、请求参数及示例响应。

建议

  • 使用 OpenAPI (Swagger) 等工具生成 API 文档,可以让文档更规范化。
  • 文档应保持更新,特别是 API 版本迭代时。

示例

通过 Swagger 生成的文档,可以直接在线测试 API,开发者不必自己手动编写文档。

3. 结束语

在开发过程中,RESTful API 的设计往往容易被忽略,但它对项目的长期可维护性和可扩展性至关重要。如果你所在的公司有自己的操作流程和约定,建议在 RESTful 规范和内部需求之间做一个合理的权衡。过于严格地遵循某些规范,可能会导致开发过程中效率降低,特别是在需要快速迭代或应对复杂业务需求时

例如,类似于开发者们常讨论的 JavaScript 与 TypeScript 之争,选择一种设计风格不应仅仅是因为“这是标准”,而是要根据团队的实际情况、开发人员的习惯、项目的复杂度以及长期维护成本做出决定。

最重要的是,RESTful 设计的核心在于清晰一致高可维护性,而不是死板地遵守每一个标准。如果你能在这些原则和实际需求之间找到平衡,那么你的 API 设计既可以保持清晰易懂,又能让团队的开发效率最大化。

如果文中描述错误请指出,写着写着发现自己有一部分点也没有遵守,哈哈哈。