跳转到内容

React 连接到 FastAPI

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

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

  1. 渲染应用的 main.tsx 文件
  2. 可运行的 FastAPI 后端(使用 FastAPI 生成器生成)
  3. 若需连接使用 Cognito 或 IAM 认证的 API,需通过 ts#react-website-auth 生成器 添加 Cognito 认证
所需 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

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

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

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

    • 文件夹src
      • 文件夹components
        • <ApiName>Provider.tsx API 客户端 Provider 组件
        • QueryClientProvider.tsx TanStack React Query 客户端 Provider
      • 文件夹hooks
        • use<ApiName>.tsx 添加通过 TanStack Query 管理 API 调用的钩子
        • use<ApiName>Client.tsx 添加实例化原生 API 客户端的钩子
        • useSigV4.tsx 添加支持 IAM 认证的请求签名钩子(若选择 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,也可以直接使用原生客户端。

    生成器提供的 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();
    // 使用生成的变更选项创建 mutation
    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: (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>
    );
    }

    若 API 支持游标分页,生成的钩子会自动处理分页逻辑,从响应中提取 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: '新条目' });
    };
    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 方法视为变更操作,其他视为查询操作。可通过 x-queryx-mutation 扩展修改此行为。

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

    生成的钩子将为 POST 方法提供 queryOptions

    const items = useQuery(api.listItems.queryOptions());
    @app.get(
    "/start-processing",
    openapi_extra={
    "x-mutation": True
    }
    )
    def start_processing():
    # ...

    生成的钩子将为 GET 方法提供 mutationOptions

    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
    }

    若需禁用某端点的无限查询选项,可将 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 标签自动组织,便于管理相关 API 调用。

    示例:

    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: '新条目' });
    };
    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 的代码补全体验。

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

    通过定义自定义异常类、异常处理程序和响应模型,可定制 FastAPI 的错误响应。生成的客户端会自动处理这些自定义错误类型。

    使用 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"未找到 ID 为 {item_id} 的条目")
    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="无效条目数据",
    field_errors=["名称不能为空"]
    )
    )
    return save_item(item)

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

    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 类型为字符串
    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 类型为字符串
    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) => {
    // 操作失败时回滚数据
    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
    return <ServerError message={error.error.message} />;
    }
    }
    return (
    <form onSubmit={(e) => {
    e.preventDefault();
    handleSubmit({ name: '新条目' });
    }}>
    {/* 表单字段 */}
    <button
    type="submit"
    disabled={createItem.isPending}
    >
    {createItem.isPending ? '创建中...' : '创建条目'}
    </button>
    </form>
    );
    }
    点击查看直接使用客户端的示例。

    所有类型均从 FastAPI 的 OpenAPI 规范自动生成,确保 API 变更后前端代码在构建后自动同步更新。