Communitygithub.com

appwrite-skill

Compétence Appwrite BaaS — Auth, TablesDB, Stockage, Fonctions, Temps réel, modèle de permissions, client typé

Qu'est-ce que appwrite-skill ?

Cette compétence fournit un guide complet pour utiliser Appwrite, un backend open-source en tant que service, couvrant l'authentification, les bases de données (TablesDB), le stockage de fichiers, les fonctions serverless et les abonnements en temps réel. Elle s'applique aux plateformes Claude Code, Cursor et Codex. Basée sur les principes de sécurité par défaut (aucun accès sans permissions explicites), d'architecture en classes de service partagées et de séparation stricte entre SDK client (`appwrite`) et serveur (`node-appwrite`). Le document explique comment initialiser les SDK, gérer les erreurs via `AppwriteException`, effectuer des opérations CRUD sur TablesDB avec des requêtes typées, définir des permissions granulaires avec `Permission` et `Role`, et implémenter des flux d'authentification complets. Il met en garde contre les erreurs courantes comme l'omission des permissions ou l'utilisation incorrecte des identifiants, et insiste sur l'utilisation de `ID.unique()` pour les IDs automatiques.

Compatible avec✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/api-baas-appwrite

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

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 Client partagé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 Query pour 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 Channel et realtime.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 :


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 :

  1. 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.
  2. 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'un Client partagé. Ce modèle est cohérent entre les SDK client et serveur.
  3. 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.
  4. Exceptions, pas de tuples — Appwrite lance AppwriteException en cas d'échec, pas de tuples { data, error }. Entourez toujours les appels avec try/catch.
  5. 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.
  6. 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>

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

Skills associés