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.
Prerequisiti
Sezione intitolata “Prerequisiti”Prima di utilizzare questo generatore, assicurati che la tua applicazione React abbia:
- Un file
main.tsx
che renderizza l’applicazione - Un backend API Smithy TypeScript funzionante (generato usando il generatore
ts#smithy-api
) - 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>,);
Utilizzo
Sezione intitolata “Utilizzo”Esegui il Generatore
Sezione intitolata “Esegui il Generatore”- Installa il Nx Console VSCode Plugin se non l'hai già fatto
- Apri la console Nx in VSCode
- Clicca su
Generate (UI)
nella sezione "Common Nx Commands" - Cerca
@aws/nx-plugin - api-connection
- Compila i parametri richiesti
- Clicca su
Generate
pnpm nx g @aws/nx-plugin:api-connection
yarn nx g @aws/nx-plugin:api-connection
npx nx g @aws/nx-plugin:api-connection
bunx nx g @aws/nx-plugin:api-connection
Puoi anche eseguire una prova per vedere quali file verrebbero modificati
pnpm nx g @aws/nx-plugin:api-connection --dry-run
yarn nx g @aws/nx-plugin:api-connection --dry-run
npx nx g @aws/nx-plugin:api-connection --dry-run
bunx nx g @aws/nx-plugin:api-connection --dry-run
Opzioni
Sezione intitolata “Opzioni”Parametro | Tipo | Predefinito | Descrizione |
---|---|---|---|
sourceProject Obbligatorio | string | - | The source project which will call the API |
targetProject Obbligatorio | string | - | The target project containing your API |
Output del Generatore
Sezione intitolata “Output del Generatore”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
.
Generazione del Codice
Sezione intitolata “Generazione del Codice”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
Utilizzo del Codice Generato
Sezione intitolata “Utilizzo del Codice Generato”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.
Utilizzo dell’Hook API
Sezione intitolata “Utilizzo dell’Hook API”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>;}
Utilizzo diretto del client 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>;}
Mutazioni
Sezione intitolata “Mutazioni”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() }); }});
Mutations con client diretto
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); } catch (err) { setError(err); } finally { setIsLoading(false); } };
return ( <form onSubmit={handleSubmit}> <button type="submit" disabled={isLoading} > {isLoading ? 'Creating...' : 'Create Item'} </button>
{createdItem && ( <div className="success"> Item created with ID: {createdItem.id} </div> )}
{error && ( <div className="error"> Error: {error.message} </div> )} </form> );}
Paginazione con Infinite Queries
Sezione intitolata “Paginazione con Infinite Queries”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> );}
Paginazione con client diretto
import { useState, useEffect } from 'react';import { useMyApiClient } from './hooks/useMyApiClient';
function ItemList() { const api = useMyApiClient(); const [items, setItems] = useState([]); const [nextCursor, setNextCursor] = useState(null); const [isFetchingMore, setIsFetchingMore] = useState(false);
useEffect(() => { const fetchItems = async () => { const response = await api.listItems({ limit: 10 }); setItems(response.items); setNextCursor(response.nextCursor); }; fetchItems(); }, [api]);
const loadMore = async () => { const response = await api.listItems({ limit: 10, cursor: nextCursor }); setItems(prev => [...prev, ...response.items]); setNextCursor(response.nextCursor); };
return ( <div> <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul> <button onClick={loadMore} disabled={!nextCursor || isFetchingMore}> {isFetchingMore ? 'Loading...' : nextCursor ? 'Load More' : 'No more items'} </button> </div> );}
Gestione Errori
Sezione intitolata “Gestione Errori”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>;}
Gestione errori con client diretto
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: return <div>{error.error.message}</div>; case 403: return <div>{error.error.reason}</div>; case 500: return <div>{error.error.message}</div>; } } return <button onClick={handleClick}>Create Item</button>;}
Personalizzazione del Codice Generato
Sezione intitolata “Personalizzazione del Codice Generato”Query e Mutazioni
Sezione intitolata “Query e Mutazioni”Usa i trait Smithy @query
e @mutation
per forzare il comportamento:
@http(method: "POST", uri: "/items")@queryoperation ListItems {...}
@http(method: "GET", uri: "/start-processing")@mutationoperation StartProcessing {...}
Cursore di Paginazione Personalizzato
Sezione intitolata “Cursore di Paginazione Personalizzato”Modifica il parametro di paginazione con @cursor
:
@cursor(inputToken: "nextToken")operation ListItems { input := { nextToken: String, limit: Integer } output := { items: ItemList, nextToken: String }}
Raggruppamento Operazioni
Sezione intitolata “Raggruppamento Operazioni”Le operazioni con lo stesso @tags
vengono raggruppate:
const items = useQuery(api.items.listItems.queryOptions());const users = useQuery(api.users.listUsers.queryOptions());
Errori Personalizzati
Sezione intitolata “Errori Personalizzati”Definisci strutture di errore nel modello Smithy per gestire risposte specifiche.
Best Practices
Sezione intitolata “Best Practices”Gestione Stati di Caricamento
Sezione intitolata “Gestione Stati di Caricamento”Gestisci sempre stati di loading ed errori:
if (items.isLoading) return <LoadingSpinner />;if (items.isError) return <ErrorMessage message={items.error.message} />;
Aggiornamenti Ottimistici
Sezione intitolata “Aggiornamenti Ottimistici”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 };}
Type Safety
Sezione intitolata “Type Safety”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}`); },});
Type safety con client diretto
const handleSubmit = async (data: CreateItemInput) => { try { await api.createItem(data); // ✅ Errore se input non valido } catch (e) { const err = e as CreateItemError; // ✅ Tipi errori definiti }};
I tipi sono generati automaticamente dallo schema OpenAPI, garantendo sincronizzazione tra frontend e backend dopo ogni build.