Salta ai contenuti

Reagire all'API Smithy

Il generatore api-connection fornisce un modo rapido per integrare la tua applicazione React con il backend API Smithy TypeScript. Configura tutto il necessario per connettersi all’API Smithy in modo type-safe, inclusi la generazione di client e hook TanStack Query, il supporto per autenticazione AWS IAM e Cognito, e una corretta gestione degli errori.

Prima di utilizzare questo generatore, assicurati che la tua applicazione React abbia:

  1. Un file main.tsx che renderizza l’applicazione
  2. Un backend API Smithy TypeScript funzionante (generato usando il generatore ts#smithy-api)
  3. Autenticazione Cognito aggiunta tramite il generatore ts#react-website-auth se si connette un’API che utilizza autenticazione Cognito o IAM
Esempio della struttura richiesta per 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. Installa il Nx Console VSCode Plugin se non l'hai già fatto
  2. Apri la console Nx in VSCode
  3. Clicca su Generate (UI) nella sezione "Common Nx Commands"
  4. Cerca @aws/nx-plugin - api-connection
  5. Compila i parametri richiesti
    • Clicca su Generate
    Parametro Tipo Predefinito Descrizione
    sourceProject Obbligatorio string - The source project which will call the API
    targetProject Obbligatorio string - The target project containing your API

    Il generatore apporterà modifiche ai seguenti file nella tua applicazione React:

    • Directorysrc
      • Directorycomponents
        • <ApiName>Provider.tsx Provider per il client API
        • QueryClientProvider.tsx Provider del client TanStack React Query
        • DirectoryRuntimeConfig/ Componente per la configurazione runtime in sviluppo locale
      • Directoryhooks
        • use<ApiName>.tsx Aggiunge un hook per chiamare l’API con stato gestito da TanStack Query
        • use<ApiName>Client.tsx Aggiunge un hook per istanziare il client API vanilla
        • useSigV4.tsx Aggiunge un hook per firmare richieste HTTP con SigV4 (se è selezionata l’autenticazione IAM)
    • project.json Aggiunge un nuovo target per la build che genera un client type-safe
    • .gitignore Ignora i file generati del client per default

    Il generatore aggiungerà anche un file al tuo modello Smithy:

    • Directorymodel
      • Directorysrc
        • extensions.smithy Definisce trait per personalizzare il client generato

    Aggiungerà inoltre la Runtime Config all’infrastruttura del sito web se non presente, assicurando che l’URL dell’API Smithy sia disponibile e configurato automaticamente dall’hook use<ApiName>.tsx.

    Durante la build, viene generato un client type-safe dalla specifica OpenAPI dell’API Smithy. Questo aggiunge tre nuovi file:

    • Directorysrc
      • Directorygenerated
        • Directory<ApiName>
          • types.gen.ts Tipi generati dalle strutture del modello Smithy
          • client.gen.ts Client type-safe per chiamare l’API
          • options-proxy.gen.ts Metodi per creare opzioni degli hook TanStack Query

    Il client type-safe può essere usato per chiamare l’API Smithy. Si consiglia di utilizzare gli hook TanStack Query, ma è possibile usare direttamente il client vanilla.

    L’hook use<ApiName> permette di chiamare l’API con TanStack Query.

    Usa queryOptions per ottenere le opzioni necessarie per 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>;
    }
    Clicca per un esempio con il client vanilla.

    Gli hook supportano mutazioni con useMutation per operazioni create/update/delete:

    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 ? 'Creating...' : 'Create Item'}
    </button>
    {createItem.isSuccess && (
    <div className="success">
    Item created with ID: {createItem.data.id}
    </div>
    )}
    {createItem.isError && (
    <div className="error">
    Error: {createItem.error.message}
    </div>
    )}
    </form>
    );
    }

    Callback per stati della mutazione:

    const createItem = useMutation({
    ...api.createItem.mutationOptions(),
    onSuccess: (data) => {
    console.log('Item created:', data);
    navigate(`/items/${data.id}`);
    },
    onError: (error) => {
    console.error('Failed to create item:', error);
    },
    onSettled: () => {
    queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() });
    }
    });
    Clicca per esempio con client vanilla.

    Per endpoint con parametro cursor, gli hook supportano paginazione infinita:

    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
    ? 'Loading more...'
    : items.hasNextPage
    ? 'Load More'
    : 'No more items'}
    </button>
    </div>
    );
    }
    Esempio con client vanilla.

    Gli errori sono tipizzati in base al modello Smithy:

    function MyComponent() {
    const api = useMyApi();
    const createItem = useMutation(api.createItem.mutationOptions());
    if (createItem.error) {
    switch (createItem.error.status) {
    case 400:
    return <div>{createItem.error.error.message}</div>;
    case 403:
    return <div>{createItem.error.error.reason}</div>;
    case 500:
    return <div>{createItem.error.error.message}</div>;
    }
    }
    return <button onClick={() => createItem.mutate()}>Create Item</button>;
    }
    Esempio con client vanilla.

    Usa i trait Smithy @query e @mutation per forzare il comportamento:

    @http(method: "POST", uri: "/items")
    @query
    operation ListItems {...}
    @http(method: "GET", uri: "/start-processing")
    @mutation
    operation StartProcessing {...}

    Modifica il parametro di paginazione con @cursor:

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

    Le operazioni con lo stesso @tags vengono raggruppate:

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

    Definisci strutture di errore nel modello Smithy per gestire risposte specifiche.

    Gestisci sempre stati di loading ed errori:

    if (items.isLoading) return <LoadingSpinner />;
    if (items.isError) return <ErrorMessage message={items.error.message} />;

    Implementa aggiornamenti ottimistici per una UX migliore:

    onMutate: async (itemId) => {
    await queryClient.cancelQueries();
    const previousItems = queryClient.getQueryData();
    queryClient.setQueryData(old => old.filter(item => item.id !== itemId));
    return { previousItems };
    }

    La type safety end-to-end garantisce autocompletamento e controllo degli errori:

    const createItem = useMutation({
    ...api.createItem.mutationOptions(),
    onSuccess: (data) => { // ✅ Tipo corretto
    console.log(`Created: ${data.id}`);
    },
    });
    Esempio con client vanilla.

    I tipi sono generati automaticamente dallo schema OpenAPI, garantendo sincronizzazione tra frontend e backend dopo ogni build.