本文最初发表于我的个人博客.

简介

在我的上一篇博文中,我们看到使用 NestJS 开始使用 OpenAPI 是多么容易。

在这篇博文中,我想向您展示如何利用生成的 OpenAPI 文档来生成将在 React 应用程序中使用的 typescript 客户端。

我为什么要这么做?我喜欢有静态类型的端点,而不是自己打字。此外,它是自动生成的,这意味着我们可以在 CI 中自动生成,并确保在编译时一切正常。

入门

该项目这一部分的源代码可在此处获得:https://github.com/arnaud-cortisse/trying-out-nestjs-part-4。

OpenAPI 生成器

我们可以使用很多工具来生成 OpenAPI 客户端。

我要使用的是以下内容:typescript-axios。

OpenAPI 文档

在上一篇博文中,我只告诉过你http://localhost:3001/api/,它承载了 Swagger UI。

但是还有另一个关键端点:http://localhost:3001/api-json。该端点托管生成的 OpenAPI 文档,我们将参考该文档以生成客户端。

为 OpenAPI 生成器设置环境

OpenAPI 生成器工具要求我们在我们的机器上安装几个依赖项,但我不喜欢让我的机器因项目特定的依赖项而膨胀。

让我们再次尝试使用 Docker!

准备文件

在根文件夹中,执行以下命令:

  • mkdir -p tools/openapi-generator

  • cd tools/openapi-generator

  • touch Dockerfile

  • touch openapitools.json

  • touch generate.sh

  • touch .gitignore

工具/openapi-generator/Dockerfile

通过访问 NestJS 的/api-json端点,此 docker 映像将用于生成 OpenAPI 文档。

FROM timbru31/java-node:jdk-14
RUN npm install @openapitools/openapi-generator-cli -g
RUN mkdir /local
WORKDIR /local
COPY . .
CMD ["sh", "generate.sh"]

进入全屏模式 退出全屏模式

  • 我们使用预装了 JDK 的 docker 镜像(因为openapi-generator-cli需要它)。

  • 我们安装openapi-generator-cli

  • 我们创建一个文件夹/local并将/tools/openapi-generator中的所有内容复制到其中。

  • 启动图像时,我们启动脚本generate.sh(我们仍然需要填充它)。

工具/openapi-generator/openapitools.json

OpenAPI 生成器配置文件。有关详细信息,请参阅配置。

{
  "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json",
  "spaces": 2,
  "generator-cli": {
    "version": "5.0.0"
  }
}

进入全屏模式 退出全屏模式

工具/openapi-generator/generate.sh

启动新定义的Dockerfile时执行的脚本。

openapi-generator-cli generate \
    -i http://nestjs:3001/api-json \
    --generator-name typescript-axios \
    -o /local/out \
    --additional-properties=useSingleRequestParameter=true

进入全屏模式 退出全屏模式

  • -i参数表示 OpenAPI 文档所在的位置。在这里,我决定使用http://nestjs:3001/api-json而不是http://localhost:3001/api-json(两者都可以,但我更喜欢前者)。您将无法在浏览器中访问http://nestjs:3001/api-json,因为它不是您能够在您的机器上解析的名称(但这可以在 docker-compose 中解析,因为两个图像都将在同一个网络中运行)。

  • --generator-name表示我们要使用的生成器。

  • -o表示我们要在哪里输出生成的文件。

  • --additional-properties用于为生成器提供附加参数(参见本页)。

工具/openapi-generator/.gitignore

我们不想在此文件夹中对生成器输出的文件进行版本控制(但我们将在 React 应用程序中对生成的文件进行版本控制)。

.build

进入全屏模式 退出全屏模式

修改docker-compose.yml

让我们可以从现有的docker-compose文件开始openapi_generator

  openapi_generator:
    build:
      context: ./tools/openapi-generator
      dockerfile: Dockerfile
    depends_on:
      - nestjs
    volumes:
      - ./tools/openapi-generator/.build:/local/out

进入全屏模式 退出全屏模式

  • 我们使服务依赖于nestjs。这样,如果之前没有启动过nestjs,它将启动它。实际上,nestjs必须运行才能使openapi_generator能够生成客户端 API。

  • 我们将文件夹./tools/openapi-generator/.build挂载到服务中,将在其中生成客户端(我们在上面自己配置了该路径)。这样,我们就可以访问主机上生成的文件。

修改根package.json

在根package.json中,添加以下脚本:

"scripts": {
  ...
    "generate-api-client": "docker-compose up --build openapi_generator"
  ...
}

进入全屏模式 退出全屏模式

试用 OpenAPI 生成器

在根文件夹中,键入以下内容:

  • npm run generate-api-client

如果一切顺利,您应该在此文件夹中有文件:tools/openapi-generator/.build

如果您没有任何文件,可能是因为nestjs服务在生成器尝试访问它时尚未准备好。只需尝试重新启动npm run generate-api-client,一切都应该没问题。

将客户端交付给 React App。

在根文件夹中,执行以下命令:

  • mkdir scripts

  • touch scripts/update-api.sh

更新-api.sh

#!/bin/bash
cd "$(dirname "$0")"

SOURCE_FOLDER="../tools/openapi-generator/.build"
DEST_FOLDER="../packages/react-app/src/api/generated"

rm -rf $DEST_FOLDER
mkdir -p $DEST_FOLDER
cp $SOURCE_FOLDER/**.ts $DEST_FOLDER

进入全屏模式 退出全屏模式

使用这个脚本,我们实质上是将服务openapi_generator自动生成的文件交付给 React 应用程序。

修改根package.json

在根package.json中,添加以下脚本:

"scripts": {
  ...
    "update-api-client": "sh ./scripts/update-api.sh",
    "generate-and-update-api-client": "npm run generate-api-client && npm run update-api-client"
  ...
}

进入全屏模式 退出全屏模式

试用传递机制

在根文件夹中,键入以下内容:

  • npm run generate-and-update-api-client

如果一切顺利,您应该在packages/react-app/src/api/generated中有文件。

在 React App 中使用客户端

安装新的依赖项

packages/react-app/src目录下,执行以下命令:

  • npm install axios react-query

删除部分文件

  • cd packages/react-app/src

  • rm App.css App.test.tsx App.tsx

创建新文件

  • cd packages/react-app/src

  • mkdir axios

  • mkdir api(但它应该已经存在)

  • mkdir components

  • touch axios/axios-client.ts

  • touch api/api.ts

  • touch components/App.tsx

  • touch components/Example.tsx

包/react-app/src/axios/axios-client.ts

用于配置 axios 实例,使其预先配置为访问 NestJS。

import axios, { AxiosRequestConfig } from "axios";

export const axiosBaseUrl = `${process.env.REACT_APP_BACKEND_SCHEMA}://${process.env.REACT_APP_BACKEND_HOSTNAME}:${process.env.REACT_APP_BACKEND_PORT}`;

export const axiosConfig: AxiosRequestConfig = {
  baseURL: axiosBaseUrl,
};

const axiosBackendClient = axios.create(axiosConfig);

export default axiosBackendClient;

进入全屏模式 退出全屏模式

包/react-app/src/api/api.ts

配置TasksApi的实例(由生成器自动生成的类),我们将使用它与后端进行通信。

import axiosBackendClient, { axiosBaseUrl } from "../axios/axios-client";
import { TasksApi } from "./generated";

export const tasksApi = new TasksApi(
  {
    basePath: axiosBaseUrl,
    isJsonMime: () => false,
  },
  undefined,
  axiosBackendClient
);

进入全屏模式 退出全屏模式

包/react-app/src/components/App.tsx

import React from "react";

import { QueryClient, QueryClientProvider } from "react-query";
import Example from "./Example";

const queryClient = new QueryClient();

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  );
}

进入全屏模式 退出全屏模式

  • 我们配置react-query提供者。

  • 我们渲染Example组件(尚未定义)。

包/react-app/src/components/Example.tsx

import { useQuery } from "react-query";
import { tasksApi } from "../api/api";

export default function Example() {
  const id = "fake id";

  const { isLoading, error, data } = useQuery(`tasks_find_one_${id}`, () =>
    tasksApi.tasksControllerFindOne({
      id,
    })
  );

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

  if (error as Error) return <div>An error has occurred</div>;

  return <div>{data?.data.title}</div>;
}

进入全屏模式 退出全屏模式

看看查询。这就是神奇的地方:我们利用自动生成的客户端,因此拥有静态类型的所有好处。

修改现有文件

包/react-app/src/index.tsx

我刚刚删除了一些无用的行(在本博客的上下文中)并从适当的路径导入了App组件。

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./components/App";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

进入全屏模式 退出全屏模式

试用客户端

在根文件夹中,执行以下操作:

  • docker-compose up --build(可能需要一段时间,因为要安装的 React App 中有新的依赖项)。

在浏览器中浏览http://localhost:3000/

您应该在某个时候收到以下消息:An error has occurred

打开您的开发人员工具:您应该会看到 CORS 错误。我们可以通过更新 Nest 应用来解决这个问题。

启用 CORS

packages/nestjs/src/main.ts中,添加以下内容

...
  app.enableCors();
...

进入全屏模式 退出全屏模式

请注意,您绝对应该在生产环境中适当地配置 CORS 规则。

测试一切

现在,如果您在浏览器中继续http://localhost:3000/,您应该会看到消息fake title

这意味着我们确实能够使用自动生成的客户端与我们的 API 进行通信。

最后的话

设置一切并不简单。尽管如此,我们现在有了一种与 API 通信的好方法:我们有一个类型化的客户端,它将极大地改善 React 内部的开发体验。更重要的是,重新生成该客户端以使其匹配最新的 API 基本上不需要任何成本。最后,我们现在能够在编译时检测到 React 应用和 NestJS 应用之间的任何不同步。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐