Skip to content

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プロジェクトの以下のファイルを変更します:

    • Directoryscripts
      • generate_open_api.py APIのOpenAPI仕様を生成するスクリプトを追加
    • project.json 上記の生成スクリプトを呼び出す新しいビルドターゲットを追加

    ジェネレータはReactアプリケーションの以下のファイルを変更します:

    • Directorysrc
      • Directorycomponents
        • <ApiName>Provider.tsx APIクライアントのプロバイダ
        • QueryClientProvider.tsx TanStack React Queryクライアントプロバイダ
      • Directoryhooks
        • use<ApiName>.tsx TanStack Queryで状態管理されたAPI呼び出し用フックを追加
        • use<ApiName>Client.tsx 直接APIを呼び出すバニラクライアント生成用フックを追加
        • useSigV4.tsx IAM認証選択時にHTTPリクエストのSigV4署名用フックを追加
    • project.json タイプセーフなクライアントを生成する新しいビルドターゲットを追加
    • .gitignore 生成されたクライアントファイルをデフォルトで無視

    また、ジェネレータはウェブサイトインフラにRuntime Configを追加します(まだ存在しない場合)。これにより、FastAPIのAPI URLがウェブサイトで利用可能になり、use<ApiName>.tsxフックによって自動的に設定されます。

    コード生成

    ビルド時に、FastAPIのOpenAPI仕様からタイプセーフなクライアントが生成されます。これによりReactアプリケーションに3つの新しいファイルが追加されます:

    • Directorysrc
      • Directorygenerated
        • Directory<ApiName>
          • types.gen.ts FastAPIで定義されたpydanticモデルから生成された型
          • client.gen.ts API呼び出し用タイプセーフクライアント
          • options-proxy.gen.ts TanStack Queryを使用したAPI操作のためのフックオプション生成メソッドを提供

    生成コードの使用方法

    生成されたタイプセーフクライアントを使用して、ReactアプリケーションからFastAPIを呼び出すことができます。TanStack Queryフック経由での使用が推奨されますが、直接クライアントを使用することも可能です。

    APIフックの使用

    ジェネレータはuse<ApiName>フックを提供し、TanStack QueryでAPIを呼び出すことができます。

    クエリ

    queryOptionsメソッドを使用して、TanStack QueryのuseQueryフックで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, // 1ページあたりのアイテム数
    }, {
    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値はレスポンスから抽出され、次のページの取得に使用されます。

    バニラクライアントを直接使用する例を見るにはここをクリック

    エラーハンドリング

    統合には型指定されたエラーレスポンスが含まれます。OpenAPI仕様で定義された可能なエラーレスポンスをカプセル化する<operation-name>Error型が生成されます。各エラーはstatuserrorプロパティを持ち、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:
    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:
    return (
    <div>
    <h2>権限なし:</h2>
    <p>{createItem.error.error.reason}</p>
    </div>
    );
    case 500:
    case 502:
    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リクエスト送信

      • isLoading: true
      • fetchStatus: 'fetching'
      • data: undefined
    2. 最初のチャンク受信

      • isLoading: false
      • fetchStatus: 'fetching'
      • data: 最初のチャンクを含む配列
    3. 後続のチャンク受信

      • isLoading: false
      • fetchStatus: 'fetching'
      • data: 各チャンク到着時に更新
    4. ストリーム完了

      • isLoading: false
      • fetchStatus: 'idle'
      • data: 全受信チャンクの配列
    バニラクライアントを直接使用する例を見るにはここをクリック

    生成コードのカスタマイズ

    クエリとミューテーション

    デフォルトでは、HTTPメソッドPUTPOSTPATCHDELETEを使用するFastAPIの操作はミューテーションとみなされ、それ以外はクエリとみなされます。x-queryx-mutationを使用してこの動作を変更できます。

    x-query

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

    生成フックはPOSTメソッド使用時でもqueryOptionsを提供します:

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

    x-mutation

    @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
    }

    infiniteQueryOptionsの生成を無効化するにはx-cursorFalseに設定:

    @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():
    # ...

    生成フックはタグでグループ化されます:

    function ItemsAndUsers() {
    const api = useMyApi();
    const items = useQuery(api.items.list.queryOptions());
    const createItem = useMutation(api.items.create.mutationOptions());
    const users = useQuery(api.users.list.queryOptions());
    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>
    );
    }
    バニラクライアントを直接使用する例を見るにはここをクリック

    エラー

    カスタム例外クラス、例外ハンドラ、レスポンスモデルを定義することでエラーレスポンスをカスタマイズできます。生成クライアントはこれらのカスタムエラー型を自動処理します。

    カスタムエラーモデルの定義

    Pydanticでエラーモデルを定義:

    models.py
    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
    @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:
    # ...
    @app.post(
    "/items",
    responses={
    400: {"model": ValidationError},
    403: {"model": str}
    }
    )
    def create_item(item: Item) -> Item:
    # ...

    Reactでのカスタムエラー型の使用

    生成クライアントはカスタムエラー型を処理し、型チェックを可能にします:

    function ItemComponent() {
    const api = useMyApi();
    const getItem = useQuery({
    ...api.getItem.queryOptions({ itemId: '123' }),
    onError: (error) => {
    switch (error.status) {
    case 404:
    console.error('見つかりません:', error.error);
    break;
    case 500:
    console.error('サーバーエラー:', error.error.message);
    break;
    }
    }
    });
    const createItem = useMutation({
    ...api.createItem.mutationOptions(),
    onError: (error) => {
    switch (error.status) {
    case 400:
    console.error('検証エラー:', error.error.message);
    break;
    case 403:
    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>;
    }
    バニラクライアントを直接使用する例を見るにはここをクリック

    ベストプラクティス

    ローディング状態の処理

    ユーザーエクスペリエンス向上のため、ローディングとエラー状態を常に処理:

    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:
    return <ErrorMessage message={err.error.reason} />;
    case 500:
    return <ErrorMessage message={err.error.message} />;
    default:
    return <ErrorMessage message="不明なエラー" />;
    }
    }
    return (
    <ul>
    {items.data.map((item) => (
    <li key={item.id}>{item.name}</li>
    ))}
    </ul>
    );
    }
    バニラクライアントを直接使用する例を見るにはここをクリック

    楽観的更新

    ユーザーエクスペリエンス向上のため楽観的更新を実装:

    function ItemList() {
    const queryClient = useQueryClient();
    const deleteMutation = useMutation({
    ...api.deleteItem.mutationOptions(),
    onMutate: async (itemId) => {
    await queryClient.cancelQueries(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);
    },
    onSettled: () => {
    queryClient.invalidateQueries(api.listItems.queryKey());
    },
    });
    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呼び出しに対して完全なオートコンプリートと型チェックを提供します:

    function ItemForm() {
    const api = useMyApi();
    const createItem = useMutation({
    ...api.createItem.mutationOptions(),
    onSuccess: (data) => {
    console.log(`作成されたアイテムID: ${data.id}`);
    },
    });
    const handleSubmit = (data: CreateItemInput) => {
    createItem.mutate(data);
    };
    if (createItem.error) {
    switch (createItem.error.status) {
    case 400:
    return <FormError errors={createItem.error.error.validationErrors} />;
    case 403:
    return <AuthError reason={createItem.error.error.reason} />;
    default:
    return <ServerError message={createItem.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への変更はビルド後にフロントエンドコードに反映されます。