Modèles Appwrite
Guide rapide : Utilisez Appwrite comme votre BaaS open-source pour l'authentification, les données structurées (TablesDB), le stockage de fichiers, les fonctions serverless et les abonnements en temps réel. Initialisez toujours les classes de service à partir d'une instance
Clientpartagée, définissez les permissions explicitement sur chaque ligne (rien n'est accessible par défaut) et utilisez le SDK serveur (node-appwrite) avec authentification par clé API uniquement côté backend.
<critical_requirements>
CRITIQUE : Avant d'utiliser cette compétence
Tout le code doit suivre les conventions du projet dans CLAUDE.md (kebab-case, exports nommés, ordre des imports,
import type, constantes nommées)
(Vous DEVEZ définir les permissions explicitement sur chaque ligne et fichier — Appwrite n'accorde AUCUN accès par défaut, donc omettre les permissions rend les données inaccessibles)
(Vous DEVEZ utiliser node-appwrite avec authentification par clé API côté serveur et appwrite avec authentification par session côté client — N'exposez JAMAIS les clés API dans les bundles clients)
(Vous DEVEZ toujours vérifier AppwriteException sur chaque appel SDK — Appwrite lance des exceptions, il ne renvoie PAS de tuples { data, error })
(Vous DEVEZ utiliser ID.unique() pour les IDs générés automatiquement — passer une chaîne brute crée un ID personnalisé, pas un ID automatique)
(Vous DEVEZ utiliser l'API TablesDB pour les nouveaux projets — l'API héritée Bases de données/collections/documents est obsolète et ne reçoit que des correctifs de sécurité)
</critical_requirements>
Détection automatique : Appwrite, appwrite, node-appwrite, TablesDB, Account, Databases, Permission, Role, ID.unique, Query.equal, createEmailPasswordSession, realtime.subscribe, Channel.files, Channel.tablesdb, APPWRITE_FUNCTION
Quand l'utiliser :
- Configuration d'un SDK client ou serveur Appwrite avec TypeScript
- Implémentation de l'authentification (email/mot de passe, OAuth, URL magique, OTP téléphonique, sessions anonymes)
- Opérations CRUD sur les tables TablesDB (lignes, requêtes, permissions)
- Téléchargement et diffusion de fichiers depuis des buckets de stockage avec contrôle d'accès
- Construction de fonctions serverless déclenchées par des événements, des plannings ou HTTP
- Abonnement aux changements en temps réel sur les lignes, fichiers ou événements de compte
- Gestion des adhésions d'équipe et permissions basées sur les rôles
Modèles clés couverts :
- Initialisation des SDK client et serveur avec classes de service typées
- Flux d'authentification : inscription, connexion, OAuth, URL magique, gestion des sessions
- Opérations TablesDB avec la classe
Querypour le filtrage, la pagination, l'ordonnancement - Modèle de permissions :
Permission.read(Role.any()),Role.user(),Role.team() - Stockage : téléchargement, téléchargement, aperçu avec transformations d'image, permissions de bucket
- Fonctions serverless :
export default async ({ req, res, log, error }) => {} - Abonnements en temps réel via les helpers
Channeletrealtime.subscribe()
Quand NE PAS l'utiliser :
- Connexions directes à la base de données ou requêtes SQL (Appwrite est uniquement API, pas de SQL brut)
- Jointures relationnelles complexes sur plusieurs tables (Appwrite prend en charge les relations mais pas les jointures SQL arbitraires)
- Applications nécessitant du temps réel côté serveur (Appwrite Realtime est uniquement SDK client)
Ressources détaillées :
- Pour des cadres de décision et des anti-patrons, voir reference.md
Client & Requêtes :
- examples/core.md — Configuration client, CRUD TablesDB, gestion des erreurs, permissions
Authentification :
- examples/auth.md — Flux d'authentification complets, OAuth, URL magique, sessions, équipes
Stockage & Fonctions :
- examples/storage.md — Téléchargement, téléchargement, aperçus, permissions de bucket
- examples/functions.md — Fonctions serverless, déclencheurs, utilisation du SDK à l'intérieur des fonctions
Temps réel :
- examples/realtime.md — Abonnements aux canaux, filtrage d'événements, nettoyage
Philosophie
Appwrite est un backend open-source en tant que service fournissant l'authentification, les bases de données (TablesDB), le stockage de fichiers, les fonctions serverless et le temps réel — le tout via un SDK unifié. Il peut être auto-hébergé ou utilisé via Appwrite Cloud.
Principes fondamentaux :
- Sécurisé par défaut — Rien n'est accessible sans permissions explicites. Chaque ligne et fichier doit avoir des permissions définies, sinon il est invisible pour tous les utilisateurs. C'est l'inverse de « ouvert par défaut » — traitez-le comme une étape obligatoire, pas une réflexion après coup.
- Architecture en classes de service — Toutes les interactions SDK passent par des classes de service (
Account,TablesDB,Storage,Functions,Teams,Realtime) instanciées à partir d'unClientpartagé. Ce modèle est cohérent entre les SDK client et serveur. - Deux SDK distincts —
appwrite(client) utilise l'authentification par session pour les navigateurs.node-appwrite(serveur) utilise l'authentification par clé API pour les backends. Ils partagent la même forme API mais des mécanismes d'authentification différents. Ne les mélangez jamais. - Exceptions, pas de tuples — Appwrite lance
AppwriteExceptionen cas d'échec, pas de tuples{ data, error }. Entourez toujours les appels avec try/catch. - TablesDB est l'avenir — Appwrite 1.8 a introduit TablesDB (tables/lignes/colonnes) comme API moderne. L'API héritée Databases (collections/documents/attributs) fonctionne toujours mais est obsolète. Les nouvelles fonctionnalités n'arrivent que dans TablesDB.
- Génération d'ID — Utilisez
ID.unique()pour les ID uniques générés par le serveur. Si vous passez une chaîne directement, elle devient un ID personnalisé (qui doit être globalement unique dans la table).
Quand utiliser Appwrite :
- BaaS open-source auto-hébergeable avec contrôle total de vos données
- Projets nécessitant authentification, base de données, stockage et fonctions sur une seule plateforme
- Équipes souhaitant un BaaS auto-hébergeable sans verrouillage fournisseur
- Applications bénéficiant de permissions granulaires au niveau des documents/lignes
Quand NE PAS utiliser :
- Applications SQL complexes nécessitant des jointures, des vues et des procédures stockées
- Consommateurs de temps réel côté serveur (Appwrite Realtime est uniquement SDK client — pas de support SDK serveur)
- Applications hors ligne nécessitant une synchronisation locale (Appwrite n'a pas de synchronisation hors ligne intégrée)
<modèles>
Modèles principaux
Modèle 1 : Configuration SDK Client (Navigateur)
Initialisez le client Appwrite avec le point de terminaison et l'ID de projet. Toutes les classes de service partagent une seule instance Client.
// lib/appwrite.ts
import { Client, Account, TablesDB, Storage, Realtime } from "appwrite";
const APPWRITE_ENDPOINT = process.env.APPWRITE_ENDPOINT!;
const APPWRITE_PROJECT_ID = process.env.APPWRITE_PROJECT_ID!;
const client = new Client()
.setEndpoint(APPWRITE_ENDPOINT)
.setProject(APPWRITE_PROJECT_ID);
export const account = new Account(client);
export const tablesDB = new TablesDB(client);
export const storage = new Storage(client);
export const realtime = new Realtime(client);
Pourquoi c'est bien : Instance client unique partagée entre les services, constantes nommées pour la configuration, exports nommés pour chaque service, variables d'environnement gardent la configuration hors du code
// MAUVAIS : Configuration en dur, recréation des clients
import { Client, Account } from "appwrite";
const account = new Account(
new Client()
.setEndpoint("https://cloud.appwrite.io/v1") // En dur
.setProject("abc123"), // ID de projet en dur
);
Pourquoi c'est mauvais : Point de terminaison et ID de projet en dur, nouveau Client par service gaspille des ressources, pas de configuration partagée
Modèle 2 : Configuration SDK Serveur (Node.js)
Le SDK serveur utilise l'authentification par clé API — n'exposez jamais les clés API dans le code client.
// lib/appwrite-server.ts
import { Client, TablesDB, Users, Storage } from "node-appwrite";
const APPWRITE_ENDPOINT = process.env.APPWRITE_ENDPOINT!;
const APPWRITE_PROJECT_ID = process.env.APPWRITE_PROJECT_ID!;
const APPWRITE_API_KEY = process.env.APPWRITE_API_KEY!;
const client = new Client()
.setEndpoint(APPWRITE_ENDPOINT)
.setProject(APPWRITE_PROJECT_ID)
.setKey(APPWRITE_API_KEY);
export const tablesDB = new TablesDB(client);
export const users = new Users(client);
export const storage = new Storage(client);
Pourquoi c'est bien : .setKey() pour l'authentification par clé API (serveur uniquement), service Users pour la gestion admin des utilisateurs (non disponible dans le SDK client), constantes nommées
Quand l'utiliser : Routes API, fonctions serverless, scripts d'administration, webhooks. N'importez JAMAIS ce module depuis du code côté client.
Modèle 3 : Gestion des erreurs (AppwriteException)
Appwrite lance AppwriteException — il ne renvoie PAS de tuples { data, error }. Utilisez toujours try/catch.
import { AppwriteException, type Models } from "appwrite";
async function getUser(): Promise<Models.User<Models.Preferences>> {
try {
return await account.get();
} catch (error) {
if (error instanceof AppwriteException) {
// error.message — message lisible
// error.code — code HTTP (401, 404, etc.)
// error.type — chaîne de type d'erreur lisible par machine
throw new Error(`Erreur Appwrite ${error.code} : ${error.message}`);
}
throw error;
}
}
Pourquoi c'est bien : AppwriteException avec type réduit fournit code, message, type ; relance les erreurs inconnues ; type de retour explicite utilisant l'espace de noms Models
// MAUVAIS : En supposant des retours de tuple { data, error }
const { data, error } = await account.get(); // FAUX — Appwrite lance, ne renvoie pas de tuples
if (error) {
/* ... */
}
Pourquoi c'est mauvais : Appwrite ne renvoie PAS de tuples d'erreur — ce code échouera silencieusement car account.get() renvoie soit des données, soit lance une exception
Modèle 4 : Opérations TablesDB (CRUD)
Utilisez TablesDB pour toutes les opérations de base de données. Les IDs de ligne sont requis pour la création — utilisez ID.unique() pour la génération automatique.
Créer une ligne
import { ID, Permission, Role } from "appwrite";
interface Todo {
title: string;
completed: boolean;
}
const DATABASE_ID = "main";
const TABLE_ID = "todos";
const row = await tablesDB.createRow<Todo>({
databaseId: DATABASE_ID,
tableId: TABLE_ID,
rowId: ID.unique(),
data: {
title: "Acheter des courses",
completed: false,
},
permissions: [
Permission.read(Role.user(userId)),
Permission.update(Role.user(userId)),
Permission.delete(Role.user(userId)),
],
});
Lister les lignes avec des requêtes
import { Query } from "appwrite";
const PAGE_SIZE = 25;
const result = await tablesDB.listRows<Todo>({
databaseId: DATABASE_ID,
tableId: TABLE_ID,
queries: [
Query.equal("completed", false),
Query.orderDesc("$createdAt"),
Query.limit(PAGE_SIZE),
],
});
// result.rows — tableau des lignes Todo
// result.total — nombre total correspondant
Mettre à jour une ligne
const updated = await tablesDB.updateRow<Todo>({
databaseId: DATABASE_ID,
tableId: TABLE_ID,
rowId: row.$id,
data: { completed: true },
});
Supprimer une ligne
await tablesDB.deleteRow({
databaseId: DATABASE_ID,
tableId: TABLE_ID,
rowId: row.$id,
});
Pourquoi c'est bien : Constantes nommées pour les IDs de base de données/table, ID.unique() pour les IDs générés automatiquement, permissions définies explicitement à la création, classe Query pour un filtrage typé sûr, paramètre de type générique pour la forme de la ligne
Modèle 5 : Modèle de permissions
Appwrite n'accorde AUCUN accès par défaut. Vous devez définir les permissions sur chaque ligne et fichier.
Syntaxe Permission + Role
import { Permission, Role } from "appwrite";
// Lecture publique, accès complet au propriétaire
const publicReadPermissions = [
Permission.read(Role.any()),
Permission.update(Role.user(userId)),
Permission.delete(Role.user(userId)),
];
// Accès d'équipe
const teamPermissions = [
Permission.read(Role.team(teamId)),
Permission.update(Role.team(teamId, "admin")),
Permission.delete(Role.team(teamId, "owner")),
];
// Utilisateurs vérifiés uniquement
const verifiedOnlyPermissions = [Permission.read(Role.users("verified"))];
Helpers Role courants
Role.any(); // N'importe qui (y compris les invités)
Role.guests(); // Utilisateurs non authentifiés uniquement
Role.users(); // Tous les utilisateurs authentifiés
Role.users("verified"); // Utilisateurs vérifiés uniquement
Role.user(userId); // Utilisateur spécifique par ID
Role.team(teamId); // Tous les membres d'une équipe
Role.team(teamId, "admin"); // Membres d'équipe avec le rôle "admin"
Role.member(membershipId); // Adhésion d'équipe spécifique
Role.label("premium"); // Utilisateurs avec le label "premium"
Pourquoi c'est bien : Permissions explicites sur chaque ressource, helpers de rôle typés, rôles d'équipe permettent un accès granulaire
// MAUVAIS : Créer une ligne sans permissions
const row = await tablesDB.createRow({
databaseId: DATABASE_ID,
tableId: TABLE_ID,
rowId: ID.unique(),
data: { title: "Secret" },
// AUCUNE permission définie — cette ligne est INVISIBLE pour tout le monde !
});
Pourquoi c'est mauvais : Sans permissions, la ligne existe dans la base de données mais AUCUN utilisateur ne peut la lire, la mettre à jour ou la supprimer (même pas le créateur) — c'est l'erreur la plus courante avec Appwrite
Modèle 6 : Flux d'authentification
Utilisez le service Account pour toutes les opérations d'authentification.
Inscription et Connexion par Email/Mot de passe
import { ID, type Models } from "appwrite";
// Inscription : créer un compte + créer une session (deux étapes)
const user = await account.create({
userId: ID.unique(),
email,
password,
name,
});
const session = await account.createEmailPasswordSession({ email, password });
// Connexion
const session = await account.createEmailPasswordSession({ email, password });
// Déconnexion (appareil actuel)
await account.deleteSession({ sessionId: "current" });
Pourquoi c'est bien : Inscription en deux étapes (créer un compte + créer une session), paramètres objet pour tous les appels SDK, deleteSession({ sessionId: "current" }) pour l'appareil actuel uniquement
Voir examples/auth.md pour les flux d'authentification complets incluant OAuth, URL magique, sessions anonymes, équipes et réinitialisation de mot de passe.
Modèle 7 : Opérations de stockage
Téléchargez, téléchargez et prévisualisez des fichiers avec le service Storage.
import { ID, Permission, Role } from "appwrite";
const BUCKET_ID = "user-uploads";
const PREVIEW_WIDTH = 400;
const PREVIEW_HEIGHT = 300;
// Téléchargement avec permissions
await storage.createFile({
bucketId: BUCKET_ID,
fileId: ID.unique(),
file,
permissions: [
Permission.read(Role.user(userId)),
Permission.update(Role.user(userId)),
],
});
// Aperçu d'image avec transformations
const url = storage.getFilePreview({
bucketId: BUCKET_ID,
fileId: "file123",
width: PREVIEW_WIDTH,
height: PREVIEW_HEIGHT,
});
Pourquoi c'est bien : Permissions définies explicitement sur le fichier téléchargé, prévisualisation avec dimensions et transformations, constantes pour l'ID du bucket
Voir examples/storage.md pour d'autres exemples incluant les options de téléchargement, les permaliens et les autorisations de bucket.
Modèle 8 : Fonctions serverless
Les fonctions serverless Appwrite s'exécutent dans un environnement Node.js sécurisé. Elles utilisent l'authentification par clé API via node-appwrite.
// src/functions/todo-handler/src/main.ts
export default async ({ req, res, log, error }) => {
try {
const payload = req.body;
const userId = req.headers['x-appwrite-user-id'];
log(`Traitement de la tâche pour l'utilisateur ${userId}`);
const tablesDB = new TablesDB(client);
await tablesDB.createRow({
databaseId: 'main',
tableId: 'todos',
rowId: ID.unique(),
data: payload,
permissions: [
Permission.read(Role.user(userId)),
Permission.update(Role.user(userId)),
Permission.delete(Role.user(userId)),
],
});
return res.json({ success: true }, 201);
} catch (err) {
error(`Échec : ${err instanceof AppwriteException ? err.message : err}`);
return res.json({ error: 'Échec du traitement' }, 500);
}
};
Pourquoi c'est bien : Déstructuration des paramètres req, res, log, error ; codes de statut explicites ; journalisation ; blocs try/catch ; client partagé initialisé avec .setKey()
Voir examples/functions.md pour des modèles de fonctions plus avancés incluant les déclencheurs d'événements, les tâches planifiées et les intégrations webhook.
Modèle 9 : Abonnements en temps réel
Utilisez le service Realtime avec les helpers Channel pour vous abonner aux changements.
import { Channel } from "appwrite";
// S'abonner aux changements sur une table
const unsubscribe = realtime.subscribe({
channels: [Channel.tablesdb({
databaseId: 'main',
tableId: 'todos',
})],
callback: (event) => {
// event.payload — les données de la ligne
// event.events — le tableau d'événements (['databases.*.collections.*.documents.*.create', ...])
// event.channels — les canaux auxquels nous sommes abonnés
console.log('Changement reçu :', event);
},
});
// S'abonner aux fichiers dans un bucket
const unsubscribeFiles = realtime.subscribe({
channels: [Channel.files({ bucketId: 'user-uploads' })],
callback: (event) => {
console.log('Changement de fichier :', event.payload);
},
});
// Se désabonner quand on a fini
unsubscribe();
Pourquoi c'est bien : Helpers Channel pour des abonnements typés, filtrage par table ou bucket, fonction de désabonnement pour le nettoyage, callback simple avec données d'événement
Voir examples/realtime.md pour plus de modèles incluant le filtrage par canal et la gestion de plusieurs abonnements.
Modèle 10 : Gestion des équipes
Créez des équipes et gérez les adhésions pour un contrôle d'accès basé sur les rôles.
import { ID, Role, Permission } from "appwrite";
// Créer une équipe
const team = await teams.create({
teamId: ID.unique(),
name: 'Équipe Projets',
roles: ['admin', 'member', 'viewer'],
});
// Ajouter un membre
const membership = await teams.createMembership({
teamId: team.$id,
roles: ['member'],
email: '[email protected]',
url: 'https://example.com/invite',
});
// Définir les permissions basées sur l'équipe
const teamScopedPermissions = [
Permission.read(Role.team(team.$id)),
Permission.update(Role.team(team.$id, 'admin')),
Permission.delete(Role.team(team.$id, 'owner')),
];
Pourquoi c'est bien : Permissions d'équipe pour un accès granulaire, rôles personnalisables, IDs générés automatiquement, désignation claire du propriétaire de l'équipe
Voir examples/auth.md pour les workflows complets de gestion d'équipe.
</modèles>