Appwrite
Appwrite self-hosted BaaS with database, auth, storage, and serverless functions
You are an expert in Appwrite for rapid backend development, including its self-hosted architecture, database, authentication, storage, and serverless functions. ## Key Points - Use document-level permissions for user-generated content and collection-level permissions for shared data like categories or tags. - Define collection attributes and indexes in the Appwrite Console or via the server SDK before inserting documents — Appwrite enforces schema validation. - Use the Appwrite CLI `appwrite init` to scaffold function boilerplate in your preferred runtime. - Set up webhooks or function triggers to react to auth and database events without polling. - For self-hosted deployments, place Appwrite behind a reverse proxy (Traefik, Nginx) with TLS termination. - **Forgetting to create attributes/indexes**: Unlike schemaless databases, Appwrite requires you to define attributes on collections before you can store or query those fields. - **Hitting rate limits**: Appwrite enforces abuse protection by default; increase limits in `.env` for high-traffic self-hosted deployments. - **Permission model confusion**: Mixing document-level and collection-level security modes on the same collection leads to unexpected access patterns — pick one model per collection. - **Large file uploads without chunked upload**: For files over 5 MB, use the chunked upload API provided by the SDK. - **Not configuring SMTP**: Email-based auth features (verification, password reset) silently fail without an SMTP provider configured. ## Quick Example ```bash npm install -g appwrite-cli appwrite login appwrite init project ``` ```bash # Deploy via CLI appwrite functions createDeployment \ --functionId=<FUNCTION_ID> \ --entrypoint='src/main.js' \ --code='./functions/hello' ```
skilldb get baas-skills/AppwriteFull skill: 264 linesAppwrite — Backend as a Service
You are an expert in Appwrite for rapid backend development, including its self-hosted architecture, database, authentication, storage, and serverless functions.
Core Philosophy
Overview
Appwrite is an open-source, self-hosted Backend-as-a-Service platform. It provides a REST and GraphQL API layer over a MariaDB-backed document database, user authentication with 30+ OAuth providers, file storage with built-in image transformations, serverless functions in multiple runtimes, and real-time event subscriptions. It can also be used via Appwrite Cloud for a managed experience.
Setup & Configuration
Self-Hosted Installation
# Docker Compose install (recommended)
docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.5
# Appwrite will be available at http://localhost:80
CLI Setup
npm install -g appwrite-cli
appwrite login
appwrite init project
Client SDK (Web)
import { Client, Account, Databases, Storage } from 'appwrite';
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1') // or self-hosted URL
.setProject('<PROJECT_ID>');
const account = new Account(client);
const databases = new Databases(client);
const storage = new Storage(client);
Server SDK (Node.js)
import { Client, Databases, Users } from 'node-appwrite';
const client = new Client()
.setEndpoint('https://cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>')
.setKey('<API_KEY>'); // server-side API key
const databases = new Databases(client);
const users = new Users(client);
Core Patterns
Authentication
// Sign up
const user = await account.create(
ID.unique(),
'user@example.com',
'secure-password',
'Alice' // optional name
);
// Create email session (sign in)
const session = await account.createEmailPasswordSession(
'user@example.com',
'secure-password'
);
// OAuth sign in
account.createOAuth2Session(
OAuthProvider.Github,
'http://localhost:3000/success', // success redirect
'http://localhost:3000/failure' // failure redirect
);
// Get current user
const currentUser = await account.get();
// Sign out
await account.deleteSession('current');
Database
import { ID, Query } from 'appwrite';
const DATABASE_ID = 'main';
const COLLECTION_ID = 'posts';
// Create a document
const post = await databases.createDocument(
DATABASE_ID,
COLLECTION_ID,
ID.unique(),
{
title: 'My Post',
body: 'Content here',
authorId: currentUser.$id,
published: true,
}
);
// Get a document
const doc = await databases.getDocument(DATABASE_ID, COLLECTION_ID, documentId);
// List with queries
const results = await databases.listDocuments(
DATABASE_ID,
COLLECTION_ID,
[
Query.equal('published', true),
Query.orderDesc('$createdAt'),
Query.limit(10),
]
);
// Update a document
await databases.updateDocument(DATABASE_ID, COLLECTION_ID, documentId, {
title: 'Updated Title',
});
// Delete a document
await databases.deleteDocument(DATABASE_ID, COLLECTION_ID, documentId);
Permissions
import { Permission, Role } from 'appwrite';
// Create document with permissions
await databases.createDocument(
DATABASE_ID,
COLLECTION_ID,
ID.unique(),
{ title: 'Private Post', body: 'Secret' },
[
Permission.read(Role.user(currentUser.$id)),
Permission.update(Role.user(currentUser.$id)),
Permission.delete(Role.user(currentUser.$id)),
]
);
// Collection-level permissions (set in console or via server SDK)
// - Document-level: each document has its own permissions
// - Collection-level: all documents share the collection's permissions
Real-time Subscriptions
const unsubscribe = client.subscribe(
[`databases.${DATABASE_ID}.collections.${COLLECTION_ID}.documents`],
(response) => {
if (response.events.includes('databases.*.collections.*.documents.*.create')) {
console.log('New document:', response.payload);
}
if (response.events.includes('databases.*.collections.*.documents.*.delete')) {
console.log('Deleted document:', response.payload);
}
}
);
// Cleanup
unsubscribe();
Storage
import { ID } from 'appwrite';
const BUCKET_ID = 'avatars';
// Upload a file
const file = await storage.createFile(BUCKET_ID, ID.unique(), fileInput);
// Get file preview (with transformations)
const previewUrl = storage.getFilePreview(
BUCKET_ID,
file.$id,
400, // width
400, // height
'center', // gravity
80 // quality
);
// Download a file
const result = await storage.getFileDownload(BUCKET_ID, file.$id);
// Delete a file
await storage.deleteFile(BUCKET_ID, file.$id);
Serverless Functions
// functions/hello/src/main.js
export default async ({ req, res, log, error }) => {
log('Function invoked');
if (req.method === 'GET') {
return res.json({ message: 'Hello from Appwrite Functions!' });
}
const body = JSON.parse(req.body);
return res.json({ received: body });
};
# Deploy via CLI
appwrite functions createDeployment \
--functionId=<FUNCTION_ID> \
--entrypoint='src/main.js' \
--code='./functions/hello'
Best Practices
- Use document-level permissions for user-generated content and collection-level permissions for shared data like categories or tags.
- Define collection attributes and indexes in the Appwrite Console or via the server SDK before inserting documents — Appwrite enforces schema validation.
- Use the Appwrite CLI
appwrite initto scaffold function boilerplate in your preferred runtime. - Set up webhooks or function triggers to react to auth and database events without polling.
- For self-hosted deployments, place Appwrite behind a reverse proxy (Traefik, Nginx) with TLS termination.
Common Pitfalls
- Forgetting to create attributes/indexes: Unlike schemaless databases, Appwrite requires you to define attributes on collections before you can store or query those fields.
- Hitting rate limits: Appwrite enforces abuse protection by default; increase limits in
.envfor high-traffic self-hosted deployments. - Permission model confusion: Mixing document-level and collection-level security modes on the same collection leads to unexpected access patterns — pick one model per collection.
- Large file uploads without chunked upload: For files over 5 MB, use the chunked upload API provided by the SDK.
- Not configuring SMTP: Email-based auth features (verification, password reset) silently fail without an SMTP provider configured.
Anti-Patterns
Over-engineering for hypothetical requirements. Building for scenarios that may never materialize adds complexity without value. Solve the problem in front of you first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide wastes time and introduces risk.
Premature abstraction. Creating elaborate frameworks before having enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at system boundaries. Internal code can trust its inputs, but boundaries with external systems require defensive validation.
Skipping documentation. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add baas-skills
Related Skills
AWS Amplify
AWS Amplify BaaS with AppSync GraphQL, Cognito auth, S3 storage, and Lambda functions
Backendless
Backendless BaaS with real-time database, user authentication, Cloud Code,
Clerk Auth
Clerk authentication service with pre-built UI components, session management, and multi-framework support
Convex
Convex real-time backend with reactive queries, mutations, and serverless functions
Encore
Encore is a backend development platform that automatically provisions, configures, and manages cloud infrastructure based on your Go code. It simplifies building and deploying cloud-native applications by allowing you to focus purely on business logic.
Firebase
Firebase BaaS with Firestore, Authentication, Cloud Functions, and Hosting