Skip to content

React から Smithy API へ

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 - api-connection
  5. 必須パラメータを入力
    • クリック Generate
    パラメータ デフォルト 説明
    sourceProject 必須 string - The source project which will call the API
    targetProject 必須 string - The target project containing your API

    ジェネレータは 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 フックオプション生成メソッド

    生成されたタイプセーフクライアントは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,
    }, {
    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>
    );
    }

    生成フックはカーソルベースのページネーションを自動処理します。nextCursor値はレスポンスから抽出され次ページの取得に使用されます。

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

    統合には型付きエラーレスポンスが含まれます。<operation-name>Error型でSmithyモデルで定義された可能性のあるエラーをカプセル化します。

    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>
    </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>
    </div>
    );
    }
    }
    return <button onClick={handleClick}>アイテム作成</button>;
    }
    クリックでバニラクライアント直接使用例を表示

    Smithyモデルのextensions.smithyに追加されるトレートを使用して生成クライアントをカスタマイズできます。

    デフォルトでPUTPOSTPATCHDELETEメソッドはミューテーション、他はクエリとして扱われます。@query@mutationトレートでこの動作を変更できます。

    POSTメソッドでもクエリとして扱いたい場合:

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

    GETメソッドでもミューテーションとして扱いたい場合:

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

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

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

    デフォルトのcursorパラメータ名を変更する場合:

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

    ページネーションを無効化する場合:

    @cursor(enabled: false)
    operation ListItems {
    input := {
    cursor: String
    }
    output := {
    ...
    }
    }

    @tagsトレートを使用して操作をグループ化できます。同じタグの操作はグループ化され、IDEでのコード補完が向上します。

    @tags(["items"])
    operation ListItems { ... }
    @tags(["items"])
    operation CreateItem { ... }
    @tags(["users"])
    operation ListUsers { ... }

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

    const items = useQuery(api.items.listItems.queryOptions());
    const createItem = useMutation(api.items.createItem.mutationOptions());
    const users = useQuery(api.users.listUsers.queryOptions());
    クリックでバニラクライアント直接使用例を表示

    Smithyモデルでカスタムエラー構造を定義できます。生成クライアントはこれらのエラータイプを自動処理します。

    @error("client")
    @httpError(400)
    structure InvalidRequestError {
    @required
    message: String
    fieldErrors: FieldErrorList
    }
    operation CreateItem {
    ...
    errors: [
    InvalidRequestError
    UnauthorizedError
    ]
    }

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

    Section titled “Reactでのカスタムエラータイプの使用”
    switch (error.status) {
    case 400:
    console.error('バリデーションエラー:', error.error.fieldErrors);
    break;
    case 403:
    console.error('権限不足:', error.error.reason);
    break;
    }
    クリックでバニラクライアント直接使用例を表示

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

    if (items.isLoading) {
    return <LoadingSpinner />;
    }
    if (items.isError) {
    return <ErrorMessage message={items.error.message} />;
    }
    クリックでバニラクライアント直接使用例を表示

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

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

    統合は完全なエンドツーエンドのタイプセーフティを提供します。IDEはすべてのAPI呼び出しで完全なオートコンプリートと型チェックを提供します。

    const createItem = useMutation({
    ...api.createItem.mutationOptions(),
    onSuccess: (data) => {
    console.log(`作成されたアイテムID: ${data.id}`);
    },
    });
    const handleSubmit = (data: CreateItemInput) => {
    createItem.mutate(data); // 入力スキーマの型チェック
    };
    クリックでバニラクライアント直接使用例を表示

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