跳转至内容
There is a version suitable for your browser's language settings. Would you like to go to the english language of the site?
主页文档

API 风格

H1

你会记得你吃过多少片面包吗?其实,面包 (BREAD) 还有另一层含义,是 browse、read、edit、add、delete 的简写啦。

本章节没有太多的知识,主要想与你讨论一下,在 Milkio 中,编写 BREAD 的最佳风格是什么?顺便,和你分享一下流行的 REST 的历史。

REST 与历史

REST 是一种很流行的 BREAD 风格,它通常是这样子的:

名称HTTP MethodURLHTTP Body
browseGET/user/-
readGET/user/1-
editPUT/user/1{ "name": "alice" }
addPOST/user/{ "name": "alice" }
deleteDELETE/user/1-

REST 是一个伟大的风格,它兴起于千禧年,并在接下来的十年中盛行。

在那个时代,前后端还不像现在这样泾渭分明,只有 RD,没有 FE 和 PE。服务器通过拼接 HTML 来动态地改变内容,没有 JavaScript 或者只是用来为网页添加一些交互效果,直到 Gmail 利用 JavaScript 为世界带来了一些小小的震撼,人们才意识到 JavaScript 的潜力,最终演变成一场席卷整个世界的飓风。

网页内容逐渐不再由服务器预先渲染,而是由 XMLHttpRequest 动态地获取,服务器越来越多地变为了单纯地向前端提供 XML 或 JSON 格式的数据,不再是混合了数据与界面的 HTML。

终于,尘归尘,土归土。用户所接触到的一切,都是由运行在浏览器中的 JavaScript 创造的,服务器只保留了提供数据和保证安全性等关键部分。

REST 现在的问题

如今,对于客户端而言,有着自己的路由,通常是使用 history 或者 location.hash 实现的。REST 之所以不再适合,原因之一就在于客户端与服务端间路由的彻底分离。

浏览器默认发送 GET 请求。GET 方法用于展示页面,其他方法用于执行动作。这也是为什么 REST 将 browse 和 read 都使用 GET 方法实现的原因。

后来,由于历史惯性,大家仍然保持着 REST 的编程习惯,即便,现在提供的已经不再是页面,而是数据。

然而,GET 请求存在一个重要问题:它没有请求体。对于 REST 而言,如果想向服务器传递参数,只能将其作为目录的一部分(例如 /user/1)或 URL 参数(例如 /user/?id=1)。

这个限制使我们无法向服务器传递复杂参数,并且在过程中会丢失类型信息,无法区分数字和布尔值(因为所有参数都从 URL 中截取),也不支持数组和对象。

REST 的优势之一是为用户提供漂亮且符合直觉的 URL。但随着前端拥有自己的路由功能后,用户已经看不到我们服务器的URL了。此外,在使用 REST 时编写API需要考虑多少种参数传递方式呢?

方式示例缺点
URL 目录/user/1只能传递单值、没有类型、不支持数组和对象
URL Params/user/?id=1没有类型、不支持数组和对象
Request HeaderX-Foo-Bar没有类型、不支持数组和对象
Request Body{"name": "alice"}
Request MethodPOST用于表达执行动作的类型,为 GET、POST、PUT、DELETE 其中之一

此外,REST 在许多情况下也会限制我们的 API 表达能力。当我们想要实现与用户相关的 API,包括注册、登录和 BREAD 操作时,直觉上的 URL 应该是这样的:

/user/register
/user/login

在 REST 中,如果我们严格遵循规范,就会面临选择登录和注册的 HTTP 方法的困扰。如果我们将注册尚且可以视为添加,使用 POST 方法,那么,登录又该怎么办呢?而且,如果我的管理后台,需要拥有一个添加用户的方法,如何区分添加和注册?

该使用怎样的风格

是时候,采用一种在 REST 基础上进行简化的新风格了!

对于这种新风格,首先,应该所有的请求都使用 POST 方法,这样就可以全部使用 Body 来传递参数数据了。

并且,为了能够实现类型安全,Body 中使用 JSON 这种拥有类型的数据格式,抛弃掉 form-data/urlencoded 等老旧的数据格式。

既然没有了方法之分,那么,我们可以用 URL 中的目录代表资源,而文件名则用来描述动作,来完成取代方法的职责。

/article/add
/article/delete
/article/comment/add
/article/comment/delete
...

至于参数部分,所有的参数,全部放在 Request Body 内,对于 Milkio 而言,以 JSON 形式通过 Body 发送的数据,会被序列化为你代码中的 params,并享有类型安全。一个实际的请求如下:

终端窗口
POST /article/add
Content-Type: application/json
{
"title": "Hello world!",
"content": "Welcome to example.com. This is your first post. Edit or delete it and start blogging!"
}

而在 Milkio 中,你可以轻易地接收它。

/src/apps/article/add.ts
export const api = defineApi({
meta: {},
async action(params: { title: string, content: string }) {
// ...
},
});

BREAD 模板

当你学会使用 Milkio,并决定要使用的其他技术栈后,你可能会觉得每次重复编写 BREAD 代码都很繁琐。通过 Milkio 的模板 功能,你可以将这个工程变得很轻松。