Skip to content

React から Smithy API へ

connection ジェネレータは、React ウェブサイトと Smithy TypeScript API バックエンドを迅速に統合する方法を提供します。タイプセーフな方法で Smithy API に接続するために必要なすべての設定(クライアントと TanStack Query フックの生成、AWS IAM および Cognito 認証のサポート、適切なエラーハンドリングなど)を行います。

このジェネレータを使用する前に、React アプリケーションが以下を満たしていることを確認してください:

  1. アプリケーションをレンダリングする main.tsx ファイルが存在すること
  2. 動作する Smithy TypeScript API バックエンドが存在すること(ts#smithy-api ジェネレータを使用して生成)
  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 - connection
  5. 必須パラメータを入力
    • クリック Generate
    パラメータ デフォルト 説明
    sourceProject 必須 string - ソース プロジェクト
    targetProject 必須 string - 接続先のターゲット プロジェクト
    sourceComponent string - 接続元のソース コンポーネント (コンポーネント名、ソース プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをソースとして明示的に選択するには '.' を使用します。
    targetComponent string - 接続先のターゲット コンポーネント (コンポーネント名、ターゲット プロジェクト ルートからの相対パス、またはジェネレーター ID)。プロジェクトをターゲットとして明示的に選択するには '.' を使用します。

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

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

    ジェネレータは Smithy モデルにもファイルを追加します:

    • Directorymodel
      • Directorysrc
        • extensions.smithy 生成クライアントをカスタマイズするためのトレート定義

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

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

    • Directorysrc
      • Directorygenerated
        • Directory<ApiName>
          • types.gen.ts Smithyモデル構造から生成された型
          • client.gen.ts API呼び出し用タイプセーフクライアント
          • options-proxy.gen.ts TanStack Query を使用してAPIと対話するためのTanStack Query フックオプション生成メソッド

    生成されたタイプセーフクライアントはReactアプリからSmithy APIを呼び出すために使用できます。TanStack Queryフック経由での使用が推奨されますが、バニラクライアントも直接使用可能です。

    ジェネレータが提供するuse<ApiName>フックを使用して、TanStack QueryでAPIを呼び出せます。

    queryOptionsメソッドで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>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() });
    }
    });
    クリックでクライアント直接使用例を表示

    無限クエリによるページネーション

    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, // ページあたりのアイテム数
    }, {
    // 次ページの '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値はレスポンスから抽出され次ページの取得に使用されます。

    クリックでクライアント直接使用例を表示

    統合には型付きエラーレスポンスを持つ組み込みエラーハンドリングが含まれます。<operation-name>Error型が生成され、Smithyモデルで定義された可能性のあるエラーレスポンスをカプセル化します。各エラーには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:
    // error.error は CreateItem400Response として型付け
    return (
    <div>
    <h2>無効な入力:</h2>
    <p>{createItem.error.error.message}</p>
    </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>
    </div>
    );
    }
    }
    return <button onClick={handleClick}>アイテム作成</button>;
    }
    クリックでバニラクライアント直接使用例を表示

    ターゲットSmithy modelプロジェクトのextensions.smithyに一連のSmithyトレートが追加され、生成クライアントのカスタマイズに使用できます。

    デフォルトで、Smithy APIでHTTPメソッドPUTPOSTPATCHDELETEを使用する操作はミューテーションとして扱われ、他はすべてクエリとして扱われます。

    extensions.smithyのモデルプロジェクトに追加される@query@mutation Smithyトレートを使用してこの動作を変更できます。

    Smithy操作に@queryトレートを適用して、クエリとして扱うよう強制します:

    @http(method: "POST", uri: "/items")
    @query
    operation ListItems {
    input: ListItemsInput
    output: ListItemsOutput
    }

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

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

    Smithy操作に@mutationトレートを適用して、ミューテーションとして扱うよう強制します:

    @http(method: "GET", uri: "/start-processing")
    @mutation
    operation StartProcessing {
    input: StartProcessingInput
    output: StartProcessingOutput
    }

    生成フックはGET HTTPメソッドを使用していてもmutationOptionsを提供します:

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

    カスタムページネーションカーソル

    Section titled “カスタムページネーションカーソル”

    デフォルトで、生成フックはcursorという名前のパラメータを使用したカーソルベースのページネーションを想定します。extensions.smithyのモデルプロジェクトに追加される@cursorトレートを使用してこの動作をカスタマイズできます。

    @cursorトレートにinputTokenを指定して、ページネーショントークンに使用する入力パラメータ名を変更します:

    @http(method: "GET", uri: "/items")
    @cursor(inputToken: "nextToken")
    operation ListItems {
    input := {
    nextToken: String
    limit: Integer
    }
    output := {
    items: ItemList
    nextToken: String
    }
    }

    cursorという名前の入力パラメータを持つ操作に対してinfiniteQueryOptionsを生成したくない場合、カーソルベースのページネーションを無効化できます:

    @cursor(enabled: false)
    operation ListItems {
    input := {
    // 'cursor'という名前の入力パラメータは、デフォルトでこの操作をページネーション操作として扱います
    cursor: String
    }
    output := {
    ...
    }
    }

    生成されたフックとクライアントメソッドは、Smithy操作の@tagsトレートに基づいて自動的に整理されます。同じタグを持つ操作はグループ化され、API呼び出しを整理しやすくし、IDEでのコード補完が向上します。

    例えば、このSmithyモデルの場合:

    service MyService {
    operations: [ListItems, CreateItem, ListUsers, CreateUser]
    }
    @tags(["items"])
    operation ListItems {
    input: ListItemsInput
    output: ListItemsOutput
    }
    @tags(["items"])
    operation CreateItem {
    input: CreateItemInput
    output: CreateItemOutput
    }
    @tags(["users"])
    operation ListUsers {
    input: ListUsersInput
    output: ListUsersOutput
    }
    @tags(["users"])
    operation CreateUser {
    input: CreateUserInput
    output: CreateUserOutput
    }

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

    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.listItems.queryOptions());
    const createItem = useMutation(api.items.createItem.mutationOptions());
    // Users操作は api.users 配下にグループ化
    const users = useQuery(api.users.listUsers.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でのコード補完が向上します。

    クリックでクライアント直接使用例を表示

    Smithyモデルでカスタムエラー構造を定義することで、Smithy APIのエラーレスポンスをカスタマイズできます。生成クライアントはこれらのカスタムエラータイプを自動的に処理します。

    Smithyモデルでエラー構造を定義します:

    @error("client")
    @httpError(400)
    structure InvalidRequestError {
    @required
    message: String
    fieldErrors: FieldErrorList
    }
    @error("client")
    @httpError(403)
    structure UnauthorizedError {
    @required
    reason: String
    }
    @error("server")
    @httpError(500)
    structure InternalServerError {
    @required
    message: String
    traceId: String
    }
    list FieldErrorList {
    member: FieldError
    }
    structure FieldError {
    @required
    field: String
    @required
    message: String
    }

    操作が返す可能性のあるエラーを指定します:

    operation CreateItem {
    input: CreateItemInput
    output: CreateItemOutput
    errors: [
    InvalidRequestError
    UnauthorizedError
    InternalServerError
    ]
    }
    operation GetItem {
    input: GetItemInput
    output: GetItemOutput
    errors: [
    ItemNotFoundError
    InternalServerError
    ]
    }
    @error("client")
    @httpError(404)
    structure ItemNotFoundError {
    @required
    message: String
    }

    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) => {
    // エラーはSmithyモデルのエラーに基づいて型付け
    switch (error.status) {
    case 404:
    // error.error は ItemNotFoundError として型付け
    console.error('見つかりません:', error.error.message);
    break;
    case 500:
    // error.error は InternalServerError として型付け
    console.error('サーバーエラー:', error.error.message);
    console.error('トレースID:', error.error.traceId);
    break;
    }
    }
    });
    // 型付きエラーハンドリングを伴うミューテーション
    const createItem = useMutation({
    ...api.createItem.mutationOptions(),
    onError: (error) => {
    switch (error.status) {
    case 400:
    // error.error は InvalidRequestError として型付け
    console.error('バリデーションエラー:', error.error.message);
    console.error('フィールドエラー:', error.error.fieldErrors);
    break;
    case 403:
    // error.error は UnauthorizedError として型付け
    console.error('権限なし:', error.error.reason);
    break;
    }
    }
    });
    // エラーハンドリングを伴うコンポーネントレンダリング
    if (getItem.isError) {
    if (getItem.error.status === 404) {
    return <NotFoundMessage message={getItem.error.error.message} />;
    } else if (getItem.error.status === 500) {
    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}
    />
    );
    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 は InvalidRequestError として型付け
    return (
    <FormError
    message="無効な入力"
    errors={error.error.fieldErrors}
    />
    );
    case 403:
    // error.error は UnauthorizedError として型付け
    return <AuthError reason={error.error.reason} />;
    default:
    // error.error は 500 などの場合 InternalServerError として型付け
    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>
    );
    }
    クリックでバニラクライアント直接使用例を表示

    型はSmithy APIのOpenAPIスキーマから自動生成されるため、APIへの変更はビルド後にフロントエンドコードに反映されます。

    If your Smithy API 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.