Réagir à FastAPI
Le générateur api-connection
permet d’intégrer rapidement votre site React avec votre backend FastAPI. Il configure tous les éléments nécessaires pour une connexion typée à vos APIs FastAPI, incluant la génération de clients et de hooks TanStack Query, la prise en charge de l’authentification AWS IAM et Cognito, ainsi qu’une gestion d’erreurs appropriée.
Prérequis
Section intitulée « Prérequis »Avant d’utiliser ce générateur, assurez-vous que votre application React dispose de :
- Un fichier
main.tsx
qui rend votre application - Un backend FastAPI fonctionnel (généré via le générateur FastAPI)
- Une authentification Cognito ajoutée via le générateur
ts#react-website-auth
si vous connectez une API utilisant l’authentification Cognito ou IAM
Exemple de structure requise pour 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 HTZLElement,);root.render( <StrictMode> <App /> </StrictMode>,);
Utilisation
Section intitulée « Utilisation »Exécuter le générateur
Section intitulée « Exécuter le générateur »- Installez le Nx Console VSCode Plugin si ce n'est pas déjà fait
- Ouvrez la console Nx dans VSCode
- Cliquez sur
Generate (UI)
dans la section "Common Nx Commands" - Recherchez
@aws/nx-plugin - api-connection
- Remplissez les paramètres requis
- Cliquez sur
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
Vous pouvez également effectuer une simulation pour voir quels fichiers seraient modifiés
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
Paramètre | Type | Par défaut | Description |
---|---|---|---|
sourceProject Requis | string | - | The source project which will call the API |
targetProject Requis | string | - | The target project containing your API |
Résultat du générateur
Section intitulée « Résultat du générateur »Le générateur modifiera les fichiers suivants dans votre projet FastAPI :
Répertoirescripts
- generate_open_api.py Ajoute un script pour générer la spécification OpenAPI de votre API
- project.json Ajoute une nouvelle cible de build pour exécuter le script de génération
Le générateur modifiera les fichiers suivants dans votre application React :
Répertoiresrc
Répertoirecomponents
- <ApiName>Provider.tsx Provider pour le client de votre API
- QueryClientProvider.tsx Provider du client TanStack React Query
Répertoirehooks
- use<ApiName>.tsx Ajoute un hook pour appeler votre API avec gestion d’état par TanStack Query
- use<ApiName>Client.tsx Ajoute un hook pour instancier le client vanilla pouvant appeler votre API
- useSigV4.tsx Ajoute un hook pour signer les requêtes HTTP avec SigV4 (si l’authentification IAM est sélectionnée)
- project.json Ajoute une nouvelle cible de build pour générer un client typé
- .gitignore Les fichiers clients générés sont ignorés par défaut
Le générateur ajoutera également une configuration Runtime à votre infrastructure de site web si elle n’existe pas déjà, garantissant que l’URL de l’API FastAPI est disponible dans le site et configurée automatiquement par le hook use<ApiName>.tsx
.
Génération de code
Section intitulée « Génération de code »Lors du build, un client typé est généré à partir de la spécification OpenAPI de votre FastAPI. Cela ajoute trois nouveaux fichiers à votre application React :
Répertoiresrc
Répertoiregenerated
Répertoire<ApiName>
- types.gen.ts Types générés à partir des modèles pydantic de votre FastAPI
- client.gen.ts Client typé pour appeler votre API
- options-proxy.gen.ts Fournit des méthodes pour créer des options de hooks TanStack Query interagissant avec votre API
Utiliser le code généré
Section intitulée « Utiliser le code généré »Le client typé généré permet d’appeler votre FastAPI depuis votre application React. Il est recommandé d’utiliser les hooks TanStack Query, mais le client vanilla peut aussi être utilisé directement.
Utiliser le hook d’API
Section intitulée « Utiliser le hook d’API »Le générateur fournit un hook use<ApiName>
pour appeler votre API avec TanStack Query.
Requêtes
Section intitulée « Requêtes »Utilisez la méthode queryOptions
pour récupérer les options nécessaires à l’appel de votre API via le hook useQuery
de TanStack Query :
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>Chargement...</div>; if (item.isError) return <div>Erreur : {item.error.message}</div>;
return <div>Élément : {item.data.name}</div>;}
Utiliser le client d'API directement
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>Chargement...</div>; if (error) return <div>Erreur : {error.message}</div>;
return <div>Élément : {item.name}</div>;}
Mutations
Section intitulée « Mutations »Les hooks générés prennent en charge les mutations via le hook useMutation
de TanStack Query, offrant une gestion propre des opérations de création, mise à jour et suppression avec états de chargement, gestion d’erreurs et mises à jour optimistes.
import { useMutation } from '@tanstack/react-query';import { useMyApi } from './hooks/useMyApi';
function CreateItemForm() { const api = useMyApi(); // Crée une mutation avec les options générées const createItem = useMutation(api.createItem.mutationOptions());
const handleSubmit = (e) => { e.preventDefault(); createItem.mutate({ name: 'Nouvel élément', description: 'Un nouvel élément' }); };
return ( <form onSubmit={handleSubmit}> {/* Champs du formulaire */} <button type="submit" disabled={createItem.isPending} > {createItem.isPending ? 'Création...' : 'Créer l\'élément'} </button>
{createItem.isSuccess && ( <div className="success"> Élément créé avec l'ID : {createItem.data.id} </div> )}
{createItem.isError && ( <div className="error"> Erreur : {createItem.error.message} </div> )} </form> );}
Vous pouvez ajouter des callbacks pour différents états de mutation :
const createItem = useMutation({ ...api.createItem.mutationOptions(), onSuccess: (data) => { console.log('Élément créé :', data); navigate(`/items/${data.id}`); }, onError: (error) => { console.error('Échec de la création :', error); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: api.listItems.queryKey() }); }});
Mutations avec le client d'API directement
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: 'Nouvel élément', description: 'Un nouvel élément' }); setCreatedItem(newItem); } catch (err) { setError(err); console.error('Échec de la création :', err); } finally { setIsLoading(false); } };
return ( <form onSubmit={handleSubmit}> <button type="submit" disabled={isLoading} > {isLoading ? 'Création...' : 'Créer l\'élément'} </button>
{createdItem && ( <div className="success"> Élément créé avec l'ID : {createdItem.id} </div> )}
{error && ( <div className="error"> Erreur : {error.message} </div> )} </form> );}
Pagination avec requêtes infinies
Section intitulée « Pagination avec requêtes infinies »Pour les endpoints utilisant un paramètre cursor
, les hooks générés prennent en charge les requêtes infinies via useInfiniteQuery
de TanStack Query, facilitant l’implémentation de fonctionnalités “charger plus” ou de défilement infini.
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 ? 'Chargement...' : items.hasNextPage ? 'Charger plus' : 'Fin des éléments'} </button> </div> );}
Les hooks générés gèrent automatiquement la pagination par curseur si votre API la supporte. La valeur nextCursor
est extraite de la réponse pour récupérer la page suivante.
Pagination avec le client d'API directement
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(prev => [...prev, ...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 ? 'Chargement...' : nextCursor ? 'Charger plus' : 'Fin des éléments'} </button> </div> );}
Gestion des erreurs
Section intitulée « Gestion des erreurs »L’intégration inclut une gestion d’erreurs typée. Un type <operation-name>Error
est généré, encapsulant les réponses d’erreur possibles définies dans la spécification OpenAPI. Chaque erreur a une propriété status
et error
, permettant un traitement spécifique via le status
.
import { useMutation } from '@tanstack/react-query';
function MyComponent() { const api = useMyApi(); const createItem = useMutation(api.createItem.mutationOptions());
const handleClick = () => { createItem.mutate({ name: 'Nouvel élément' }); };
if (createItem.error) { switch (createItem.error.status) { case 400: return ( <div> <h2>Entrée invalide :</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: return ( <div> <h2>Non autorisé :</h2> <p>{createItem.error.error.reason}</p> </div> ); case 500: case 502: return ( <div> <h2>Erreur serveur :</h2> <p>{createItem.error.error.message}</p> <p>Trace ID : {createItem.error.error.traceId}</p> </div> ); } }
return <button onClick={handleClick}>Créer l'élément</button>;}
Gestion des erreurs avec le client directement
function MyComponent() { const api = useMyApiClient(); const [error, setError] = useState<CreateItemError | null>(null);
const handleClick = async () => { try { await api.createItem({ name: 'Nouvel élément' }); } catch (e) { const err = e as CreateItemError; setError(err); } };
if (error) { switch (error.status) { case 400: return ( <div> <h2>Entrée invalide :</h2> <p>{error.error.message}</p> <ul> {error.error.validationErrors.map((err) => ( <li key={err.field}>{err.message}</li> ))} </ul> </div> ); case 403: return ( <div> <h2>Non autorisé :</h2> <p>{error.error.reason}</p> </div> ); case 500: case 502: return ( <div> <h2>Erreur serveur :</h2> <p>{error.error.message}</p> <p>Trace ID : {error.error.traceId}</p> </div> ); } }
return <button onClick={handleClick}>Créer l'élément</button>;}
Consommer un flux
Section intitulée « Consommer un flux »Si vous avez configuré votre FastAPI pour streamer des réponses, le hook useQuery
mettra automatiquement à jour ses données à chaque réception de nouveau morceau.
Exemple :
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> );}
Le cycle de vie d’un flux :
-
La requête HTTP est envoyée
isLoading
=true
fetchStatus
='fetching'
data
=undefined
-
Premier morceau reçu
isLoading
=false
fetchStatus
='fetching'
data
= tableau avec le premier morceau
-
Morceaux suivants reçus
isLoading
restefalse
fetchStatus
reste'fetching'
data
est mis à jour à chaque morceau
-
Flux terminé
isLoading
restefalse
fetchStatus
='idle'
data
contient tous les morceaux
Streaming avec le client directement
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> );}
Personnalisation du code généré
Section intitulée « Personnalisation du code généré »Requêtes et mutations
Section intitulée « Requêtes et mutations »Par défaut, les opérations utilisant PUT
, POST
, PATCH
et DELETE
sont considérées comme des mutations. Ce comportement peut être modifié via x-query
et x-mutation
.
@app.post( "/items", openapi_extra={ "x-query": True })def list_items(): # ...
Génère des queryOptions
malgré la méthode POST
:
const items = useQuery(api.listItems.queryOptions());
x-mutation
Section intitulée « x-mutation »@app.get( "/start-processing", openapi_extra={ "x-mutation": True })def start_processing(): # ...
Génère des mutationOptions
malgré la méthode GET
:
const startProcessing = useMutation(api.startProcessing.mutationOptions());
Curseur de pagination personnalisé
Section intitulée « Curseur de pagination personnalisé »Par défaut, le paramètre de pagination est nommé cursor
. Personnalisez-le via 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 }
Pour désactiver la pagination par curseur :
@app.get( "/items", openapi_extra={ "x-cursor": False })def list_items(page: int = 1, limit: int = 10): # ...
Regroupement des opérations
Section intitulée « Regroupement des opérations »Les hooks et méthodes client sont organisés selon les tags OpenAPI de vos endpoints FastAPI.
Exemple :
@app.get("/items", tags=["items"])def list(): # ...
@app.post("/items", tags=["items"])def create(item: Item): # ...
@app.get("/users", tags=["users"])def list(): # ...
Utilisation groupée :
function ItemsAndUsers() { const api = useMyApi();
const items = useQuery(api.items.list.queryOptions()); const createItem = useMutation(api.items.create.mutationOptions()); const users = useQuery(api.users.list.queryOptions());
return ( <div> <h2>Éléments</h2> <ul> {items.data?.map(item => <li key={item.id}>{item.name}</li>)} </ul> <button onClick={() => createItem.mutate({ name: 'Nouvel élément' })}> Ajouter </button>
<h2>Utilisateurs</h2> <ul> {users.data?.map(user => <li key={user.id}>{user.name}</li>)} </ul> </div> );}
Regroupement avec le client directement
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); const itemsData = await api.items.list(); setItems(itemsData); const usersData = await api.users.list(); setUsers(usersData); } catch (error) { console.error('Erreur :', error); } finally { setIsLoading(false); } }; fetchData(); }, [api]);
const handleCreateItem = async () => { try { const newItem = await api.items.create({ name: 'Nouvel élément' }); setItems(prev => [...prev, newItem]); } catch (error) { console.error('Erreur :', error); } };
if (isLoading) return <div>Chargement...</div>;
return ( <div> <h2>Éléments</h2> <ul> {items.map(item => <li key={item.id}>{item.name}</li>)} </ul> <button onClick={handleCreateItem}>Ajouter</button>
<h2>Utilisateurs</h2> <ul> {users.map(user => <li key={user.id}>{user.name}</li>)} </ul> </div> );}
Personnalisez les réponses d’erreur dans FastAPI avec des modèles d’exception et des gestionnaires. Le client généré gérera automatiquement ces types d’erreur.
Modèles d’erreur personnalisés
Section intitulée « Modèles d’erreur personnalisés »class ErrorDetails(BaseModel): message: str
class ValidationError(BaseModel): message: str field_errors: list[str]
Exceptions personnalisées
Section intitulée « Exceptions personnalisées »class NotFoundException(Exception): def __init__(self, message: str): self.message = message
class ValidationException(Exception): def __init__(self, details: ValidationError): self.details = details
Gestionnaires d’exceptions
Section intitulée « Gestionnaires d’exceptions »@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(), )
Modèles de réponse d’erreur
Section intitulée « Modèles de réponse d’erreur »@app.get( "/items/{item_id}", responses={ 404: {"model": str}, 500: {"model": ErrorDetails} })def get_item(item_id: str) -> Item: # ...
@app.post( "/items", responses={ 400: {"model": ValidationError}, 403: {"model": str} })def create_item(item: Item) -> Item: # ...
Gestion des erreurs typées dans React
Section intitulée « Gestion des erreurs typées dans React »function ItemComponent() { const api = useMyApi();
const getItem = useQuery({ ...api.getItem.queryOptions({ itemId: '123' }), onError: (error) => { switch (error.status) { case 404: console.error('Non trouvé :', error.error); break; case 500: console.error('Erreur serveur :', error.error.message); break; } } });
const createItem = useMutation({ ...api.createItem.mutationOptions(), onError: (error) => { switch (error.status) { case 400: console.error('Erreur de validation :', error.error.message); break; case 403: console.error('Interdit :', 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>;}
Gestion d'erreurs personnalisées avec le client directement
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) { const err = e as GetItemError; setError(err); switch (err.status) { case 404: console.error('Non trouvé :', err.error); break; case 500: console.error('Erreur serveur :', err.error.message); break; } } finally { setLoading(false); } }; fetchItem(); }, [api]);
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>;}
Bonnes pratiques
Section intitulée « Bonnes pratiques »Gérer les états de chargement
Section intitulée « Gérer les états de chargement »Toujours gérer les états de chargement et d’erreur :
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: return <ErrorMessage message={err.error.reason} />; case 500: return <ErrorMessage message={err.error.message} trace={err.error.traceId} />; default: return <ErrorMessage message="Erreur inconnue" />; } }
return ( <ul> {items.data.map(item => <li key={item.id}>{item.name}</li>)} </ul> );}
Gestion des états avec le client directement
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: return <ErrorMessage message={err.error.reason} />; case 500: return <ErrorMessage message={err.error.message} trace={err.error.traceId} />; default: return <ErrorMessage message="Erreur inconnue" />; } }
return ( <ul> {items.map(item => <li key={item.id}>{item.name}</li>)} </ul> );}
Mises à jour optimistes
Section intitulée « Mises à jour optimistes »Implémentez des mises à jour optimistes pour une meilleure expérience utilisateur :
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(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); }, onSettled: () => { queryClient.invalidateQueries(api.listItems.queryKey()); }, });
return ( <ul> {itemsQuery.data.map(item => ( <li key={item.id}> {item.name} <button onClick={() => deleteMutation.mutate(item.id)} disabled={deleteMutation.isPending} > {deleteMutation.isPending ? 'Suppression...' : 'Supprimer'} </button> </li> ))} </ul> );}
Mises à jour optimistes avec le client directement
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); } };
return ( <ul> {items.map(item => ( <li key={item.id}> {item.name} <button onClick={() => handleDelete(item.id)}>Supprimer</button> </li> ))} </ul> );}
Sécurité des types
Section intitulée « Sécurité des types »L’intégration assure une sécurité des types de bout en bout. Votre IDE fournira l’autocomplétion et la vérification de types pour tous les appels d’API :
function ItemForm() { const api = useMyApi(); const createItem = useMutation({ ...api.createItem.mutationOptions(), onSuccess: (data) => { console.log(`ID : ${data.id}`); // data typé selon le schéma de réponse }, });
const handleSubmit = (data: CreateItemInput) => { createItem.mutate(data); // Erreur de type si `data` ne correspond pas };
if (createItem.error) { const error = createItem.error; switch (error.status) { case 400: return <FormError errors={error.error.validationErrors} />; case 403: return <AuthError reason={error.error.reason} />; default: return <ServerError message={error.error.message} />; } }
return ( <form onSubmit={e => { e.preventDefault(); handleSubmit({ name: 'Nouvel élément' }); }}> <button disabled={createItem.isPending}> {createItem.isPending ? 'Création...' : 'Créer'} </button> </form> );}
Sécurité des types avec le client directement
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: console.error('Erreurs :', err.error.validationErrors); break; case 403: console.error('Raison :', err.error.reason); break; case 500: console.error('Trace :', err.error.traceId); break; } setError(err); } };
if (error) { switch (error.status) { case 400: return <FormError errors={error.error.validationErrors} />; case 403: return <AuthError reason={error.error.reason} />; default: return <ServerError message={error.error.message} />; } }
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}
Les types sont générés automatiquement depuis le schéma OpenAPI de votre FastAPI, garantissant que toute modification de l’API est reflétée dans le code frontend après un build.