试用 NestJS 第 4 部分:从 OpenAPI 文档生成 Typescript 客户端
本文最初发表于我的个人博客.
简介
在我的上一篇博文中,我们看到使用 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 应用之间的任何不同步。
更多推荐
所有评论(0)