跳转到内容

React 连接到 FastAPI

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

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

  1. 渲染应用的 main.tsx 文件
  2. 可运行的 FastAPI 后端(使用 FastAPI 生成器生成)
  3. 如果连接的 API 使用 Cognito 或 IAM 身份验证,需通过 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 - connection
  5. 填写必需参数
    • 点击 Generate
    参数 类型 默认值 描述
    sourceProject 必需 string - 源项目
    targetProject 必需 string - 要连接到的目标项目
    sourceComponent string - 要从其连接的源组件(组件名称、相对于源项目根目录的路径或生成器 ID)。使用 '.' 显式选择项目作为源。
    targetComponent string - 要连接到的目标组件(组件名称、相对于目标项目根目录的路径或生成器 ID)。使用 '.' 显式选择项目作为目标。

    生成器将修改 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 与 API 交互

    生成的类型安全客户端可用于从 React 应用调用 FastAPI。建议通过 TanStack Query 钩子使用客户端,但也可直接使用普通客户端。

    文件监听依赖

    watch-generate:<ApiName>-client 依赖 nx watch 命令,需要 Nx Daemon 运行。因此如果禁用了守护进程,修改 FastAPI 时客户端不会自动重新生成。

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

    使用 queryOptions 方法获取调用 API 所需的选项,结合 TanStack Query 的 useQuery 钩子:

    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>加载中...</div>;
    if (item.isError) return <div>错误:{item.error.message}</div>;
    return <div>项目:{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: '新项目', description: '新项目描述' });
    };
    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>
    );
    }

    生成的钩子会自动处理基于游标的分页(如果 API 支持)。nextCursor 值从响应中提取并用于获取下一页。

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

    集成包含内置的错误处理,带有类型化的错误响应。生成 <operation-name>Error 类型封装 OpenAPI 规范中定义的可能错误响应。每个错误都有 statuserror 属性,通过检查 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 HTTP 方法的操作视为变更,其他视为查询。

    可通过 x-queryx-mutation 修改此行为。

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

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

    const items = useQuery(api.listItems.queryOptions());
    @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: '新项目' });
    };
    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) => {
    // 变更失败时使用 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: '新项目' });
    }}>
    {/* 表单字段 */}
    <button
    type="submit"
    disabled={createItem.isPending}
    >
    {createItem.isPending ? '创建中...' : '创建项目'}
    </button>
    </form>
    );
    }
    点击查看直接使用客户端的示例。

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

    If your FastAPI uses Custom authentication (Lambda Authorizer), you will need to edit the generated client provider to add the authorization headers your authorizer expects. Look for the fetch configuration in the generated <ApiName>Provider.tsx and add your token or API key to the request headers.