ReactからFastAPIへ
connection ジェネレータは、React ウェブサイトと FastAPI バックエンドを迅速に統合する方法を提供します。型安全な方法で FastAPI バックエンドに接続するために必要なすべての設定(クライアントと TanStack Query フックの生成、AWS IAM および Cognito 認証のサポート、適切なエラーハンドリングなど)をセットアップします。
このジェネレータを使用する前に、React アプリケーションが以下を満たしていることを確認してください:
- アプリケーションをレンダリングする
main.tsxファイルが存在すること - FastAPI ジェネレータで生成された動作可能な FastAPI バックエンドが存在すること
- 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>,);ジェネレータの実行
Section titled “ジェネレータの実行”- インストール Nx Console VSCode Plugin まだインストールしていない場合
- VSCodeでNxコンソールを開く
- クリック
Generate (UI)"Common Nx Commands"セクションで - 検索
@aws/nx-plugin - connection - 必須パラメータを入力
- クリック
Generate
pnpm nx g @aws/nx-plugin:connectionyarn nx g @aws/nx-plugin:connectionnpx nx g @aws/nx-plugin:connectionbunx nx g @aws/nx-plugin:connection| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
| sourceProject 必須 | string | - | ソース プロジェクト |
| targetProject 必須 | string | - | 接続先のターゲット プロジェクト |
| sourceComponent | string | - | 接続元のソース コンポーネント (コンポーネント名、ソース プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをソースとして明示的に選択するには '.' を使用します。 |
| targetComponent | string | - | 接続先のターゲット コンポーネント (コンポーネント名、ターゲット プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをターゲットとして明示的に選択するには '.' を使用します。 |
ジェネレータの出力
Section titled “ジェネレータの出力”ジェネレータは 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 を呼び出せる通常の API クライアントをインスタンス化するフックを追加
- useSigV4.tsx IAM 認証を選択した場合、SigV4 で HTTP リクエストに署名するフックを追加
- project.json 型安全なクライアントを生成する新しいビルドターゲットを追加
- .gitignore 生成されたクライアントファイルをデフォルトで無視
また、ジェネレータは、まだ存在しない場合、ウェブサイトインフラにランタイム設定を追加します。これにより、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 とやり取りするための TanStack Query フックオプションを作成するメソッドを提供
生成コードの使用方法
Section titled “生成コードの使用方法”生成された型安全なクライアントを使用して、React アプリケーションから FastAPI を呼び出すことができます。TanStack Query フック経由での使用が推奨されますが、通常のクライアントを直接使用することも可能です。
ファイルウォッチャー依存
watch-generate:<ApiName>-client は nx watch コマンドに依存しており、Nx Daemon の実行が必要です。デーモンを無効にしている場合、FastAPI への変更時にクライアントが自動再生成されません。
API フックの使用方法
Section titled “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>;}API クライアントを直接使用する例
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApiClient';
function MyComponent() { const api = useMyApiClient(); const [item, setItem] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { const fetchItem = async () => { try { const data = await api.getItem({ itemId: 'some-id' }); setItem(data); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchItem(); }, [api]);
if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>;
return <div>Item: {item.name}</div>;}ミューテーション
Section titled “ミューテーション”生成されたフックは、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() }); }});クライアント直接使用したミューテーション例
import { useState } from 'react';import { useMyApiClient } from './hooks/useMyApiClient';
function CreateItemForm() { const api = useMyApiClient(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [createdItem, setCreatedItem] = useState(null);
const handleSubmit = async (e) => { e.preventDefault(); setIsLoading(true); setError(null);
try { const newItem = await api.createItem({ name: 'New Item', description: 'A new item' }); setCreatedItem(newItem); // 新しいアイテムに遷移 // navigate(`/items/${newItem.id}`); } catch (err) { setError(err); console.error('アイテム作成失敗:', err); } finally { setIsLoading(false); } };
return ( <form onSubmit={handleSubmit}> {/* フォームフィールド */} <button type="submit" disabled={isLoading} > {isLoading ? '作成中...' : 'アイテム作成'} </button>
{createdItem && ( <div className="success"> 作成されたアイテムID: {createdItem.id} </div> )}
{error && ( <div className="error"> エラー: {error.message} </div> )} </form> );}無限クエリによるページネーション
Section titled “無限クエリによるページネーション”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ページあたりのアイテム数 }, { // 次のページの 'cursor' として渡すパラメータを返す // getNextPageParam 関数を定義してください 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 値はレスポンスから抽出され、次のページの取得に使用されます。
クライアント直接使用したページネーション例
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApiClient';
function ItemList() { const api = useMyApiClient(); const [items, setItems] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [nextCursor, setNextCursor] = useState(null); const [isFetchingMore, setIsFetchingMore] = useState(false);
// 初期データの取得 useEffect(() => { const fetchItems = async () => { try { setIsLoading(true); const response = await api.listItems({ limit: 10 }); setItems(response.items); setNextCursor(response.nextCursor); } catch (err) { setError(err); } finally { setIsLoading(false); } };
fetchItems(); }, [api]);
// さらにアイテムを読み込む関数 const loadMore = async () => { if (!nextCursor) return;
try { setIsFetchingMore(true); const response = await api.listItems({ limit: 10, cursor: nextCursor });
setItems(prevItems => [...prevItems, ...response.items]); setNextCursor(response.nextCursor); } catch (err) { setError(err); } finally { setIsFetchingMore(false); } };
if (isLoading) { return <LoadingSpinner />; }
if (error) { return <ErrorMessage message={error.message} />; }
return ( <div> <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul>
<button onClick={loadMore} disabled={!nextCursor || isFetchingMore} > {isFetchingMore ? '読み込み中...' : nextCursor ? 'さらに読み込む' : 'これ以上アイテムはありません'} </button> </div> );}エラーハンドリング
Section titled “エラーハンドリング”統合には型指定されたエラーレスポンスを伴う組み込みのエラーハンドリングが含まれます。OpenAPI 仕様で定義された可能なエラーレスポンスをカプセル化する <operation-name>Error 型が生成されます。各エラーは status と error プロパティを持ち、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>;}クライアント直接使用したエラーハンドリング例
function MyComponent() { const api = useMyApiClient(); const [error, setError] = useState<CreateItemError | null>(null);
const handleClick = async () => { try { await api.createItem({ name: 'New Item' }); } catch (e) { const err = e as CreateItemError; setError(err); } };
if (error) { switch (error.status) { case 400: // error.error は CreateItem400Response として型指定 return ( <div> <h2>無効な入力:</h2> <p>{error.error.message}</p> <ul> {error.error.validationErrors.map((err) => ( <li key={err.field}>{err.message}</li> ))} </ul> </div> ); case 403: // error.error は CreateItem403Response として型指定 return ( <div> <h2>権限なし:</h2> <p>{error.error.reason}</p> </div> ); case 500: case 502: // error.error は CreateItem5XXResponse として型指定 return ( <div> <h2>サーバーエラー:</h2> <p>{error.error.message}</p> <p>トレースID: {error.error.traceId}</p> </div> ); } }
return <button onClick={handleClick}>アイテム作成</button>;}ストリームの消費
Section titled “ストリームの消費”ストリーム応答を設定した 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> );}必要に応じて、ストリームの現在の状態を判断するために isLoading と fetchStatus プロパティを使用できます。ストリームのライフサイクル:
-
ストリーム開始 HTTP リクエスト送信
isLoading:truefetchStatus:'fetching'data:undefined
-
最初のチャンク受信
isLoading:falsefetchStatus:'fetching'data: 最初のチャンクを含む配列
-
後続のチャンク受信
isLoading:falsefetchStatus:'fetching'data: 受信したチャンクごとに即座に更新
-
ストリーム完了
isLoading:falsefetchStatus:'idle'data: 全チャンクを含む配列
クライアント直接使用したストリーミング例
ストリーム応答を設定した FastAPI がある場合、生成クライアントは for await 構文を使用したストリームチャンクの非同期反復処理をサポートする型安全なメソッドを含みます。
例:
function MyStreamingComponent() { const api = useMyApiClient();
const [chunks, setChunks] = useState<Chunk[]>([]);
useEffect(() => { const streamChunks = async () => { for await (const chunk of api.myStream()) { setChunks((prev) => [...prev, chunk]); } }; streamChunks(); }, [api]);
return ( <ul> {chunks.map((chunk) => ( <li> {chunk.timestamp.toISOString()}: {chunk.message} </li> ))} </ul> );}生成コードのカスタマイズ
Section titled “生成コードのカスタマイズ”クエリとミューテーション
Section titled “クエリとミューテーション”デフォルトでは、HTTP メソッド PUT、POST、PATCH、DELETE を使用する FastAPI 操作はミューテーションと見なされ、その他はクエリと見なされます。
x-query と x-mutation でこの動作を変更できます。
x-query
Section titled “x-query”@app.post( "/items", openapi_extra={ "x-query": True })def list_items(): # ...POST メソッドを使用していても、生成フックは queryOptions を提供:
const items = useQuery(api.listItems.queryOptions());x-mutation
Section titled “x-mutation”@app.get( "/start-processing", openapi_extra={ "x-mutation": True })def start_processing(): # ...GET メソッドを使用していても、生成フックは mutationOptions を提供:
// 生成フックにはカスタムオプションが含まれますconst startProcessing = useMutation(api.startProcessing.mutationOptions());カスタムページネーションカーソル
Section titled “カスタムページネーションカーソル”デフォルトでは、生成フックは 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 }操作のグループ化
Section titled “操作のグループ化”生成フックとクライアントメソッドは、FastAPI エンドポイントの OpenAPI タグに基づいて自動的に整理されます。これにより API 呼び出しが整理され、関連する操作を簡単に見つけられるようになります。
例:
@app.get( "/items", tags=["items"],)def list(): # ...
@app.post( "/items", tags=["items"],)def create(item: Item): # ...@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>Items</h2> <ul> {items.data?.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> <button onClick={handleCreateItem}>アイテム追加</button>
<h2>Users</h2> <ul> {users.data?.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> );}このグループ化により、API 呼び出しの整理が容易になり、IDE でのコード補完が向上します。
クライアント直接使用したグループ化例
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApiClient';
function ItemsAndUsers() { const api = useMyApiClient(); const [items, setItems] = useState([]); const [users, setUsers] = useState([]); const [isLoading, setIsLoading] = useState(true);
// データ読み込み useEffect(() => { const fetchData = async () => { try { setIsLoading(true);
// Items 操作は api.items の下にグループ化 const itemsData = await api.items.list(); setItems(itemsData);
// Users 操作は api.users の下にグループ化 const usersData = await api.users.list(); setUsers(usersData); } catch (error) { console.error('データ取得エラー:', error); } finally { setIsLoading(false); } };
fetchData(); }, [api]);
const handleCreateItem = async () => { try { // グループ化されたメソッドを使用してアイテムを作成 const newItem = await api.items.create({ name: 'New Item' }); setItems(prevItems => [...prevItems, newItem]); } catch (error) { console.error('アイテム作成エラー:', error); } };
if (isLoading) { return <div>Loading...</div>; }
return ( <div> <h2>Items</h2> <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> <button onClick={handleCreateItem}>アイテム追加</button>
<h2>Users</h2> <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> );}カスタム例外クラス、例外ハンドラ、異なるエラーステータスコードのレスポンスモデルを定義することで、FastAPI のエラーレスポンスをカスタマイズできます。生成クライアントはこれらのカスタムエラータイプを自動処理します。
カスタムエラーモデルの定義
Section titled “カスタムエラーモデルの定義”まず、Pydantic でエラーモデルを定義:
from pydantic import BaseModel
class ErrorDetails(BaseModel): message: str
class ValidationError(BaseModel): message: str field_errors: list[str]カスタム例外の作成
Section titled “カスタム例外の作成”異なるエラーシナリオ用のカスタム例外クラスを作成:
class NotFoundException(Exception): def __init__(self, message: str): self.message = message
class ValidationException(Exception): def __init__(self, details: ValidationError): self.details = details例外ハンドラの追加
Section titled “例外ハンドラの追加”例外を HTTP レスポンスに変換する例外ハンドラを登録:
from fastapi import Requestfrom 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(), )レスポンスモデルの指定
Section titled “レスポンスモデルの指定”最後に、エンドポイント定義で異なるエラーステータスコードのレスポンスモデルを指定:
@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 でのカスタムエラータイプの使用
Section titled “React でのカスタムエラータイプの使用”生成クライアントはこれらのカスタムエラータイプを自動処理し、異なるエラーレスポンスの型チェックと処理が可能:
import { useMutation, useQuery } from '@tanstack/react-query';
function ItemComponent() { const api = useMyApi();
// 型指定されたエラーハンドリング付きクエリ const getItem = useQuery({ ...api.getItem.queryOptions({ itemId: '123' }), onError: (error) => { // エラーは FastAPI の responses に基づいて型指定 switch (error.status) { case 404: // error.error は responses で指定された文字列 console.error('Not found:', error.error); break; case 500: // error.error は ErrorDetails として型指定 console.error('Server error:', error.error.message); break; } } });
// 型指定されたエラーハンドリング付きミューテーション const createItem = useMutation({ ...api.createItem.mutationOptions(), onError: (error) => { switch (error.status) { case 400: // error.error は ValidationError として型指定 console.error('Validation error:', error.error.message); console.error('Field errors:', error.error.field_errors); break; case 403: // error.error は responses で指定された文字列 console.error('Forbidden:', 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 { useState, useEffect } from 'react';
function ItemComponent() { const api = useMyApiClient(); const [item, setItem] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true);
// エラーハンドリング付きアイテム取得 useEffect(() => { const fetchItem = async () => { try { setLoading(true); const data = await api.getItem({ itemId: '123' }); setItem(data); } catch (e) { // エラーは FastAPI の responses に基づいて型指定 const err = e as GetItemError; setError(err);
switch (err.status) { case 404: // err.error は responses で指定された文字列 console.error('Not found:', err.error); break; case 500: // err.error は ErrorDetails として型指定 console.error('Server error:', err.error.message); break; } } finally { setLoading(false); } };
fetchItem(); }, [api]);
// エラーハンドリング付きアイテム作成 const handleCreateItem = async (data) => { try { await api.createItem(data); } catch (e) { const err = e as CreateItemError;
switch (err.status) { case 400: // err.error は ValidationError として型指定 console.error('Validation error:', err.error.message); console.error('Field errors:', err.error.field_errors); break; case 403: // err.error は responses で指定された文字列 console.error('Forbidden:', err.error); break; } } };
// エラーハンドリング付きコンポーネントレンダリング if (loading) { return <LoadingSpinner />; }
if (error) { if (error.status === 404) { return <NotFoundMessage message={error.error} />; } else if (error.status === 500) { return <ErrorMessage message={error.error.message} />; } }
return ( <div> {/* コンポーネントコンテンツ */} </div> );}ベストプラクティス
Section titled “ベストプラクティス”ローディング状態の処理
Section titled “ローディング状態の処理”ユーザーエクスペリエンス向上のため、ローディングとエラー状態を常に処理:
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> );}クライアント直接使用したローディング処理例
function ItemList() { const api = useMyApiClient(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { const fetchItems = async () => { try { const data = await api.listItems(); setItems(data); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchItems(); }, [api]);
if (loading) { return <LoadingSpinner />; }
if (error) { const err = error as ListItemsError; 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.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> );}クライアント直接使用した楽観的更新例
function ItemList() { const api = useMyApiClient(); const [items, setItems] = useState([]);
const handleDelete = async (itemId) => { // アイテムを楽観的に削除 const previousItems = items; setItems(items.filter((item) => item.id !== itemId));
try { await api.deleteItem(itemId); } catch (error) { // エラー時に以前のアイテムを復元 setItems(previousItems); console.error('アイテム削除失敗:', error); } };
return ( <ul> {items.map((item) => ( <li key={item.id}> {item.name} <button onClick={() => handleDelete(item.id)}>削除</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 は 500、502 などの CreateItem5XXResponse として型指定 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> );}クライアント直接使用した型安全性例
function ItemForm() { const api = useMyApiClient(); const [error, setError] = useState<CreateItemError | null>(null);
const handleSubmit = async (data: CreateItemInput) => { try { // ✅ 入力がスキーマと一致しない場合、型エラー await api.createItem(data); } catch (e) { // ✅ エラー型にはすべての可能なエラーレスポンスが含まれる const err = e as CreateItemError; switch (err.status) { case 400: // err.error は CreateItem400Response として型指定 console.error('検証エラー:', err.error.validationErrors); break; case 403: // err.error は CreateItem403Response として型指定 console.error('権限なし:', err.error.reason); break; case 500: case 502: // err.error は CreateItem5XXResponse として型指定 console.error( 'サーバーエラー:', err.error.message, 'トレース:', err.error.traceId, ); break; } setError(err); } };
// エラー UI は型の絞り込みを使用して異なるエラータイプを処理可能 if (error) { switch (error.status) { case 400: return ( <FormError message="無効な入力" errors={error.error.validationErrors} /> ); case 403: return <AuthError reason={error.error.reason} />; default: return <ServerError message={error.error.message} />; } }
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}型は FastAPI の OpenAPI スキーマから自動生成されるため、API への変更はビルド後にフロントエンドコードに反映されます。
Custom Auth
Section titled “Custom Auth”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.