Communitygithub.com

Patrones de Appwrite

Appwrite backend-como-servicio — Autenticación, TablesDB, Almacenamiento, Funciones, Tiempo real, modelo de permisos, cliente tipado

¿Qué es Patrones de Appwrite?

Este skill proporciona una guía completa para usar Appwrite como plataforma de backend como servicio (BaaS) de código abierto. Cubre la configuración de los SDK de cliente y servidor con TypeScript, implementación de flujos de autenticación (email/contraseña, OAuth, URL mágica, OTP telefónico, sesiones anónimas), operaciones CRUD en TablesDB (tablas, filas, consultas, permisos), carga y descarga de archivos en Storage con control de acceso, creación de funciones serverless desencadenadas por eventos o HTTP, y suscripciones en tiempo real a cambios en filas, archivos o eventos de cuenta. También explica el modelo de permisos explícito de Appwrite, donde ningún recurso es accesible por defecto, y la importancia de usar `ID.unique()` para IDs generados automáticamente y manejar `AppwriteException` con try/catch. El skill detalla los patrones clave como la separación de SDKs (`appwrite` para cliente con autenticación de sesión y `node-appwrite` para servidor con clave API), la arquitectura de clases de servicio compartiendo una única instancia de `Client`, y la sintaxis de permisos con `Permission` y `Role`. Incluye enlaces a ejemplos detallados para casos de uso comunes y marca las prácticas incorrectas más frecuentes.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace Patrones de Appwrite?

Guía rápida: Utiliza Appwrite como tu BaaS de código abierto para autenticación, datos estructurados (TablesDB), almacenamiento de archivos, funciones serverless y suscripciones en tiempo real. Siempre inicializa las clases de servicio desde una instancia compartida de Client, establece permisos explícitamente en cada fila (nada es accesible por defecto) y usa el SDK de servidor (node-appwrite) con autenticación mediante clave API solo en el backend.


<critical_requirements>

CRÍTICO: Antes de Usar Este Skill

Todo el código debe seguir las convenciones del proyecto en CLAUDE.md (kebab-case, exportaciones nombradas, orden de importaciones, import type, constantes nombradas)

(DEBES establecer permisos explícitamente en cada fila y archivo — Appwrite no concede acceso por defecto, por lo que omitir permisos hace que los datos sean inaccesibles)

(DEBES usar node-appwrite con autenticación mediante clave API en el servidor y appwrite con autenticación de sesión en el cliente — NUNCA expongas claves API en paquetes de cliente)

(DEBES siempre verificar AppwriteException en cada llamada al SDK — Appwrite lanza excepciones, NO devuelve tuplas { data, error })

(DEBES usar ID.unique() para IDs generados automáticamente — pasar una cadena de texto crea un ID personalizado, no uno generado automáticamente)

(DEBES usar la API de TablesDB para proyectos nuevos — la API heredada de Bases de datos/colecciones/documentos está obsoleta y solo recibe parches de seguridad)

</critical_requirements>


Detección automática: Appwrite, appwrite, node-appwrite, TablesDB, Account, Databases, Permission, Role, ID.unique, Query.equal, createEmailPasswordSession, realtime.subscribe, Channel.files, Channel.tablesdb, APPWRITE_FUNCTION

Cuándo usarlo:

  • Configurar el SDK de cliente o servidor de Appwrite con TypeScript
  • Implementar autenticación (email/contraseña, OAuth, URL mágica, OTP telefónico, sesiones anónimas)
  • Realizar operaciones CRUD en tablas de TablesDB (filas, consultas, permisos)
  • Subir y servir archivos desde buckets de Storage con control de acceso
  • Construir funciones serverless desencadenadas por eventos, programaciones o HTTP
  • Suscribirse a cambios en tiempo real en filas, archivos o eventos de cuenta
  • Gestionar membresías de equipos y permisos basados en roles

Patrones clave cubiertos:

  • Inicialización de SDK de cliente y servidor con clases de servicio tipadas
  • Flujos de autenticación: registro, inicio de sesión, OAuth, URL mágica, gestión de sesiones
  • Operaciones de TablesDB con la clase Query para filtrado, paginación, ordenación
  • Modelo de permisos: Permission.read(Role.any()), Role.user(), Role.team()
  • Almacenamiento: subida, descarga, vista previa con transformaciones de imagen, permisos de bucket
  • Funciones serverless: export default async ({ req, res, log, error }) => {}
  • Suscripciones en tiempo real mediante ayudantes Channel y realtime.subscribe()

Cuándo NO usarlo:

  • Conexiones directas a bases de datos o consultas SQL (Appwrite solo es API, no SQL directo)
  • Uniones relacionales complejas entre muchas tablas (Appwrite soporta relaciones pero no uniones SQL arbitrarias)
  • Aplicaciones que requieren tiempo real del lado del servidor (Appwrite Realtime solo es SDK de cliente)

Recursos detallados:

  • Para marcos de decisión y antipatrones, consulta reference.md

Cliente y Consultas:

  • examples/core.md — Configuración del cliente, CRUD de TablesDB, manejo de errores, permisos

Autenticación:

  • examples/auth.md — Flujos completos de autenticación, OAuth, URL mágica, sesiones, equipos

Almacenamiento y Funciones:

Tiempo Real:


Filosofía

Appwrite es un backend-como-servicio de código abierto que proporciona autenticación, bases de datos (TablesDB), almacenamiento de archivos, funciones serverless y tiempo real, todo a través de un SDK unificado. Puede ser auto-alojado o usado a través de Appwrite Cloud.

Principios fundamentales:

  1. Seguro por defecto — Nada es accesible sin permisos explícitos. Cada fila y archivo debe tener permisos establecidos, o será invisible para todos los usuarios. Esto es lo opuesto a "abierto por defecto" — trátalo como un paso obligatorio, no una ocurrencia tardía.
  2. Arquitectura de clases de servicio — Todas las interacciones con el SDK pasan a través de clases de servicio (Account, TablesDB, Storage, Functions, Teams, Realtime) instanciadas desde un Client compartido. Este patrón es consistente en los SDK de cliente y servidor.
  3. División en dos SDKs — appwrite (cliente) usa autenticación basada en sesión para navegadores. node-appwrite (servidor) usa autenticación mediante clave API para backends. Comparten la misma forma de API pero diferentes mecanismos de autenticación. Nunca los mezcles.
  4. Excepciones, no tuplas — Appwrite lanza AppwriteException en caso de fallo, no tuplas { data, error }. Siempre envuelve las llamadas en try/catch.
  5. TablesDB es el futuro — Appwrite 1.8 introdujo TablesDB (tablas/filas/columnas) como la API moderna. La API heredada de Bases de datos (colecciones/documentos/atributos) aún funciona pero está obsoleta. Las nuevas características solo llegan a TablesDB.
  6. Generación de IDs — Usa ID.unique() para IDs únicos generados por el servidor. Si pasas una cadena directamente, se convierte en un ID personalizado (que debe ser globalmente único dentro de la tabla).

Cuándo usar Appwrite:

  • BaaS de código abierto y auto-alojable con control total sobre tus datos
  • Proyectos que necesitan autenticación, base de datos, almacenamiento y funciones en una sola plataforma
  • Equipos que buscan un BaaS auto-alojable sin bloqueo de proveedor
  • Aplicaciones que se benefician de permisos granulares a nivel de fila/documento

Cuándo NO usarlo:

  • Aplicaciones complejas con mucho SQL que requieren uniones, vistas y procedimientos almacenados
  • Consumidores de tiempo real del lado del servidor (Appwrite Realtime solo es SDK de cliente — no tiene soporte en el SDK de servidor)
  • Aplicaciones offline-first que requieren sincronización local (Appwrite no tiene sincronización offline integrada)

Patrones Fundamentales

Patrón 1: Configuración del SDK de Cliente (Navegador)

Inicializa el cliente de Appwrite con el endpoint y el ID del proyecto. Todas las clases de servicio comparten una única instancia de 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);

Por qué es bueno: Una única instancia de cliente compartida entre servicios, constantes nombradas para la configuración, exportaciones nombradas para cada servicio, variables de entorno mantienen la configuración fuera del código

// MAL: Configuración hardcodeada, recreando clientes
import { Client, Account } from "appwrite";

const account = new Account(
  new Client()
    .setEndpoint("https://cloud.appwrite.io/v1") // Hardcodeado
    .setProject("abc123"), // ID de proyecto hardcodeado
);

Por qué es malo: Endpoint y ID de proyecto hardcodeados, un nuevo Cliente por servicio desperdicia recursos, no hay configuración compartida


Patrón 2: Configuración del SDK de Servidor (Node.js)

El SDK de servidor usa autenticación mediante clave API — nunca expongas claves API en código de cliente.

// 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);

Por qué es bueno: .setKey() para autenticación mediante clave API (solo servidor), servicio Users para gestión de usuarios administradores (no disponible en el SDK de cliente), constantes nombradas

Cuándo usarlo: Rutas de API, funciones serverless, scripts de administración, webhooks. NUNCA importes este módulo desde código del lado del cliente.


Patrón 3: Manejo de Errores (AppwriteException)

Appwrite lanza AppwriteException — NO devuelve tuplas { data, error }. Siempre usa 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 — mensaje legible para humanos
      // error.code — código de estado HTTP (401, 404, etc.)
      // error.type — cadena de tipo de error legible por máquina
      throw new Error(`Error de Appwrite ${error.code}: ${error.message}`);
    }
    throw error;
  }
}

Por qué es bueno: AppwriteException con reducción de tipo proporciona code, message, type; vuelve a lanzar errores desconocidos; tipo de retorno explícito usando el espacio de nombres Models

// MAL: Asumiendo devolución de tupla { data, error }
const { data, error } = await account.get(); // INCORRECTO — Appwrite lanza, no devuelve tuplas
if (error) {
  /* ... */
}

Por qué es malo: Appwrite NO devuelve tuplas de error — este código fallará silenciosamente porque account.get() o devuelve datos o lanza una excepción


Patrón 4: Operaciones de TablesDB (CRUD)

Usa TablesDB para todas las operaciones de base de datos. Los IDs de fila son necesarios para crear — usa ID.unique() para generación automática.

Crear una Fila

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: "Comprar víveres",
    completed: false,
  },
  permissions: [
    Permission.read(Role.user(userId)),
    Permission.update(Role.user(userId)),
    Permission.delete(Role.user(userId)),
  ],
});

Listar Filas con Consultas

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 — array de filas Todo
// result.total — total de coincidencias

Actualizar una Fila

const updated = await tablesDB.updateRow<Todo>({
  databaseId: DATABASE_ID,
  tableId: TABLE_ID,
  rowId: row.$id,
  data: { completed: true },
});

Eliminar una Fila

await tablesDB.deleteRow({
  databaseId: DATABASE_ID,
  tableId: TABLE_ID,
  rowId: row.$id,
});

Por qué es bueno: Constantes nombradas para IDs de base de datos/tablas, ID.unique() para IDs generados automáticamente, permisos establecidos explícitamente en la creación, clase Query para filtrado seguro de tipos, parámetro de tipo genérico para la forma de la fila


Patrón 5: Modelo de Permisos

Appwrite no concede NINGÚN acceso por defecto. Debes establecer permisos en cada fila y archivo.

Sintaxis de Permission + Role

import { Permission, Role } from "appwrite";

// Lectura pública, acceso completo del propietario
const publicReadPermissions = [
  Permission.read(Role.any()),
  Permission.update(Role.user(userId)),
  Permission.delete(Role.user(userId)),
];

// Acceso de equipo
const teamPermissions = [
  Permission.read(Role.team(teamId)),
  Permission.update(Role.team(teamId, "admin")),
  Permission.delete(Role.team(teamId, "owner")),
];

// Solo usuarios verificados
const verifiedOnlyPermissions = [Permission.read(Role.users("verified"))];

Ayudantes de Role Comunes

Role.any(); // Cualquiera (incluyendo invitados)
Role.guests(); // Solo usuarios no autenticados
Role.users(); // Todos los usuarios autenticados
Role.users("verified"); // Solo usuarios verificados
Role.user(userId); // Usuario específico por ID
Role.team(teamId); // Todos los miembros de un equipo
Role.team(teamId, "admin"); // Miembros del equipo con rol "admin"
Role.member(membershipId); // Membresía de equipo específica
Role.label("premium"); // Usuarios con etiqueta "premium"

Por qué es bueno: Permisos explícitos en cada recurso, los ayudantes de rol son seguros de tipos, los roles de equipo permiten acceso granular

// MAL: Crear una fila sin permisos
const row = await tablesDB.createRow({
  databaseId: DATABASE_ID,
  tableId: TABLE_ID,
  rowId: ID.unique(),
  data: { title: "Secreto" },
  // NO se establecieron permisos — ¡esta fila es INVISIBLE para todos!
});

Por qué es malo: Sin permisos, la fila existe en la base de datos pero NINGÚN usuario puede leerla, actualizarla o eliminarla (ni siquiera el creador) — este es el error más común de Appwrite


Patrón 6: Flujos de Autenticación

Usa el servicio Account para todas las operaciones de autenticación.

Registro e Inicio de Sesión con Email/Contraseña

import { ID, type Models } from "appwrite";

// Registro: crear cuenta + crear sesión (dos pasos)
const user = await account.create({
  userId: ID.unique(),
  email,
  password,
  name,
});
const session = await account.createEmailPasswordSession({ email, password });

// Inicio de sesión
const session = await account.createEmailPasswordSession({ email, password });

// Cierre de sesión (dispositivo actual)
await account.deleteSession({ sessionId: "current" });

Por qué es bueno: Registro en dos pasos (crear cuenta + crear sesión), parámetros de objeto para todas las llamadas al SDK, deleteSession({ sessionId: "current" }) solo para el dispositivo actual

Consulta examples/auth.md para flujos completos de autenticación que incluyen OAuth, URL mágica, sesiones anónimas, equipos y restablecimiento de contraseña.


Patrón 7: Operaciones de Almacenamiento

Sube, descarga y previsualiza archivos con el servicio Storage.

import { ID, Permission, Role } from "appwrite";

const BUCKET_ID = "user-uploads";
const PREVIEW_WIDTH = 400;
const PREVIEW_HEIGHT = 300;

// Subir con permisos
await storage.createFile({
  bucketId: BUCKET_ID,
  fileId: ID.unique(),
  file,
  permissions: [
    Permission.read(Role.user(userId)),
    Permission.update(Role.user(userId)),
  ],
});

// Vista previa de imagen con transformaciones
const url = storage.getFilePreview({
  
…

[truncado para traducción]

---

</patterns>

Individual skills in this repo

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

Skills relacionados