做后端开发这些年,几乎每个项目都要和 RESTful API 打交道。早期那会儿我设计接口基本靠感觉,同一个系统里既有 /users/getUserInfo,又有 /user/detail,还有 /users/123,前端同学每天都在猜后端到底给了什么。后来我花了不少时间把接口规范沉淀下来,再结合 Python 生态里常用的工具,总算让前后端协作顺畅了很多。这篇文章就把我整理出的 RESTful API 设计最佳实践完整写出来,重点放在 Python 项目里能直接落地的部分,包括资源设计、状态码与异常、框架选型、认证安全、版本管理、文档测试和性能优化。
写这份内容的时候,我默认你已经装好了 Python 3.10 以上的环境,并且会用 venv 或 uv 管理依赖。如果你还在纠结 Python 怎么装、VSCode 或者 PyCharm 怎么配,建议先花半天时间把基础环境理顺,再回来看接口设计,否则代码示例跑不起来,后面全是纸上谈兵。
1. 先把接口当成“资源”来设计,而不是一堆函数
1.1 为什么“资源思维”是 REST 的起点
很多人对 REST 的理解停留在“URL 短一点、用 HTTP 方法区分动作”,但真正让接口稳定的核心其实是“资源”这个概念。REST 强调把系统中的一切抽象为资源,比如用户、订单、文章、评论,每个资源有唯一的 URI,客户端通过 HTTP 方法对这些资源做操作,而不是直接调用某个函数。
我举个例子:传统接口可能长这样 POST /getUserById,函数味儿很浓。换成资源思维之后就是 GET /users/123。前者描述的是“我要执行一个获取用户的方法”,后者描述的是“我要获取用户这个资源中标识为 123 的那一个”。看起来只是风格差异,但资源思维会逼着你去思考数据的归属、粒度和层级。
这种思维最直接的好处是接口数量和复杂度会降下来。只要资源模型设计得对,你会发现大部分增删改查都能用固定的几个模式覆盖掉,而不是为每个页面单独写一个自定义接口。后端的维护成本、前端的对接成本,都会明显降低。
1.2 资源命名:名词复数、用 HTTP 方法表达动作
在设计 URI 时,我建议统一使用名词复数,比如 /users、/orders、/articles,而不是 /user、/getOrder、/articleList。原因很简单:资源是一类对象的集合,复数更符合集合语义。当你看到 GET /users 时,能直观理解为“获取用户列表”;看到 GET /users/1 时,能理解是“获取用户 1”。
动作交给 HTTP 方法来表达:
- GET:查询资源,不改变状态
- POST:创建资源,或执行一些需要发送数据的复杂操作
- PUT:整体替换资源
- PATCH:局部更新资源
- DELETE:删除资源
我经常遇到的一个争议是“登录用 POST 还是 GET”,答案一定是 POST,因为登录会创建会话资源,而且请求体里有敏感信息。另一个常见场景是“搜索接口要不要用 GET”,如果只是查询条件,用 GET 没问题,参数放 query string;如果有复杂的过滤条件,可以考虑 POST 加特殊 action,但要谨慎,不要把所有接口都变成 POST。
在命名风格上,URL 路径建议用 kebab-case(/user-profiles),JSON 字段建议用 snake_case(user_name),因为 Python 后端几乎都遵守 snake_case,前端如果要转 camelCase 可以在展示层转换。我不推荐在 URL 里出现大写字母,因为大小写在部分服务器和网关里是敏感的,容易造成资源定位不一致。
1.3 子资源与嵌套深度:别把 URL 写成森林
资源之间会有从属关系,比如“用户的订单”“订单的商品”。这种关系可以映射为嵌套 URI,例如 GET /users/123/orders,表示获取用户 123 的订单列表;GET /orders/456/items,表示获取订单 456 的明细项。
嵌套层级我建议最多两层,超过两层就考虑拆开或打平。比如 GET /users/123/orders/456/items/789 这种三层嵌套,看起来精确,但实际使用中又长又难维护,而且客户端经常需要同时知道用户 ID、订单 ID、商品 ID 三个参数才算得出来。更合理的做法是给 item 一个全局唯一 ID,用 GET /items/789 直接定位,或者用 GET /orders/456/items 拿到明细后自己处理。
还有一类东西不适合占一个资源层级,比如“某个操作触发后的临时结果”。拿“导出报表”举例,你当然可以设计成 POST /reports/export,但更好的做法是创建任务资源:POST /export-jobs 创建一个导出任务,GET /export-jobs/123 查询任务状态,GET /export-jobs/123/download 下载结果。这样既符合资源语义,又能支持异步处理,代码结构也清晰。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. HTTP状态码与错误处理:把“发生了什么”告诉调用方
2.1 常用状态码怎么选
处理状态码是我见过分歧最大的地方。有些团队喜欢一股脑返回 200,然后靠响应体里的 code 字段判断成败;有些团队则严格使用状态码。我这里的建议是:以 HTTP 状态码作为第一层判断,响应体里的业务码作为第二层细化。
基础选择逻辑其实不难:
- 200 OK:查询成功、更新成功
- 201 Created:资源创建成功,通常配合
Location响应头返回新资源的 URI - 204 No Content:删除成功或没有返回体但结果成功的操作
- 400 Bad Request:请求语法错误,比如 JSON 解析失败
- 401 Unauthorized:未认证,不关心你是谁
- 403 Forbidden:已认证但没权限,或者 IP 被限制
- 404 Not Found:资源不存在,或者接口路径不存在
- 405 Method Not Allowed:接口路径对了但方法不对,比如只支持 GET 的接口被 POST 了
- 409 Conflict:资源当前状态和请求冲突,比如重复创建、唯一键冲突
- 422 Unprocessable Entity:请求体格式正确但语义校验失败,比如用户名为空、邮箱格式不对
- 429 Too Many Requests:触发限流
- 500 Internal Server Error:服务端未处理的异常
- 503 Service Unavailable:服务暂时不可用,比如依赖数据库挂了
我踩过的一个坑是 400 和 422 混用。最开始的接口只要校验失败就返回 400,搞得前端分不清是“参数根本缺了”还是“参数类型不对”。后来统一成:JSON 解析失败、缺少必要字段这类结构性
