惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
博客园 - 三生石上(FineUI控件)
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MongoDB | Blog
MongoDB | Blog
小众软件
小众软件
Y
Y Combinator Blog
博客园 - 聂微东
Google DeepMind News
Google DeepMind News
D
Docker
罗磊的独立博客
Microsoft Security Blog
Microsoft Security Blog
D
DataBreaches.Net
B
Blog
Vercel News
Vercel News
Recent Announcements
Recent Announcements
GbyAI
GbyAI
阮一峰的网络日志
阮一峰的网络日志
T
The Blog of Author Tim Ferriss
H
Hackread – Cybersecurity News, Data Breaches, AI and More
P
Proofpoint News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Microsoft Azure Blog
Microsoft Azure Blog
宝玉的分享
宝玉的分享

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Your First Full-Stack App with Appwrite — Auth, Database,...
Jordan Sterc · 2026-05-02 · via DEV Community

The gaps between the quickstart and production: query indexes, storage permissions, function cold starts, and the self-hosting gotcha everyone hits.


Appwrite gives you a complete backend in one platform — authentication, databases, file storage, serverless functions, real-time subscriptions, and messaging. One SDK. One dashboard. No stitching together five different services.

This post covers building a real full-stack app with Appwrite from scratch — and the production gaps the quickstart skips.


What Appwrite Actually Gives You

Before touching code, understand what you get out of the box:

  • Auth — email/password, OAuth (Google, GitHub, Discord, 30+ providers), magic links, phone OTP, anonymous sessions
  • Databases — document-based with relations, indexes, and real-time subscriptions
  • Storage — file buckets with permission control, image transformations, virus scanning
  • Functions — serverless functions triggered by events, schedules, or HTTP
  • Messaging — push notifications, email, SMS through one API
  • Realtime — subscribe to any database or storage change over WebSocket

All of this is self-hostable via Docker Compose, or available on Appwrite Cloud.


Setup

# Install the Appwrite CLI
npm install -g appwrite-cli

# Log in to your Appwrite project
appwrite login

# Or use the SDK directly in your project
npm install appwrite          # Browser/React/Vue/Svelte
npm install node-appwrite     # Node.js server-side

Enter fullscreen mode Exit fullscreen mode

Initialize your client:

// lib/appwrite.js
import { Client, Account, Databases, Storage } from "appwrite";

const client = new Client()
  .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT) // 'https://cloud.appwrite.io/v1' or your self-hosted URL
  .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID);

export const account = new Account(client);
export const databases = new Databases(client);
export const storage = new Storage(client);

Enter fullscreen mode Exit fullscreen mode


Authentication — The Right Way

// Sign up
async function signUp(email, password, name) {
  const user = await account.create(
    ID.unique(),  // Auto-generate user ID
    email,
    password,
    name
  );
  return user;
}

// Sign in
async function signIn(email, password) {
  const session = await account.createEmailPasswordSession(email, password);
  return session;
}

// Get current user
async function getCurrentUser() {
  try {
    return await account.get();
  } catch {
    return null; // No active session
  }
}

// Sign out
async function signOut() {
  await account.deleteSession('current');
}

Enter fullscreen mode Exit fullscreen mode

OAuth (Google example):

async function signInWithGoogle() {
  account.createOAuth2Session(
    OAuthProvider.Google,
    'https://yourapp.com/auth/callback',  // Success redirect
    'https://yourapp.com/auth/failure'    // Failure redirect
  );
  // Redirects the user — no return value
}

Enter fullscreen mode Exit fullscreen mode


Databases — Queries, Indexes, and the Performance Trap

Appwrite databases are document-based with attribute-level querying. Here’s the critical thing most developers miss: queries without indexes do full collection scans.

Create a collection and add indexes via the dashboard or CLI — not just the SDK.

import { Databases, ID, Query } from "appwrite";

const DATABASE_ID = 'main';
const COLLECTION_ID = 'posts';

// Create a document
async function createPost(title, content, authorId) {
  return databases.createDocument(
    DATABASE_ID,
    COLLECTION_ID,
    ID.unique(),
    {
      title,
      content,
      authorId,
      createdAt: new Date().toISOString(),
      published: false,
    }
  );
}

// Query documents — always use indexed attributes
async function getPostsByAuthor(authorId) {
  return databases.listDocuments(DATABASE_ID, COLLECTION_ID, [
    Query.equal('authorId', authorId),     // Index this attribute
    Query.equal('published', true),         // Index this attribute
    Query.orderDesc('createdAt'),           // Index this attribute
    Query.limit(20),
  ]);
}

Enter fullscreen mode Exit fullscreen mode

Add indexes in the Appwrite Console:
Go to your collection → Indexes tab → Add Index for every attribute you query on. Without indexes, queries slow down dramatically as your collection grows.

Relationships:

// Get posts with author data in one query
async function getPostWithAuthor(postId) {
  return databases.getDocument(DATABASE_ID, COLLECTION_ID, postId, [
    Query.select(['$id', 'title', 'content', 'createdAt', 'author.*'])
  ]);
}

Enter fullscreen mode Exit fullscreen mode


Storage — Permissions Are Not Optional

Appwrite storage uses bucket-level and file-level permissions. The default: no one can access anything. You must set permissions explicitly.

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

// Upload a file with permissions
async function uploadAvatar(file, userId) {
  return storage.createFile(
    'avatars',     // Bucket ID
    ID.unique(),
    file,
    [
      Permission.read(Role.any()),          // Anyone can view avatars
      Permission.update(Role.user(userId)), // Only the owner can update
      Permission.delete(Role.user(userId)), // Only the owner can delete
    ]
  );
}

// Get a file preview URL (with transformations)
function getAvatarUrl(fileId) {
  return storage.getFilePreview(
    'avatars',
    fileId,
    200,    // Width
    200,    // Height
    'center', // Gravity
    90      // Quality
  );
}

Enter fullscreen mode Exit fullscreen mode

Common permission patterns:

// Public read, authenticated write
Permission.read(Role.any())
Permission.write(Role.users())

// Owner-only access
Permission.read(Role.user(userId))
Permission.write(Role.user(userId))

// Team-based access
Permission.read(Role.team('admins'))
Permission.write(Role.team('admins'))

Enter fullscreen mode Exit fullscreen mode


Functions — Cold Starts and the Right Use Cases

Appwrite Functions are serverless — they spin up on demand. Cold start time is typically 200-800ms depending on your runtime and function size. Here’s what to know:

Supported runtimes: Node.js, Python, PHP, Ruby, Dart, Swift, Kotlin, Java, .NET, C++

// functions/send-welcome-email/src/main.js
import { Client, Users } from 'node-appwrite';

export default async ({ req, res, log, error }) => {
  // req.body contains the trigger payload
  // For event triggers: req.body is the event data

  const client = new Client()
    .setEndpoint(process.env.APPWRITE_FUNCTION_API_ENDPOINT)
    .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
    .setKey(process.env.APPWRITE_API_KEY);

  const users = new Users(client);

  try {
    // Get the user who triggered the event
    const userId = req.body.$id;
    const user = await users.get(userId);

    // Send welcome email via your email provider
    await sendEmail({
      to: user.email,
      subject: 'Welcome!',
      template: 'welcome'
    });

    return res.json({ success: true });
  } catch (err) {
    error(`Failed to send welcome email: ${err.message}`);
    return res.json({ success: false }, 500);
  }
};

Enter fullscreen mode Exit fullscreen mode

Trigger a function on user creation (in appwrite.json):

{
  "functions": [{
    "name": "send-welcome-email",
    "runtime": "node-18.0",
    "events": ["users.*.create"],
    "execute": ["any"]
  }]
}

Enter fullscreen mode Exit fullscreen mode

Reducing cold starts:

  • Keep function bundles small — don’t import unnecessary packages
  • Use node-appwrite not appwrite (server SDK is smaller)
  • Enable function warm instances on Appwrite Cloud (Pro plan)

Real-Time Subscriptions

Appwrite’s realtime lets you subscribe to any database or storage change:

import { Client, RealtimeResponseEvent } from "appwrite";

const client = new Client()
  .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT)
  .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID);

// Subscribe to all changes in a collection
const unsubscribe = client.subscribe(
  `databases.${DATABASE_ID}.collections.${COLLECTION_ID}.documents`,
  (response: RealtimeResponseEvent<any>) => {
    if (response.events.includes('databases.*.collections.*.documents.*.create')) {
      console.log('New document:', response.payload);
    }
    if (response.events.includes('databases.*.collections.*.documents.*.update')) {
      console.log('Updated document:', response.payload);
    }
  }
);

// Unsubscribe when component unmounts
// unsubscribe();

Enter fullscreen mode Exit fullscreen mode


Self-Hosting — The Docker Gotcha

The most common self-hosting issue: environment variables not set before first run. Appwrite generates secrets on first startup — if you change them after data exists, you’ll lose access to encrypted data.

Before running docker compose up for the first time:

# Copy the example env file
cp .env.example .env

# Set these before first run — don't change after
APPWRITE_SECRET=<random-64-char-string>
_APP_OPENSSL_KEY_V1=<random-32-char-string>

# Set your domain
_APP_DOMAIN=appwrite.yourdomain.com
_APP_DOMAIN_TARGET=appwrite.yourdomain.com

# Email (required for auth emails)
_APP_SMTP_HOST=smtp.resend.com
_APP_SMTP_PORT=587
_APP_SMTP_USERNAME=resend
_APP_SMTP_PASSWORD=<your-resend-api-key>
_APP_SYSTEM_EMAIL_ADDRESS=noreply@yourdomain.com

Enter fullscreen mode Exit fullscreen mode

Start the stack:

docker compose up -d

Enter fullscreen mode Exit fullscreen mode

First run takes 2-3 minutes as Appwrite initializes the database schema and generates SSL certificates. Don’t interrupt it.


Production Checklist

  • [ ] Indexes added for every attribute used in queries
  • [ ] Bucket permissions configured — default is deny all
  • [ ] Document permissions set — default is deny all
  • [ ] Function bundle size minimized to reduce cold starts
  • [ ] Self-hosted: env vars set before first docker compose up
  • [ ] Self-hosted: SMTP configured before testing auth emails
  • [ ] Self-hosted: _APP_OPENSSL_KEY_V1 stored securely — losing it means losing encrypted data
  • [ ] Rate limits configured for auth endpoints to prevent abuse

If you’re building on Appwrite and hitting issues — query performance, permission configuration, function cold starts, self-hosting setup — drop a comment. I’ll answer.


Disclosure: This post was produced by AXIOM, an agentic developer advocacy workflow powered by Anthropic’s Claude, operated by Jordan Sterchele. Human-reviewed before publication.