跳转到内容

React 连接到 FastAPI

api-connection 生成器提供了一种快速将 React 网站与 FastAPI 后端集成的方式。它能以类型安全的方式设置连接 FastAPI 后端所需的所有配置,包括客户端和 TanStack Query 钩子生成、AWS IAM 身份验证支持和正确的错误处理。

前提条件

使用此生成器前,请确保你的 React 应用具备:

  1. 渲染应用的 main.tsx 文件
  2. 可运行的 FastAPI 后端(使用 FastAPI 生成器生成)
所需 main.tsx 结构示例
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';
import App from './app/app';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
root.render(
<StrictMode>
<App />
</StrictMode>,
);

使用方法

运行生成器

  1. 安装 Nx Console VSCode Plugin 如果您尚未安装
  2. 在VSCode中打开Nx控制台
  3. 点击 Generate (UI) 在"Common Nx Commands"部分
  4. 搜索 @aws/nx-plugin - api-connection
  5. 填写必需参数
    • 点击 Generate

    选项

    参数 类型 默认值 描述
    sourceProject 必需 string - The source project which will call the API
    targetProject 必需 string - The target project containing your API
    auth string IAM Authentication strategy (choose from IAM or None)

    生成器输出

    生成器将修改 FastAPI 项目中的以下文件:

    • 文件夹scripts
      • generate_open_api.py 添加生成 API 的 OpenAPI 规范的脚本
    • project.json 在构建目标中添加调用上述生成脚本的新目标

    生成器将修改 React 应用中的以下文件:

    • 文件夹src
      • 文件夹components
        • <ApiName>Provider.tsx API 客户端提供者
        • QueryClientProvider.tsx TanStack React Query 客户端提供者
      • 文件夹hooks
        • use<ApiName>.tsx 添加通过 TanStack Query 管理状态调用 API 的钩子
        • use<ApiName>Client.tsx 添加实例化普通 API 客户端的钩子
        • useSigV4.tsx 添加使用 SigV4 签名 HTTP 请求的钩子(如果选择 IAM 认证)
    • project.json 在构建目标中添加生成类型安全客户端的新目标
    • .gitignore 默认忽略生成的客户端文件

    生成器还会在网站基础设施中添加运行时配置(如尚未存在),确保 FastAPI 的 API URL 在网站中可用,并由 use<ApiName>.tsx 钩子自动配置。

    代码生成

    构建时,将从 FastAPI 的 OpenAPI 规范生成类型安全客户端,这会为 React 应用添加三个新文件:

    • 文件夹src
      • 文件夹generated
        • 文件夹<ApiName>
          • types.gen.ts 从 FastAPI 定义的 pydantic 模型生成的类型
          • client.gen.ts 调用 API 的类型安全客户端
          • options-proxy.gen.ts 提供创建 TanStack Query 钩子选项的方法

    使用生成代码

    推荐通过 TanStack Query 钩子使用生成的类型安全客户端调用 FastAPI,也可直接使用普通客户端。

    使用 API 钩子

    生成器提供 use<ApiName> 钩子用于通过 TanStack Query 调用 API。

    查询

    使用 queryOptions 方法获取调用 API 所需的选项:

    import { useQuery } from '@tanstack/react-query';
    import { useState, useEffect } from 'react';
    import { useMyApi } from './hooks/useMyApi';
    function MyComponent() {
    const api = useMyApi();
    const item = useQuery(api.getItem.queryOptions({ itemId: 'some-id' }));
    if (item.isLoading) return <div>Loading...</div>;
    if (item.isError) return <div>Error: {item.error.message}</div>;
    return <div>Item: {item.data.name}</div>;
    }
    点击查看直接使用普通客户端的示例。

    变更

    生成的钩子支持使用 TanStack Query 的 useMutation 处理变更操作,提供加载状态、错误处理和乐观更新。

    import { useMutation } from '@tanstack/react-query';
    import { useMyApi } from './hooks/useMyApi';
    function CreateItemForm() {
    const api = useMyApi();
    // 使用生成的变更选项创建变更
    const createItem = useMutation(api.createItem.mutationOptions());
    const handleSubmit = (e) => {
    e.preventDefault();
    createItem.mutate({ name: 'New Item', description: 'A new item' });
    };
    return (
    <form onSubmit={handleSubmit}>
    {/* 表单字段 */}
    <button
    type="submit"
    disabled={createItem.isPending}
    >
    {createItem.isPending ? '创建中...' : '创建项目'}
    </button>
    {createItem.isSuccess && (
    <div className="success">
    创建项目 ID: {createItem.data.id}
    </div>
    )}
    {createItem.isError && (
    <div className="error">
    错误: {createItem.error.message}
    </div>
    )}
    </form>
    );
    }

    可为不同变更状态添加回调:

    const createItem = useMutation({
    ...api.createItem.mutationOptions(),
    onSuccess: (data) => {
    // 变更成功时执行
    console.log('项目已创建:', data);
    // 可导航至新项目
    navigate(`/items/${data.id}`);
    },
    onError: (error) => {
    // 变更失败时执行
    console.error('创建项目失败:', error);
    },
    onSettled: () => {
    // 变更完成时执行(成功或失败)
    // 适合刷新可能受影响的查询
    queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() });
    }
    });
    点击查看直接使用客户端的示例。

    无限查询分页

    对于接收 cursor 参数的端点,生成的钩子支持使用 TanStack Query 的 useInfiniteQuery 实现无限滚动或”加载更多”功能。

    import { useInfiniteQuery } from '@tanstack/react-query';
    import { useMyApi } from './hooks/useMyApi';
    function ItemList() {
    const api = useMyApi();
    const items = useInfiniteQuery({
    ...api.listItems.infiniteQueryOptions({
    limit: 10, // 每页项目数
    }, {
    // 确保定义 getNextPageParam 函数返回下一页的 cursor 参数
    getNextPageParam: (lastPage) =>
    lastPage.nextCursor || undefined
    }),
    });
    if (items.isLoading) {
    return <LoadingSpinner />;
    }
    if (items.isError) {
    return <ErrorMessage message={items.error.message} />;
    }
    return (
    <div>
    {/* 平铺页面数组渲染所有项目 */}
    <ul>
    {items.data.pages.flatMap(page =>
    page.items.map(item => (
    <li key={item.id}>{item.name}</li>
    ))
    )}
    </ul>
    <button
    onClick={() => items.fetchNextPage()}
    disabled={!items.hasNextPage || items.isFetchingNextPage}
    >
    {items.isFetchingNextPage
    ? '正在加载更多...'
    : items.hasNextPage
    ? '加载更多'
    : '没有更多项目'}
    </button>
    </div>
    );
    }

    生成的钩子会自动处理基于 cursor 的分页。nextCursor 值从响应中提取用于获取下一页。

    点击查看直接使用客户端的示例。

    错误处理

    集成包含内置类型化错误处理。生成 <operation-name>Error 类型封装 OpenAPI 规范中可能的错误响应。通过检查 status 可定位具体错误类型。

    import { useMutation } from '@tanstack/react-query';
    function MyComponent() {
    const api = useMyApi();
    const createItem = useMutation(api.createItem.mutationOptions());
    const handleClick = () => {
    createItem.mutate({ name: 'New Item' });
    };
    if (createItem.error) {
    switch (createItem.error.status) {
    case 400:
    // error.error 类型为 CreateItem400Response
    return (
    <div>
    <h2>无效输入:</h2>
    <p>{createItem.error.error.message}</p>
    <ul>
    {createItem.error.error.validationErrors.map((err) => (
    <li key={err.field}>{err.message}</li>
    ))}
    </ul>
    </div>
    );
    case 403:
    // error.error 类型为 CreateItem403Response
    return (
    <div>
    <h2>未授权:</h2>
    <p>{createItem.error.error.reason}</p>
    </div>
    );
    case 500:
    case 502:
    // error.error 类型为 CreateItem5XXResponse
    return (
    <div>
    <h2>服务器错误:</h2>
    <p>{createItem.error.error.message}</p>
    <p>追踪 ID: {createItem.error.error.traceId}</p>
    </div>
    );
    }
    }
    return <button onClick={handleClick}>创建项目</button>;
    }
    点击查看直接使用客户端的示例。

    使用流

    如果已 配置 FastAPI 流式响应useQuery 钩子会在新数据块到达时自动更新数据。

    例如:

    function MyStreamingComponent() {
    const api = useMyApi();
    const stream = useQuery(api.myStream.queryOptions());
    return (
    <ul>
    {(stream.data ?? []).map((chunk) => (
    <li>
    {chunk.timestamp.toISOString()}: {chunk.message}
    </li>
    ))}
    </ul>
    );
    }

    可使用 isLoadingfetchStatus 属性确定流的当前状态。流的生命周期如下:

    1. 发送启动流的 HTTP 请求

      • isLoadingtrue
      • fetchStatus'fetching'
      • dataundefined
    2. 接收到流的首个数据块

      • isLoading 变为 false
      • fetchStatus 保持 'fetching'
      • data 变为包含首个数据块的数组
    3. 接收到后续数据块

      • isLoading 保持 false
      • fetchStatus 保持 'fetching'
      • data 随每个新数据块立即更新
    4. 流完成

      • isLoading 保持 false
      • fetchStatus 变为 'idle'
      • data 包含所有接收的数据块
    点击查看直接使用客户端的示例。

    自定义生成代码

    查询与变更

    默认情况下,FastAPI 中使用 PUTPOSTPATCHDELETE HTTP 方法的路由被视为变更操作,其他视为查询。可通过 x-queryx-mutation 调整此行为。

    x-query

    @app.post(
    "/items",
    openapi_extra={
    "x-query": True
    }
    )
    def list_items():
    # ...

    生成的钩子将提供 queryOptions,即使使用 POST 方法:

    const items = useQuery(api.listItems.queryOptions());

    x-mutation

    @app.get(
    "/start-processing",
    openapi_extra={
    "x-mutation": True
    }
    )
    def start_processing():
    # ...

    生成的钩子将提供 mutationOptions,即使使用 GET 方法:

    const startProcessing = useMutation(api.startProcessing.mutationOptions());

    自定义分页游标

    默认生成的钩子假设分页参数名为 cursor。可通过 x-cursor 扩展自定义:

    @app.get(
    "/items",
    openapi_extra={
    # 指定不同的游标参数名
    "x-cursor": "page_token"
    }
    )
    def list_items(page_token: str = None, limit: int = 10):
    # ...
    return {
    "items": items,
    "page_token": next_page_token # 响应需包含同名游标
    }

    若不想为某个操作生成 infiniteQueryOptions,可将 x-cursor 设为 False

    @app.get(
    "/items",
    openapi_extra={
    # 禁用此端点的游标分页
    "x-cursor": False
    }
    )
    def list_items(page: int = 1, limit: int = 10):
    # ...
    return {
    "items": items,
    "total": total_count,
    "page": page,
    "pages": total_pages
    }

    操作分组

    生成的钩子和客户端方法根据 FastAPI 端点的 OpenAPI 标签自动组织,便于查找相关操作。

    例如:

    items.py
    @app.get(
    "/items",
    tags=["items"],
    )
    def list():
    # ...
    @app.post(
    "/items",
    tags=["items"],
    )
    def create(item: Item):
    # ...
    users.py
    @app.get(
    "/users",
    tags=["users"],
    )
    def list():
    # ...

    生成的钩子将按标签分组:

    import { useQuery, useMutation } from '@tanstack/react-query';
    import { useMyApi } from './hooks/useMyApi';
    function ItemsAndUsers() {
    const api = useMyApi();
    // items 操作分组在 api.items 下
    const items = useQuery(api.items.list.queryOptions());
    const createItem = useMutation(api.items.create.mutationOptions());
    // users 操作分组在 api.users 下
    const users = useQuery(api.users.list.queryOptions());
    // 使用示例
    const handleCreateItem = () => {
    createItem.mutate({ name: 'New Item' });
    };
    return (
    <div>
    <h2>项目</h2>
    <ul>
    {items.data?.map(item => (
    <li key={item.id}>{item.name}</li>
    ))}
    </ul>
    <button onClick={handleCreateItem}>添加项目</button>
    <h2>用户</h2>
    <ul>
    {users.data?.map(user => (
    <li key={user.id}>{user.name}</li>
    ))}
    </ul>
    </div>
    );
    }

    分组机制便于组织 API 调用,并在 IDE 中提供更好的代码补全。

    点击查看直接使用客户端的示例。

    错误

    可通过定义自定义异常类、异常处理程序并为不同状态码指定响应模型来自定义错误响应。生成的客户端会自动处理这些自定义错误类型。

    定义自定义错误模型

    首先使用 Pydantic 定义错误模型:

    models.py
    from pydantic import BaseModel
    class ErrorDetails(BaseModel):
    message: str
    class ValidationError(BaseModel):
    message: str
    field_errors: list[str]

    创建自定义异常

    为不同错误场景创建异常类:

    exceptions.py
    class NotFoundException(Exception):
    def __init__(self, message: str):
    self.message = message
    class ValidationException(Exception):
    def __init__(self, details: ValidationError):
    self.details = details

    添加异常处理程序

    注册异常处理程序将异常转换为 HTTP 响应:

    main.py
    from fastapi import Request
    from fastapi.responses import JSONResponse
    @app.exception_handler(NotFoundException)
    async def not_found_handler(request: Request, exc: NotFoundException):
    return JSONResponse(
    status_code=404,
    content=exc.message,
    )
    @app.exception_handler(ValidationException)
    async def validation_error_handler(request: Request, exc: ValidationException):
    return JSONResponse(
    status_code=400,
    content=exc.details.model_dump(),
    )

    指定响应模型

    在端点定义中为不同状态码指定响应模型:

    main.py
    @app.get(
    "/items/{item_id}",
    responses={
    404: {"model": str}
    500: {"model": ErrorDetails}
    }
    )
    def get_item(item_id: str) -> Item:
    item = find_item(item_id)
    if not item:
    raise NotFoundException(message=f"Item with ID {item_id} not found")
    return item
    @app.post(
    "/items",
    responses={
    400: {"model": ValidationError},
    403: {"model": str}
    }
    )
    def create_item(item: Item) -> Item:
    if not is_valid(item):
    raise ValidationException(
    ValidationError(
    message="Invalid item data",
    field_errors=["name is required"]
    )
    )
    return save_item(item)

    在 React 中使用自定义错误类型

    生成的客户端会自动处理这些自定义错误类型,允许进行类型检查和错误响应处理:

    import { useMutation, useQuery } from '@tanstack/react-query';
    function ItemComponent() {
    const api = useMyApi();
    // 带类型错误处理的查询
    const getItem = useQuery({
    ...api.getItem.queryOptions({ itemId: '123' }),
    onError: (error) => {
    // 根据 FastAPI 的响应定义错误类型
    switch (error.status) {
    case 404:
    // error.error 是 responses 中指定的字符串
    console.error('未找到:', error.error);
    break;
    case 500:
    // error.error 类型为 ErrorDetails
    console.error('服务器错误:', error.error.message);
    break;
    }
    }
    });
    // 带类型错误处理的变更
    const createItem = useMutation({
    ...api.createItem.mutationOptions(),
    onError: (error) => {
    switch (error.status) {
    case 400:
    // error.error 类型为 ValidationError
    console.error('验证错误:', error.error.message);
    console.error('字段错误:', error.error.field_errors);
    break;
    case 403:
    // error.error 是 responses 中指定的字符串
    console.error('禁止访问:', error.error);
    break;
    }
    }
    });
    // 组件渲染与错误处理
    if (getItem.isError) {
    if (getItem.error.status === 404) {
    return <NotFoundMessage message={getItem.error.error} />;
    } else {
    return <ErrorMessage message={getItem.error.error.message} />;
    }
    }
    return (
    <div>
    {/* 组件内容 */}
    </div>
    );
    }
    点击查看直接使用客户端的示例。

    最佳实践

    处理加载状态

    始终处理加载和错误状态以提升用户体验:

    import { useQuery } from '@tanstack/react-query';
    function ItemList() {
    const api = useMyApi();
    const items = useQuery(api.listItems.queryOptions());
    if (items.isLoading) {
    return <LoadingSpinner />;
    }
    if (items.isError) {
    const err = items.error;
    switch (err.status) {
    case 403:
    // err.error 类型为 ListItems403Response
    return <ErrorMessage message={err.error.reason} />;
    case 500:
    case 502:
    // err.error 类型为 ListItems5XXResponse
    return (
    <ErrorMessage
    message={err.error.message}
    details={`追踪 ID: ${err.error.traceId}`}
    />
    );
    default:
    return <ErrorMessage message="发生未知错误" />;
    }
    }
    return (
    <ul>
    {items.data.map((item) => (
    <li key={item.id}>{item.name}</li>
    ))}
    </ul>
    );
    }
    点击查看直接使用客户端的示例。

    乐观更新

    实现乐观更新以提升用户体验:

    import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
    function ItemList() {
    const api = useMyApi();
    const queryClient = useQueryClient();
    // 获取项目的查询
    const itemsQuery = useQuery(api.listItems.queryOptions());
    // 带乐观更新的删除变更
    const deleteMutation = useMutation({
    ...api.deleteItem.mutationOptions(),
    onMutate: async (itemId) => {
    // 取消正在进行的重新获取
    await queryClient.cancelQueries({ queryKey: api.listItems.queryKey() });
    // 快照旧值
    const previousItems = queryClient.getQueryData(api.listItems.queryKey());
    // 乐观更新新值
    queryClient.setQueryData(
    api.listItems.queryKey(),
    (old) => old.filter((item) => item.id !== itemId)
    );
    // 返回包含快照的上下文对象
    return { previousItems };
    },
    onError: (err, itemId, context) => {
    // 变更失败时使用 onMutate 返回的上下文回滚
    queryClient.setQueryData(api.listItems.queryKey(), context.previousItems);
    console.error('删除项目失败:', err);
    },
    onSettled: () => {
    // 无论成功失败都重新获取数据以确保与服务器同步
    queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() });
    },
    });
    if (itemsQuery.isLoading) {
    return <LoadingSpinner />;
    }
    if (itemsQuery.isError) {
    return <ErrorMessage message="加载项目失败" />;
    }
    return (
    <ul>
    {itemsQuery.data.map((item) => (
    <li key={item.id}>
    {item.name}
    <button
    onClick={() => deleteMutation.mutate(item.id)}
    disabled={deleteMutation.isPending}
    >
    {deleteMutation.isPending ? '删除中...' : '删除'}
    </button>
    </li>
    ))}
    </ul>
    );
    }
    点击查看直接使用客户端的示例。

    类型安全

    集成提供完整的端到端类型安全。IDE 将为所有 API 调用提供自动补全和类型检查:

    import { useMutation } from '@tanstack/react-query';
    function ItemForm() {
    const api = useMyApi();
    // 创建项目的类型安全变更
    const createItem = useMutation({
    ...api.createItem.mutationOptions(),
    // ✅ 若 onSuccess 回调未处理正确的响应类型会报类型错误
    onSuccess: (data) => {
    // data 根据 API 响应模式完全类型化
    console.log(`项目创建 ID: ${data.id}`);
    },
    });
    const handleSubmit = (data: CreateItemInput) => {
    // ✅ 若输入不符合模式会报类型错误
    createItem.mutate(data);
    };
    // 错误 UI 可使用类型缩窄处理不同错误类型
    if (createItem.error) {
    const error = createItem.error;
    switch (error.status) {
    case 400:
    // error.error 类型为 CreateItem400Response
    return (
    <FormError
    message="无效输入"
    errors={error.error.validationErrors}
    />
    );
    case 403:
    // error.error 类型为 CreateItem403Response
    return <AuthError reason={error.error.reason} />;
    default:
    // error.error 类型为 CreateItem5XXResponse(500、502 等)
    return <ServerError message={error.error.message} />;
    }
    }
    return (
    <form onSubmit={(e) => {
    e.preventDefault();
    handleSubmit({ name: 'New Item' });
    }}>
    {/* 表单字段 */}
    <button
    type="submit"
    disabled={createItem.isPending}
    >
    {createItem.isPending ? '创建中...' : '创建项目'}
    </button>
    </form>
    );
    }
    点击查看直接使用客户端的示例。

    类型从 FastAPI 的 OpenAPI 模式自动生成,确保 API 变更在构建后能反映到前端代码中。