原文: RESTful API Design Tips from Experience
作者:Peter Boyer
翻译:雁惊寒
译者注:本文是作者在自己的工作经验中总结出来的RESTful API设计技巧,虽然部分技巧仍有争议,但总体来说还是有一定的参考价值的。以下是译文。
简单说一下代码重用记得在 Ken Rogers的Medium博客 里曾经见过这么一句话(原文出自海明威):
我们都是手艺学徒,没有人会成为大师。
在我写这篇文章的时候,我不禁笑了起来,因为从这件事情的背后看到了一个伟大的类比,那就是从其他人那里引用了海明威的话。也就是说,我不需要为了得到类似的功能和结果而花费精力自己去创建一个与众不同的东西,上面提到的海明威的话正是代码重用在文学上的例子。
但是,我在这里不会写代码包的好处,而是更多地提一些我的感受,这些感受会在当前以及未来的项目中积极地得到实现。我还总结了一套API规则和原语,包括了功能和实现细节。
使用API版本控制如果你要开发一个提供客户端服务的API,你需要为最后可能的修改而做好准备。最好的办法就是通过为RESTful API提供“版本命名空间”来实现。
我们只需将版本号作为前缀添加到所有的URL里即可。
GET然而,在我研究了其他的API实现之后发现,我喜欢上了这种较短的URL样式,它把api作为是子域名的一部分,并从路由中删除了 /api ,这样更短、更简洁。
GET api.myservice.com/v1/posts 跨域资源共享(CORS)需要重点关注的是,如果你打算在 上托管你的前端站点,而将API放在另外一个不同的子域上,例如 api.myservice.com ,那么你需要在后端实现CORS,这样才能使得AJAX调用不会抛出 No Access-Control-Allow-Origin header is present 这样的错误。
使用复数形式当你从 /posts 请求多个帖子的时候,这样的URL看起来更明了:
// 复数形式看起来更一致,更有意义 GET /v1/posts/:id/attachments/:id/comments // 不能有歧义 // 这只是一个评论? 还是一个表格? GET /v1/post/:id/attachment/:id/comment更多有关混合类型的信息,请看下文:“ 使用根级别的‘me’端点(URL) ”。
避免查询字符串查询字符串的作用是对关系数据库返回的记录集做进一步地过滤。
/projects/:id/collections 优于 /collections?projectId=:id 。
/projects/:id/collections/:id/items 优于 /items?projectId=:id&collectionId=:id 。
更多信息请看下文:“ 避免对嵌套路由的操作 ”。
使用HTTP方法我们可使用下面这些HTTP方法:
- GET 用于获取数据。
- POST 用于添加数据。
- PUT 用于更新数据(整个对象)。
- PATCH 用于更新数据(附带对象的部分信息)。
- DELETE 用于删除数据。
补充一点,对于修改对象的部分内容的请求来说,我认为PATCH是减少请求包大小的一个好的方法,并且它也能很好的跟自动提交/自动保存字段配合起来用。
一个很好的例子是Tumblr的“仪表盘设置”屏幕,其中,“服务的用户体验”的一些非关键性选项可以单独地编辑和保存,而不需要点最下面的提交按钮。
对于 POST , PUT 或 PATCH 的成功响应消息,应该返回更新后的对象,而不是只返回一个 null 。
有关响应的其他内容,请阅读下文:“ JSON格式的响应和请求 ”。
使用封包“我不喜欢数据封包。它只是引入了另一个键来浏览数据树。元信息应该包含在包头中。”
最初,我坚持认为封包数据是不必要的,HTTP协议已经提供了足够的“封包”来传递响应消息。
然而,根据 Reddit上的回复 所述,如果不封包为JSON数组,则可能会出现各种漏洞和潜在的黑客攻击。
现在建议使用封包,你应该把数据封包后再应答!
// 已封包,最顶级的对象既安全又简洁 { data: [ { ... }, { ... }, // ... ] } // 未封包,存在安全风险 [ { ... }, { ... }, // ... ]同样要重点关注的是,不像其他语言那样,JavaScript之类的语言将会将空对象认为是true! 因此,在下面这种情况下,不要返回空的对象来作为响应的一部分:
// 从payload中提取封包和错误 const { data, error } = payload // 错误处理 if (error) { throw ... } // 否则 const normalizedData = normalize(data, schema) JSON格式的响应和请求所有东西都应该被序列化成JSON。如果你期待从服务器上获取JSON格式的数据,那么请客气一点,请发送JSON格式的内容给服务器。请两边保持一致!
某些情况下,如果动作执行成功(例如 DELETE ),那我并没有什么需要返回的。但是,在某些语言(如Python)中返回一个空对象可能被认为是false,并且在开发人员调试程序的时候,这种情况并不容易发现。因此,我喜欢返回“OK”,尽管这是一个字符串,但是在返回的时候会被包装成一个简单的响应对象。
DELETE /v1/posts/:id // response - HTTP 200 { "message": "OK" } 使用HTTP状态码和错误响应因为我们使用了HTTP方法,所以我们应当使用HTTP状态码。
我喜欢使用这些状态码:
对于数据错误
400 :请求信息不完整或无法解析。
422 :请求信息完整,但无效。
404 :资源不存在。
409 :资源冲突。
对于鉴权错误
401 :访问令牌没有提供,或者无效。
403 :访问令牌有效,但没有权限。
对于标准状态
200 : 所有的都正确。
500 : 服务器内部抛出错误。
假设要创建一个新帐户,我们提供了 email 和 password 两个值。我们希望让客户端应用程序能够阻止任何无效的电子邮件或密码太短的请求,但外部人员可以像我们的客户端应用程序一样在需要的时候直接访问API。
如果 email 字段丢失,则返回 400 。
如果 password 字段太短,则返回 422 。
如果 email 字段不是有效的电子邮件,则返回 422 。
如果 email 已经被使用,返回一个 409 。