如何在前端自动使用 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 });
}
);
文档。
为 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 = () => {};
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
然后我们将创建一个简单的fastify实例,将模块作为插件附加fastify-swagger,fastify-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();
现在,我们可以启动服务器,并通过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}`);
});
});
}
可以安全地获取此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 });
});
上面的代码定义了两个users端点,一个用于创建用户,另一个用于检索系统中所有可用用户。
该retrieveSchema定义将验证响应,并对createSchema请求和响应进行序列化和验证。我们还可operationId以为端点添加 `<head>` 和 `<body>` 标签,使其更具描述性,并在构建 API 客户端时引用它们。
const retrieveSchema = {
operationId: "retrieveUsers",
tags: ["users"],
// ...
};
const createSchema = {
operationId: "createUser",
tags: ["users"],
// ...
};
最后,在构建 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
接下来,我们将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,
},
},
};
Orval 支持将生成的代码拆分成多个文件,并按照我们之前在模式定义中定义的标签进行组织。此外,我们还需要配置生成的 API 客户端的输入和输出路径,以及我们要使用的框架。在本例中,我们将使用SWRReact 客户端。
然后,我们将在package.json文件中添加一个脚本来生成 API 客户端,一切就准备就绪了。
{
"scripts": {
"generate:api": "orval"
}
}
npm run generate:api从命令行运行程序将在client/src/sdk文件夹中生成客户端 SDK。
使用SDK。
自动生成的 API 客户端将包含我们在 OpenAPI 规范中声明的所有端点,在本例中为 ` createUserand`retrieveUsers端点,因为它们是通过 ` createSchemaand`retrieveSchema定义定义的。
Orval 将 API 调用封装在一个自定义 hook 中,我们可以导入并使用它来获取数据。该 hook 返回 `get_data` users、`get_name`isLoading和isError`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>
);
};
接下来,我们还需要导入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>
);
};
测试客户端。
借助 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());
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();
});
});
同构数据验证。
由于 OpenAPI 规范与编程语言无关,我们可以利用其输出在前端和后端验证数据。这种方法非常合理,因为我们可以在技术栈的两端提供统一的验证规则。有一些库可以帮助我们完成这项任务,例如typed-openapi或openapi-zod-client,它们可以将 OpenAPI 定义作为输入并将其转换为zod模式定义。
自动化 SDK 生成。
我们的后端和前端代码可能不在同一个代码仓库中,因此我们需要一种方法来自动检测任何规范变更并同步客户端 SDK。这可以通过使用 CI/CD 流水线轻松实现。我们可以集成 GitHub Actions,由任何后端代码提交触发,从而生成客户端 SDK 并将其提交到我们的前端代码仓库。
此外,由于我们可以根据标签过滤将生成的代码拆分成多个部分,因此我们还可以从不同的资源甚至公开可用的 API 创建不同的 SDK。SwaggerHub、RapidAPI 和 APIs.guru 上提供了大量公开可用的OpenAPI规范。
无需后端即可运行客户端。
由于 OpenAPI 可以有效地描述我们的资源,我们可以重用它来生成一个虚拟服务器,该服务器可用于后续的开发和测试,而无需启动任何实际服务。有一些工具可以帮助我们完成这项任务,例如Prism、OpenAPI Mock、OpenAPI 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
结论。
本文探讨了如何使用 Swagger 和 OpenAPI 在前端自动化调用 RESTful API。借助一系列广泛可用的工具,我们可以显著减少样板代码,提高代码效率,并缩短 API 开发时间。简化前端和后端团队之间的 API 开发流程,还能促进双方更好的沟通与协作,最终提升代码和产品质量。
文章来源:https://dev.to/vorillaz/how-to-automatically-consume-restful-apis-in-your-frontend-48ba