发布于 2026-01-06 2 阅读
0

如何在前端自动使用 RESTful API

如何在前端自动使用 RESTful API

这是从我的博客转载的文章:https://www.vorillaz.com/consume-apis/

问题。

我过去曾广泛参与的一项任务是设计、开发、部署和最终使用大量的 RESTful API,我可以告诉你,这是 Web 开发人员必须面对的最困难的任务之一。

唉,休息真痛苦。

本文不会对 RESTful API 的优缺点进行抱怨、争论或比较,但我可以告诉你,REST API 的开发过程可能非常痛苦。我们需要解决很多棘手的问题,例如:

API变更。

当后端发生变更时,前端必须始终保持同步。API 调用点或返回数据量的微小变化都可能导致前端崩溃,而单元测试几乎不可能捕获到与后端交互相关的任何错误。

app.post(
  "/create",
  {
    schema: {
      body: {
        type: "object",
        properties: {
          name: { type: "string" },
          // This parameter addition will break the frontend.
          surname: { type: "string" },
        },
        required: ["name", "surname"],
      },
    },
  },
  (request, reply) => {
    reply.send({ data }); 
  }
);
Enter fullscreen mode Exit fullscreen mode

文档。

为 API 中的每个端点提供详细文档是一种常见且始终有效的实践,尽管这项工作维护起来相当繁琐耗时。我们必须确保文档与实际代码变更保持同步。然而,文档经常过时,无法反映实际的 API 接口,从而导致用户困惑和 bug。

型式安全

在前端使用 API 时,我们必须格外小心通过网络接收和发送的数据类型。类型安全在前端至关重要,无论是调用 API 还是渲染数据,都必须确保其安全性。

API 消耗

RESTful API 通常包含大量样板代码。我们需要处理诸如调用 API、处理响应、管理错误等任务。即使借助诸如 `Resources` axios、` SWRResources` 和 ` Resources` 之类的库TanStack Query,我们仍然需要一个自定义抽象层来简化客户端调用。然而,随着 API 的增长,抽象层将变得越来越复杂,维护难度也会越来越大。

// Using SWR to fetch the data for each endpoint
import useSWR from 'swr'

const fetcher = (url: string) => fetch(url).then((res) => res.json());
// Using SWR to fetch the data for users.
export const useUsers = () => {
  const { data, error } = useSWR("/api/users", fetcher);
  return {
    users: data,
    isLoading: !error && !data,
    isError: error,
  };
};
// More API calls...
export const useArticles = () => {};
export const useProjects = () => {};
Enter fullscreen mode Exit fullscreen mode

GraphQL 或 tRPC 能否力挽狂澜?

GraphQL 和 tRPC 都是 RESTful API 的绝佳替代方案,它们不仅提供了诸多优势,还能有效解决与“传统”REST 相关的许多挑战。但是,如果我们“技术上”无法负担使用它们该怎么办呢?

这两种解决方案都需要学习新的语法和新的思维方式,实际上增加了我们技术栈的复杂性。
此外,特别是 tRPC,它与 Node.js 生态系统紧密集成,无法用于 Python、Ruby 或 Go 等其他语言。这种限制可能会带来问题,尤其是在 Node.js 后端 API 并非总是可行的情况下。

那么,在这些限制条件下,我们还有哪些替代方案?

Swagger 和 OpenAPI。

为了实现前端代码与后端 API 的自动连接,我们需要在两者之间建立一种通用语言。后端 API 的文档必须以前端能够理解和使用的方式编写,而 Swagger 和 OpenAPI 正是为此而生的。

Swagger是一个开源软件框架,它实现了OpenAPI规范——一种用于 REST API 的 API 描述格式。OpenAPI 规范定义了一个标准的、与语言无关的 HTTP API 接口,使人和计算机都能发现并理解 API 的功能。

使用该规范,我们可以描述 API 的参数、响应、错误,并记录所有内容,所有这些都可以在我们代码库中的一处完成。

构建API。

为了演示如何在前端自动生成客户端 SDK,我们将开发一个非常简单的 API。我们将使用Fastify ,但您可以选择任何您喜欢的框架。Swagger 和 OpenAPI 都拥有非常活跃的社区支持,并且大多数流行的框架和语言都有大量的集成方案。

我们可以先安装后端所需的依赖项。

❏ ~ npm i fastify fastify-swagger @fastify/swagger-ui
Enter fullscreen mode Exit fullscreen mode

然后我们将创建一个简单的fastify实例,将模块作为插件附加fastify-swaggerfastify-swagger-ui并配置 OpenAPI 规范元数据。

import fastify from "fastify";
import ui from "@fastify/swagger-ui";

const start = async () => {
  const server = fastify();
  await server.register(swagger, {
    openapi: {
      info: {
        title: 'Test OpenAPI',
        description: 'How cool is that?',
        version: '0.1.0',
      },
      servers: [
        {
          url: 'http://localhost:3000',
          description: 'Development server',
        },
      ],
    },
  });
  await server.register(ui, {});

  server.listen({ port: 3001 }, (err, address) => {
    onsole.log(`Server listening at ${address}`);
  });
};

start();
Enter fullscreen mode Exit fullscreen mode

现在,我们可以启动服务器,并通过http://localhost:port/documentation访问 Swagger UI。目前文档为空,稍后我们会添加端点。您也可以在 Swagger 的在线演示中查看 Swagger UI 的外观

最后,在开发模式下启动服务器后,我们将以 JSON 格式导出 OpenAPI 规范,并将其保存到名为 `.openAPI.json` 的本地文件中openapi.json,这样我们的后端设置就几乎完成了。

if (process.env.NODE_ENV === "development") {
  const spec = "./openapi/openapi.json";
  const specFile = path.join(__dirname, spec);
  server.ready(() => {
    const apiSpec = JSON.stringify(server.swagger() || {}, null, 2);

    writeFile(specFile, apiSpec).then(() => {
      console.log(`Swagger specification file write to ${spec}`);
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

可以安全地获取此openapi.json规范文件并将其提交到我们的存储库中,我们的前端将使用它来自动发现 API 并使用它。

与此同时,我们将扩展后端,新增两个接口:一个用于获取数据,另一个用于创建数据。Fastify基于JSON Schema构建,采用基于模式的方法
,为 API 序列化和验证提供了开箱即用的支持 通过相应的选项,我们可以为每个路由附加模式定义。
schema

const retrieveSchema = {
  response: {
    200: {
      type: "object",
      properties: {
        data: {
          type: "array",
          items: {
            type: "object",
            properties: {
              id: { type: "number" },
              name: { type: "string" },
            },
          },
        },
      },
    },
  },
};

const createSchema = {
  body: {
    type: "object",
    properties: {
      name: { type: "string" },
    },
    required: ["name"],
  },
  response: {
    200: {
      type: "object",
      properties: {
        data: {
          type: "object",
          properties: {
            id: { type: "number" },
            name: { type: "string" },
          },
        },
      },
    },
  },
};

const data = [
  { id: 1, name: "John" },
  { id: 2, name: "Jane" },
  { id: 3, name: "Jack" },
];

app.post("/users", { schema: createSchema }, (request, reply) => {
  reply.send({ users: data });
});

app.get("/users", { schema: retrieveSchema }, (request, reply) => {
  reply.send({ users: data });
});
Enter fullscreen mode Exit fullscreen mode

上面的代码定义了两个users端点,一个用于创建用户,另一个用于检索系统中所有可用用户。
retrieveSchema定义将验证响应,并对createSchema请求和响应进行序列化和验证。我们还可operationId以为端点添加 `<head>` 和 `<body>` 标签,使其更具描述性,并在构建 API 客户端时引用它们。

const retrieveSchema = {
  operationId: "retrieveUsers",
  tags: ["users"],
  // ...
};

const createSchema = {
  operationId: "createUser",
  tags: ["users"],
  // ...
};
Enter fullscreen mode Exit fullscreen mode

最后,在构建 API 的过程中,我们可以看到 t 时刻的输出也在发生变化,反映了我们对端点模式定义所做的更新。我们还可以根据规范openapi/openapi.json,为每个路由添加额外信息,例如描述、摘要或示例值

使用 API。

现在,是时候使用我们的 API 了。本教程将使用React,但您也可以使用任何其他您喜欢的框架;流程相同。此外,我们将使用SWR从 API 获取数据,并使用 TypeScript 来确保类型安全。

生成 API 客户端。

为了生成 API 客户端,有几种选择,但我们将使用 Orval [ https://orval.dev ]。Orval 是一个命令行工具,它基于 OpenAPI 规范生成 API 客户端。它支持 TypeScript、JavaScript、Axios、React、Vue、Angular 和 Svelte,并且高度可定制。

作为替代方案,您还可以使用官方的OpenAPI 生成器,这是一个更通用的工具,支持多种语言和框架。

安装和使用orval

在我们的前端代码库中,我们将orval使用以下命令进行安装:

❏ ~ npm i -D orval
Enter fullscreen mode Exit fullscreen mode

接下来,我们将orval.config.js在项目根目录下创建一个名为`.config`的配置文件,并添加以下配置:

const path = require("path");

const input = path.join(__dirname, "server", "openapi", "openapi.json");
const output = path.resolve(__dirname, "client", "src", "sdk");

module.exports = {
  sdk: {
    output: {
      clean: true,
      prettier: true,
      mode: "tags-split",
      target: path.join(output, "api"),
      schemas: path.join(output, "schemas"),
      client: "swr",
      // ... more opts
    },
    input: {
      target: input,
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Orval 支持将生成的代码拆分成多个文件,并按照我们之前在模式定义中定义的标签进行组织。此外,我们还需要配置生成的 API 客户端的输入和输出路径,以及我们要使用的框架。在本例中,我们将使用SWRReact 客户端。

然后,我们将在package.json文件中添加一个脚本来生成 API 客户端,一切就准备就绪了。

{
  "scripts": {
    "generate:api": "orval"
  }
}
Enter fullscreen mode Exit fullscreen mode

npm run generate:api从命令行运行程序将在client/src/sdk文件夹中生成客户端 SDK。

使用SDK。

自动生成的 API 客户端将包含我们在 OpenAPI 规范中声明的所有端点,在本例中为 ` createUserand`retrieveUsers端点,因为它们是通过 ` createSchemaand`retrieveSchema定义定义的。

Orval 将 API 调用封装在一个自定义 hook 中,我们可以导入并使用它来获取数据。该 hook 返回 `get_data` users、`get_name`isLoadingisError`get_name` 标志,并且还为我们通过网络接收的数据提供类型安全保障。

// Importing the generated API client
import { useUsers } from "sdk/api/users";

export const Users = () => {
  const { users, isLoading, isError } = useUsers();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (isError) {
    return <div>Error...</div>;
  }

  return (
    <ul>
      {users?.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};
Enter fullscreen mode Exit fullscreen mode

接下来,我们还需要导入createUser调用POST /users端点的函数。函数名同样基于operationId我们在定义中定义的端点createSchema
请注意,传递的参数是类型安全的,如果传递无效的有效负载,函数会抛出错误。

import { useUsers, createUser } from "sdk/api/users";

export const Users = () => {
  const { users, isLoading, isError } = useUsers();
  const handleCreateUser = () => {
    createUser({ name: "John" });
  };

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (isError) {
    return <div>Error...</div>;
  }

  return (
    <div>
      <button onClick={handleCreateUser}>Create User</button>
      <ul>
        {users?.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

测试客户端。

借助 Orval,我们还可以将 API 客户端集成到单元测试中。Orval 通过 Mock Service Worker (Mock Service Worker) [ https://mswjs.io/ ] 库提供一流的模拟支持,并且可以自动生成用于测试服务器的 MSW 处理程序。

设置处理程序非常简单,我们只需要从getUsersMSWAPI 客户端导入自动生成的函数,并将其传递给setupServerMSW 中的函数即可。

import { setupServer } from 'msw/node';
import { getUsersMSW } from 'sdk/api/users/users.msw';
export const server = setupServer(...getUsersMSW());
Enter fullscreen mode Exit fullscreen mode

server然后我们就可以在测试中导入和使用模拟实例了。

import { render, screen } from '@testing-library/react';
import { server } from 'setupTests';
import { Users } from './Users';

describe('Users', () => {
  beforeAll(() => server.listen());
  afterEach(() => server.resetHandlers());
  afterAll(() => server.close());

  it('should render the users', async () => {
    render(<Users />);
    expect(await screen.findByText('John')).toBeInTheDocument();
  });
});
Enter fullscreen mode Exit fullscreen mode

同构数据验证。

由于 OpenAPI 规范与编程语言无关,我们可以利用其输出在前端和后端验证数据。这种方法非常合理,因为我们可以在技术栈的两端提供统一的验证规则。有一些库可以帮助我们完成这项任务,例如typed-openapiopenapi-zod-client,它们可以将 OpenAPI 定义作为输入并将其转换为zod模式定义。

自动化 SDK 生成。

我们的后端和前端代码可能不在同一个代码仓库中,因此我们需要一种方法来自动检测任何规范变更并同步客户端 SDK。这可以通过使用 CI/CD 流水线轻松实现。我们可以集成 GitHub Actions,由任何后端代码提交触发,从而生成客户端 SDK 并将其提交到我们的前端代码仓库。

此外,由于我们可以根据标签过滤将生成的代码拆分成多个部分,因此我们还可以从不同的资源甚至公开可用的 API 创建不同的 SDK。SwaggerHub、RapidAPI 和 APIs.guru 上提供了大量公开可用OpenAPI规范

无需后端即可运行客户端。

由于 OpenAPI 可以有效地描述我们的资源,我们可以重用它来生成一个虚拟服务器,该服务器可用于后续的开发和测试,而无需启动任何实际服务。有一些工具可以帮助我们完成这项任务,例如PrismOpenAPI MockOpenAPI Backend以及我们之前已经介绍过的 MSW 库。

此外,使用OpenAPI 生成器,我们可以根据给定的规范在几秒钟内生成一个功能齐全的后端。

# Generate a php laravel server
❏ ~ npx @openapitools/openapi-generator-cli generate -i ./openapi/openapi.json -g php-laravel -o ./server

# Or a nodejs express server
❏ ~ npx @openapitools/openapi-generator-cli generate -i ./openapi.yaml -g nodejs-express-server -o ./another-server
Enter fullscreen mode Exit fullscreen mode

结论。

本文探讨了如何使用 Swagger 和 OpenAPI 在前端自动化调用 RESTful API。借助一系列广泛可用的工具,我们可以显著减少样板代码,提高代码效率,并缩短 API 开发时间。简化前端和后端团队之间的 API 开发流程,还能促进双方更好的沟通与协作,最终提升代码和产品质量。

文章来源:https://dev.to/vorillaz/how-to-automatically-consume-restful-apis-in-your-frontend-48ba