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. 相关注意事项
-
幂等性:
GET
、PUT
、DELETE
应该是幂等的,意味着多次相同的请求不会对资源的状态造成不同的影响,而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 作为数据传输格式。响应的结构应保持一致,便于客户端解析和处理。
最佳实践:
- 保持一致的响应格式:无论请求成功还是失败,响应的结构应该尽量一致。
- 避免嵌套过深:过于复杂的嵌套结构会增加客户端解析的负担。( 坑 :政府项目的省市区结构一般返回的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 设计既可以保持清晰易懂,又能让团队的开发效率最大化。
如果文中描述错误请指出,写着写着发现自己有一部分点也没有遵守,哈哈哈。