Principe fondamental
Règle d’or : Aucun composant ne doit appeler nhost.graphql directement. Toutes les interactions passent par le dossier src/api/ via les wrappers apiCall() et apiCallVoid().
❌ Ne jamais faire
// Dans un composant React
import { nhost } from '@/api/client' ;
function ToolsList () {
useEffect (() => {
// ❌ Appel GraphQL direct dans le composant
nhost . graphql . request ({ query: '{ tools { id name } }' })
. then ( r => setTools ( r . body . data . tools ));
}, []);
}
✅ Toujours faire
// Via l'API layer
import { toolsApi } from '@/api' ;
function ToolsList () {
useEffect (() => {
toolsApi . list (). then ( setTools );
}, []);
}
Structure de l’API layer
src/api/
├── base.ts # apiCall, apiCallVoid, ApiError
├── client.ts # Client Nhost singleton
├── types.ts # Types partagés
├── index.ts # Barrel exports
│
├── auth.ts # Authentification (Nhost Auth)
├── tools.ts # CRUD Tools (GraphQL)
├── categories.ts # CRUD Categories
├── filters.ts # CRUD Filters/Features
├── workflows.ts # CRUD Workflows
├── packs.ts # CRUD Tool Packs
├── quiz/ # CRUD Quiz (multi-fichiers)
│
└── __tests__/ # Tests API
├── tools.spec.ts
└── ...
Fichiers de base
client.ts - Client Nhost singleton
import { createClient } from '@nhost/nhost-js' ;
const nhostSubdomain = import . meta . env . VITE_NHOST_SUBDOMAIN || 'gqvlmqwbsmkhlllmgbyw' ;
const nhostRegion = import . meta . env . VITE_NHOST_REGION || 'eu-central-1' ;
export const nhost = createClient ({
subdomain: nhostSubdomain ,
region: nhostRegion ,
});
Pourquoi un singleton ?
Une seule instance partagée
Gestion de session centralisée par Nhost
Token JWT automatiquement injecté dans les requêtes
base.ts - Wrappers et gestion d’erreurs
export class ApiError extends Error {
constructor ( message : string , public code ?: string , public details ?: unknown ) {
super ( message );
this . name = 'ApiError' ;
}
}
// Pour les opérations qui retournent des données
export async function apiCall < T >(
operation : () => Promise <{ data : T ; error : unknown }>,
operationName : string
) : Promise < T > { /* ... */ }
// Pour les mutations sans retour
export async function apiCallVoid (
operation : () => Promise <{ error : unknown }>,
operationName : string
) : Promise < void > { /* ... */ }
Bénéfices :
Erreurs typées (ApiError) et prévisibles
Logging centralisé via logger (pas de console.log)
Détection automatique des erreurs réseau
Nom d’opération pour le debugging
Patterns d’utilisation
Pattern 1 : Query simple
import { apiCall } from './base' ;
import { nhost } from './client' ;
async function listTools () {
const query = `
query GetTools {
tools(order_by: { name: asc }) {
id name description pricing_tier
}
}
` ;
return apiCall (
() => nhost . graphql . request ({ query }). then ( r => ({
data: r . body ?. data ?. tools || [],
error: r . error
})),
'tools.list'
);
}
Pattern 2 : Mutation avec variables
async function createTool ( tool : ToolInsert ) {
const mutation = `
mutation InsertTool($object: tools_insert_input!) {
insert_tools_one(object: $object) {
id name
}
}
` ;
return apiCall (
() => nhost . graphql . request ({
query: mutation ,
variables: { object: tool }
}). then ( r => ({
data: r . body ?. data ?. insert_tools_one ,
error: r . error
})),
'tools.create'
);
}
Pattern 3 : Update avec _set (convention Hasura)
async function updateTool ( id : string , updates : Partial < ToolInsert >) {
const mutation = `
mutation UpdateTool($id: uuid!, $set: tools_set_input!) {
update_tools_by_pk(pk_columns: { id: $id }, _set: $set) {
id name
}
}
` ;
return apiCall (
() => nhost . graphql . request ({
query: mutation ,
variables: { id , set: updates }
}). then ( r => ({
data: r . body ?. data ?. update_tools_by_pk ,
error: r . error
})),
'tools.update'
);
}
Convention Hasura : Utilisez _set (avec underscore) pour les champs à mettre à jour dans les mutations. C’est le format attendu par Hasura.
Pattern 4 : Delete (void)
async function deleteTool ( id : string ) {
const mutation = `
mutation DeleteTool($id: uuid!) {
delete_tools_by_pk(id: $id) { id }
}
` ;
await apiCallVoid (
() => nhost . graphql . request ({
query: mutation ,
variables: { id }
}). then ( r => ({ error: r . error })),
'tools.delete'
);
}
Authentification
auth.ts
import { nhost } from './client' ;
export const authApi = {
async signIn ( email : string , password : string ) {
const result = await nhost . auth . signIn ({ email , password });
if ( result . error ) throw new ApiError ( result . error . message );
return result . session ;
},
async signOut () {
const result = await nhost . auth . signOut ();
if ( result . error ) throw new ApiError ( result . error . message );
},
getSession () {
return nhost . auth . getSession ();
},
isAuthenticated () {
return !! nhost . auth . getSession ();
}
};
Bonnes pratiques
✅ À faire
Toujours utiliser apiCall/apiCallVoid
Toujours typer les retours : Promise<EnhancedTool[]>
Utiliser logger au lieu de console.log
Nommer les opérations : 'tools.list', 'workflows.create'
Valider les entrées côté client avant d’envoyer
❌ À éviter
Appels nhost.graphql directs dans les composants
Ignorer les erreurs silencieusement
Types any — utiliser src/api/types.ts
console.log en production — utiliser logger
Ressources
Nhost & GraphQL Architecture backend complète
Database Schema Structure des 10 tables
Hooks Utiliser l’API dans des hooks
Testing Tester l’API layer