轻识Logo

搜索

下载APP
目录

    RESTful API 设计最佳实践

    JavaScript之禅

    共 8982字,需浏览 18分钟

     ·

    2021-04-15 08:07

    ☝点击上方蓝字关注我们


    简介

    RESTful API 是目前最流行的API设计规范,它用于Web数据接口的设计。它允许包括浏览器在内的各种客户端与服务器进行通信。因此正确设计我们的RESTful是相当重要的!我们的API必须安全、高性能、同时易于使用。

    在本文我们将探讨如何设计出易于使用并且安全快速的的RESTful API。

    RESTful API 即是基于Rest构建的API。那么在开始之前,我们先来看看 REST 是什么?

    REST与技术无关,它代表的是一种软件架构风格,REST它是 Representational State Transfer 的简称,中文的含义是:  表现层状态转移(转移:通过HTTP动词实现)。即 URL 定位资源,HTTP动词操作(GET,POST,PUT,DELETE)描述操作。

    确保接受并响应 JSON数据格式

    RESTful API 应该接受 JSON 格式的请求,并返回的响应体也应该是JSON格式的。JSON 是一种数据传输标准,主流编程语言几乎都能很好的支持它。同时在浏览器中我们的JavaScript也能很轻松方便的操作这些数据。所以,以JSON 格式编写的 RESTful API 具有简单、易读、易用的特点。

    为了确保当我们的RESTful API 服务使用 JSON 格式响应,我们应该将其响应头的Content-Type设置为application/json。

    让我们来看一个接收 JSON 数据并返回 JSON 数据的 API 示例。本示例使用Node.js的 Express 框架。我们使用了 body-parser 中间件来解析 JSON 请求体,然后使用 res.json 返回传入的 JSON 对象。

    const express = require('express');
    const bodyParser = require('body-parser');

    const app = express();

    app.use(bodyParser.json());

    app.post('/', (req, res) => {
      res.json(req.body);
    });

    app.listen(3000, () => console.log('server started'));

    在本示例中 bodyParser.json() 将 JSON 请求体的字符解析为 JavaScript 对象,然后将其分配给该req.body对象。

    在 API 路径中使用名词代替动词

    RESTful API是面向资源的API,HTTP动词操作(GET,POST,PUT,DELETE)描述操作。

    我们不应该在URL路径中使用动词。我们应该使用要操作的实体的名词作为路径名。因为我们的HTTP请求方法本身就是动词,就能描述要进行的操作,如常见的方法包括 GET,POST,PUT和 DELETE,这些请求方法即可完成 CRUD。

    • GET 检索资源。

    • POST 将新数据提交到服务器。

    • PUT 更新现有数据。

    • DELETE 删除数据。

    例如,我们有个文章(/articles/)资源。我们对其进行CRUD的RESTful API如下:

    • 使用 GET  /articles/来获取文章列表
    • 使用 POST /articles/添加新文章
    • 使用 PUT /articles/:id 更新给定ID的文章
    • 使用 DELETE/articles/:id 删除具有给定ID的文章

    我们通过Express来实现上面这个增删改查的例子,如下所示:

    const express = require('express');
    const bodyParser = require('body-parser');

    const app = express();

    app.use(bodyParser.json());

    app.get('/articles', (req, res) => {
      const articles = [];
      // code to retrieve an article...
      res.json(articles);
    });

    app.post('/articles', (req, res) => {
      // code to add a new article...
      res.json(req.body);
    });

    app.put('/articles/:id', (req, res) => {
      const { id } = req.params;
      // code to update an article...
      res.json(req.body);
    });

    app.delete('/articles/:id', (req, res) => {
      const { id } = req.params;
      // code to delete an article...
      res.json({ deleted: id });
    });

    app.listen(3000, () => console.log('server started'));

    在上面的示例代码中,我们定义了API来操作文章(articles)资源。如我们所见,API URL路径中使用的都是名词,作为动词的请求方法说明了API的操作意图。

    使用名词复数

    我们应该使用复数名词来命名集合。

    通常,我们想要取得的数据都是一个集合,而不是单个项目。同时数据库中的表也是具有多个条目的。所以我们的API 也应该使用复数名词,这样更合乎情理。

    嵌套分层的资源对象

    在处理嵌套资源的API时,应该将嵌套资源附加到父资源的路径之后。

    例如一个文章有评论列表,获取某个文章的评论列表的API则为:

    GET /articles/:articleId/comments

    我们可以使用express来做个示范:

    const express = require('express');
    const bodyParser = require('body-parser');

    const app = express();

    app.use(bodyParser.json());

    app.get('/articles/:articleId/comments', (req, res) => {
      const { articleId } = req.params;
      const comments = [];
      // code to get comments by articleId
      res.json(comments);
    });


    app.listen(3000, () => console.log('server started'));

    使用标准的http状态码

    为了消除 API server 发生错误时用户的困惑,我们应该优雅地处理错误,并返回指示发生了具体错误的HTTP响应代码以及明确的错误信息。这可以很好的为API使用者提供了足够的信息来了解所发生的问题。

    常见的错误HTTP状态代码包括:

    • 400 错误的请求 – 这意味着客户端输入验证失败。
    • 401 未经授权 - 这意味着用户无权访问资源。通常在用户未通过身份验证时返回。
    • 403 禁止访问 - 表示用户已通过身份验证,但不允许其访问资源。
    • 404 Not Found – 表示找不到资源。
    • 500 内部服务器错误 – 这是一般服务器错误。它可能不应该明确地抛出。
    • 502 错误的网关 - 这表明来自上游服务器的无效响应。
    • 503 服务不可用 – 这表示服务器端发生了意外情况(可能是服务器过载,系统某些部分发生故障等)。

    我们应该抛出服务错误相对应的错误码。例如,如果我们要拒绝客服端发起的请求,则应在Express API中返回如下所示的 400 响应:

    const express = require('express');
    const bodyParser = require('body-parser');

    const app = express();

    // existing users
    const users = [
      { email: 'abc@foo.com' }
    ]

    app.use(bodyParser.json());

    app.post('/users', (req, res) => {
      const { email } = req.body;
      const userExists = users.find(u => u.email === email);
      if (userExists) {
        return res.status(400).json({ error: 'User already exists' })
      }
      res.json(req.body);
    });


    app.listen(3000, () => console.log('server started'));

    在上面的示例中,用户尝试创建一个已经存在的user,将获得 400 响应状态代码,并带有一条'User already exists'的错误消息,让用户知道该用户已经存在。利用这些信息,用户可以通过使用其他 email 来创建新用户。

    通常错误代码需要附带明确错误消息,以便用户有足够的信息来了解自己遇到了什么问题。

    每当我们的API未成功调用时,都应通过发送明确的错误信息来帮助用户采取纠正措施来完成操作。

    添加过滤,排序和分页功能

    通常我们的数据都会非常庞大。我们不可能一次全部返回,这会非常慢也可能导致系统崩溃。因此,我们需要有过滤,分页数据的方式。过滤和分页都可以通过减少消耗服务器资源来提高性能。这些功能相当基础且重要。

    分页、过滤、排序查询都功能都应该使用查询参数来实现。如:

    /employees?page=1&pageSize=10&firstName=Xing

    下面这就是一个带有过滤查询的示例:

    const express = require('express');
    const bodyParser = require('body-parser');

    const app = express();

    // employees data in a database
    const employees = [
      { firstName: 'Jane', lastName: 'Smith', age: 20 },
      //...
      { firstName: 'John', lastName: 'Smith', age: 30 },
      { firstName: 'Mary', lastName: 'Green', age: 50 },
    ]

    app.use(bodyParser.json());

    app.get('/employees', (req, res) => {
      const { firstName, lastName, age } = req.query;
      let results = [...employees];
      if (firstName) {
        results = results.filter(r => r.firstName === firstName);
      }

      if (lastName) {
        results = results.filter(r => r.lastName === lastName);
      }

      if (age) {
        results = results.filter(r => +r.age === +age);
      }
      res.json(results);
    });

    app.listen(3000, () => console.log('server started'));

    保持良好的安全意识

    客户端和服务器之间的大多数通信应该是私有的。因此,必须使用SSL/TLS进行安全保护。现在加载SSL成本是相当低的。我们没有理由不使用它。

    同时,不同的用户具有不同的数据访问权限。例如,普通用户不应该能够访问其他用户的信息。他们也不应该能够访问管理员的数据。

    适当缓存数据以提高性能

    可以适当添加缓存服务,从缓存中返回常用数据,而不是每次都从数据库去读取。缓存的好处是可以更快地获取数据,但是也让我们获取最新的数据变得复杂。缓存方式有很多如:Redis、内存缓存( in-memory cache)等等,我们应该根据自己的应用具体情况来选择是不是该用缓存,使用哪种缓存机制。

    这儿我们来使用Express的apicache中间件来实现一个简单的内存缓存:

    const express = require('express');
    const bodyParser = require('body-parser');
    const apicache = require('apicache');
    const app = express();
    let cache = apicache.middleware;
    app.use(cache('5 minutes'));

    // employees data in a database
    const employees = [
      { firstName: 'Jane', lastName: 'Smith', age: 20 },
      //...
      { firstName: 'John', lastName: 'Smith', age: 30 },
      { firstName: 'Mary', lastName: 'Green', age: 50 },
    ]

    app.use(bodyParser.json());

    app.get('/employees', (req, res) => {
      res.json(employees);
    });

    app.listen(3000, () => console.log('server started'));

    版本化我们的API

    原则上我们应该尽量让API避免破坏性变更,保持向后兼容。但是经常有些时候破坏性的变更是不可避免的,这时版本化的API就派上用场了。当我们发布了不兼容或重大更改变,则可以将其发布在新版本中的API。

    我们通常通过URL来实现版本化,及添加版本号在我们API路径的开头,例如:api.liuxing.io/v1 api.liuxing.io/v2

    我们可以在 express 很简单的实现版本化的RESTful API:

    const express = require('express');
    const bodyParser = require('body-parser');
    const app = express();

    app.use(bodyParser.json());

    app.get('/v1/employees', (req, res) => {
      const employees = [];
      // code to get employees
      res.json(employees);
    });

    app.get('/v2/employees', (req, res) => {
      const employees = [];
      // different code to get employees
      res.json(employees);
    });

    app.listen(3000, () => console.log('server started'));

    总结

    设计高质量RESTful API的最重要的一点是遵循Web标准和约定以保持一致性。JSON、SSL/TLS和HTTP状态代码都是现代Web的标准。性能也是重要的考虑因素。我们可以使用分页、缓存等手段来提升性能。可维护性可扩展性也是我们需要考虑的。


    往期精彩回顾:


    Git 工作流程及分支策略

    聊聊JavaScript单元测试

    开发并发布一个健壮的npm包

    这些JavaScript方法将在短短几分钟内提升你的技能

    如何循序渐进地学习JavaScript?





    左手代码右手砖,抛砖引玉

    给点个赞,好不好啊


    浏览 60
    点赞
    评论
    收藏
    分享

    手机扫一扫分享

    分享
    举报
    评论
    图片
    表情
    视频
    全部评论
    推荐
    RESTful API 设计最佳实践
    JAVA乐园
    0
    Restful API 设计最佳实践
    JAVA乐园
    0
    一文详解 API 设计最佳实践
    IT牧场
    0
    一文详解 API 设计最佳实践
    架构之美
    0
    一文详解 API 设计最佳实践
    公众号程序猿DD
    0
    一文详解 API 设计最佳实践
    肉眼品世界
    0
    一文详解 API 设计最佳实践
    Java资料站
    0
    系统设计:API 接口的最佳实践
    泥瓦匠BYSocket
    0
    设计 API 的 22 条最佳实践,实用!
    程序员内点事
    0
    RESTful架构和RESTful API设计总结
    开发者全社区
    0
    点赞
    评论
    收藏
    分享

    手机扫一扫分享

    分享
    举报
    2023©技术圈隐私协议用户协议关于我们机器人