appwrite-typescript

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Appwrite TypeScript SDK

Appwrite TypeScript SDK

Installation

安装

bash
undefined
bash
undefined

Web

Web

npm install appwrite
npm install appwrite

React Native

React Native

npm install react-native-appwrite
npm install react-native-appwrite

Node.js / Deno

Node.js / Deno

npm install node-appwrite
undefined
npm install node-appwrite
undefined

Setting Up the Client

客户端配置

Client-side (Web / React Native)

客户端(Web / React Native)

typescript
// Web
import { Client, Account, TablesDB, Storage, ID, Query } from 'appwrite';

// React Native
import { Client, Account, TablesDB, Storage, ID, Query } from 'react-native-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');
typescript
// Web
import { Client, Account, TablesDB, Storage, ID, Query } from 'appwrite';

// React Native
import { Client, Account, TablesDB, Storage, ID, Query } from 'react-native-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');

Server-side (Node.js / Deno)

服务端(Node.js / Deno)

typescript
import { Client, Users, TablesDB, Storage, Functions, ID, Query } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject(process.env.APPWRITE_PROJECT_ID)
    .setKey(process.env.APPWRITE_API_KEY);
typescript
import { Client, Users, TablesDB, Storage, Functions, ID, Query } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject(process.env.APPWRITE_PROJECT_ID)
    .setKey(process.env.APPWRITE_API_KEY);

Code Examples

代码示例

Authentication (client-side)

客户端认证

typescript
const account = new Account(client);

// Email signup
await account.create({
    userId: ID.unique(),
    email: 'user@example.com',
    password: 'password123',
    name: 'User Name'
});

// Email login
const session = await account.createEmailPasswordSession({
    email: 'user@example.com',
    password: 'password123'
});

// OAuth login
account.createOAuth2Session({
    provider: 'github',
    success: 'https://example.com/success',
    failure: 'https://example.com/fail'
});

// Get current user
const user = await account.get();

// Logout
await account.deleteSession({ sessionId: 'current' });
typescript
const account = new Account(client);

// 邮箱注册
await account.create({
    userId: ID.unique(),
    email: 'user@example.com',
    password: 'password123',
    name: 'User Name'
});

// 邮箱登录
const session = await account.createEmailPasswordSession({
    email: 'user@example.com',
    password: 'password123'
});

// OAuth登录
account.createOAuth2Session({
    provider: 'github',
    success: 'https://example.com/success',
    failure: 'https://example.com/fail'
});

// 获取当前用户
const user = await account.get();

// 登出
await account.deleteSession({ sessionId: 'current' });

User Management (server-side)

服务端用户管理

typescript
const users = new Users(client);

// Create user
const user = await users.create({
    userId: ID.unique(),
    email: 'user@example.com',
    password: 'password123',
    name: 'User Name'
});

// List users
const list = await users.list({ queries: [Query.limit(25)] });

// Get user
const fetched = await users.get({ userId: '[USER_ID]' });

// Delete user
await users.delete({ userId: '[USER_ID]' });
typescript
const users = new Users(client);

// 创建用户
const user = await users.create({
    userId: ID.unique(),
    email: 'user@example.com',
    password: 'password123',
    name: 'User Name'
});

// 列出用户
const list = await users.list({ queries: [Query.limit(25)] });

// 获取用户信息
const fetched = await users.get({ userId: '[USER_ID]' });

// 删除用户
await users.delete({ userId: '[USER_ID]' });

Database Operations

数据库操作

Note: Use
TablesDB
(not the deprecated
Databases
class) for all new code. Only use
Databases
if the existing codebase already relies on it or the user explicitly requests it.
typescript
const tablesDB = new TablesDB(client);

// Create database (server-side only)
const db = await tablesDB.create({ databaseId: ID.unique(), name: 'My Database' });

// Create table (server-side only)
const col = await tablesDB.createTable({
    databaseId: '[DATABASE_ID]',
    tableId: ID.unique(),
    name: 'My Table'
});

// Create row
const doc = await tablesDB.createRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: ID.unique(),
    data: { title: 'Hello World', content: 'Example content' }
});

// List rows with query
const results = await tablesDB.listRows({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    queries: [Query.equal('status', 'active'), Query.limit(10)]
});

// Get row
const row = await tablesDB.getRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]'
});

// Update row
await tablesDB.updateRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]',
    data: { title: 'Updated Title' }
});

// Delete row
await tablesDB.deleteRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]'
});
注意: 所有新代码请使用
TablesDB
(而非已弃用的
Databases
类)。仅当现有代码库已依赖
Databases
或用户明确要求时,才使用
Databases
typescript
const tablesDB = new TablesDB(client);

// 创建数据库(仅服务端可用)
const db = await tablesDB.create({ databaseId: ID.unique(), name: 'My Database' });

// 创建数据表(仅服务端可用)
const col = await tablesDB.createTable({
    databaseId: '[DATABASE_ID]',
    tableId: ID.unique(),
    name: 'My Table'
});

// 创建数据行
const doc = await tablesDB.createRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: ID.unique(),
    data: { title: 'Hello World', content: 'Example content' }
});

// 带查询条件列出数据行
const results = await tablesDB.listRows({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    queries: [Query.equal('status', 'active'), Query.limit(10)]
});

// 获取数据行
const row = await tablesDB.getRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]'
});

// 更新数据行
await tablesDB.updateRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]',
    data: { title: 'Updated Title' }
});

// 删除数据行
await tablesDB.deleteRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: '[ROW_ID]'
});

TypeScript Generics

TypeScript 泛型

typescript
import { Models } from 'appwrite';
// Server-side: import from 'node-appwrite'

// Define a typed interface for your row data
interface Todo {
    title: string;
    done: boolean;
    priority: number;
}

// listRows returns Models.DocumentList<Models.Document> by default
// Cast or use generics for typed results
const results = await tablesDB.listRows({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    queries: [Query.equal('done', false)]
});

// Each document includes built-in fields alongside your data
const doc = results.documents[0];
doc.$id;            // string — unique row ID
doc.$createdAt;     // string — ISO 8601 creation timestamp
doc.$updatedAt;     // string — ISO 8601 update timestamp
doc.$permissions;   // string[] — permission strings
doc.$databaseId;    // string
doc.$collectionId;  // string

// Common model types
// Models.User<Preferences>  — user account
// Models.Session             — auth session
// Models.File                — storage file metadata
// Models.Team                — team object
// Models.Execution           — function execution result
// Models.DocumentList<T>     — paginated list with total count
typescript
import { Models } from 'appwrite';
// 服务端:从 'node-appwrite' 导入

// 为数据行定义类型化接口
interface Todo {
    title: string;
    done: boolean;
    priority: number;
}

// listRows 默认返回 Models.DocumentList<Models.Document>
// 使用类型转换或泛型获取类型化结果
const results = await tablesDB.listRows({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    queries: [Query.equal('done', false)]
});

// 每个文档包含内置字段和自定义数据
const doc = results.documents[0];
doc.$id;            // string — 唯一数据行ID
doc.$createdAt;     // string — ISO 8601 格式的创建时间戳
doc.$updatedAt;     // string — ISO 8601 格式的更新时间戳
doc.$permissions;   // string[] — 权限字符串数组
doc.$databaseId;    // string
doc.$collectionId;  // string

// 常见模型类型
// Models.User<Preferences>  — 用户账户
// Models.Session             — 认证会话
// Models.File                — 存储文件元数据
// Models.Team                — 团队对象
// Models.Execution           — 函数执行结果
// Models.DocumentList<T>     — 带总数的分页列表

Query Methods

查询方法

typescript
// Filtering
Query.equal('field', 'value')           // field == value (or pass array for IN)
Query.notEqual('field', 'value')        // field != value
Query.lessThan('field', 100)            // field < value
Query.lessThanEqual('field', 100)       // field <= value
Query.greaterThan('field', 100)         // field > value
Query.greaterThanEqual('field', 100)    // field >= value
Query.between('field', 1, 100)          // 1 <= field <= 100
Query.isNull('field')                   // field is null
Query.isNotNull('field')                // field is not null
Query.startsWith('field', 'prefix')     // string starts with prefix
Query.endsWith('field', 'suffix')       // string ends with suffix
Query.contains('field', 'substring')    // string/array contains value
Query.search('field', 'keywords')       // full-text search (requires full-text index)

// Sorting
Query.orderAsc('field')                 // sort ascending
Query.orderDesc('field')                // sort descending

// Pagination
Query.limit(25)                         // max rows returned (default 25, max 100)
Query.offset(0)                         // skip N rows
Query.cursorAfter('[ROW_ID]')           // paginate after this row ID (preferred for large datasets)
Query.cursorBefore('[ROW_ID]')          // paginate before this row ID

// Selection
Query.select(['field1', 'field2'])      // return only specified fields

// Logical
Query.or([Query.equal('a', 1), Query.equal('b', 2)])   // OR condition
Query.and([Query.greaterThan('age', 18), Query.lessThan('age', 65)])  // explicit AND (queries are AND by default)
typescript
// 过滤
Query.equal('field', 'value')           // 字段等于值(传入数组表示IN查询)
Query.notEqual('field', 'value')        // 字段不等于值
Query.lessThan('field', 100)            // 字段小于值
Query.lessThanEqual('field', 100)       // 字段小于等于值
Query.greaterThan('field', 100)         // 字段大于值
Query.greaterThanEqual('field', 100)    // 字段大于等于值
Query.between('field', 1, 100)          // 字段在1到100之间
Query.isNull('field')                   // 字段为null
Query.isNotNull('field')                // 字段不为null
Query.startsWith('field', 'prefix')     // 字符串以指定前缀开头
Query.endsWith('field', 'suffix')       // 字符串以指定后缀结尾
Query.contains('field', 'substring')    // 字符串/数组包含指定值
Query.search('field', 'keywords')       // 全文搜索(需先创建全文索引)

// 排序
Query.orderAsc('field')                 // 升序排序
Query.orderDesc('field')                // 降序排序

// 分页
Query.limit(25)                         // 返回最大行数(默认25,最大100)
Query.offset(0)                         // 跳过N行
Query.cursorAfter('[ROW_ID]')           // 从指定数据行ID后分页(适用于大数据集)
Query.cursorBefore('[ROW_ID]')          // 从指定数据行ID前分页

// 字段选择
Query.select(['field1', 'field2'])      // 仅返回指定字段

// 逻辑运算
Query.or([Query.equal('a', 1), Query.equal('b', 2)])   // 或条件
Query.and([Query.greaterThan('age', 18), Query.lessThan('age', 65)])  // 显式与条件(默认查询为与关系)

File Storage

文件存储

typescript
const storage = new Storage(client);

// Upload file (client-side — from file input)
const file = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: document.getElementById('file-input').files[0]
});

// Upload file (server-side — from path)
import { InputFile } from 'node-appwrite/file';

const file2 = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: InputFile.fromPath('/path/to/file.png', 'file.png')
});

// List files
const files = await storage.listFiles({ bucketId: '[BUCKET_ID]' });

// Get file preview (image)
const preview = storage.getFilePreview({
    bucketId: '[BUCKET_ID]',
    fileId: '[FILE_ID]',
    width: 300,
    height: 300
});

// Download file
const download = await storage.getFileDownload({
    bucketId: '[BUCKET_ID]',
    fileId: '[FILE_ID]'
});

// Delete file
await storage.deleteFile({ bucketId: '[BUCKET_ID]', fileId: '[FILE_ID]' });
typescript
const storage = new Storage(client);

// 上传文件(客户端 — 从文件输入框获取)
const file = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: document.getElementById('file-input').files[0]
});

// 上传文件(服务端 — 从路径获取)
import { InputFile } from 'node-appwrite/file';

const file2 = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: InputFile.fromPath('/path/to/file.png', 'file.png')
});

// 列出文件
const files = await storage.listFiles({ bucketId: '[BUCKET_ID]' });

// 获取文件预览图(图片)
const preview = storage.getFilePreview({
    bucketId: '[BUCKET_ID]',
    fileId: '[FILE_ID]',
    width: 300,
    height: 300
});

// 下载文件
const download = await storage.getFileDownload({
    bucketId: '[BUCKET_ID]',
    fileId: '[FILE_ID]'
});

// 删除文件
await storage.deleteFile({ bucketId: '[BUCKET_ID]', fileId: '[FILE_ID]' });

InputFile Factory Methods (server-side)

服务端InputFile工厂方法

typescript
import { InputFile } from 'node-appwrite/file';

InputFile.fromPath('/path/to/file.png', 'file.png')          // from filesystem path
InputFile.fromBuffer(buffer, 'file.png')                       // from Buffer
InputFile.fromStream(readableStream, 'file.png', size)         // from ReadableStream (size in bytes required)
InputFile.fromPlainText('Hello world', 'hello.txt')            // from string content
typescript
import { InputFile } from 'node-appwrite/file';

InputFile.fromPath('/path/to/file.png', 'file.png')          // 从文件系统路径创建
InputFile.fromBuffer(buffer, 'file.png')                       // 从Buffer创建
InputFile.fromStream(readableStream, 'file.png', size)         // 从可读流创建(需指定字节大小)
InputFile.fromPlainText('Hello world', 'hello.txt')            // 从字符串内容创建

Teams

团队管理

typescript
const teams = new Teams(client);

// Create team
const team = await teams.create({ teamId: ID.unique(), name: 'Engineering' });

// List teams
const list = await teams.list();

// Create membership (invite a user by email)
const membership = await teams.createMembership({
    teamId: '[TEAM_ID]',
    roles: ['editor'],
    email: 'user@example.com',
});

// List memberships
const members = await teams.listMemberships({ teamId: '[TEAM_ID]' });

// Update membership roles
await teams.updateMembership({
    teamId: '[TEAM_ID]',
    membershipId: '[MEMBERSHIP_ID]',
    roles: ['admin'],
});

// Delete team
await teams.delete({ teamId: '[TEAM_ID]' });
Role-based access: Use
Role.team('[TEAM_ID]')
for all team members or
Role.team('[TEAM_ID]', 'editor')
for a specific team role when setting permissions.
typescript
const teams = new Teams(client);

// 创建团队
const team = await teams.create({ teamId: ID.unique(), name: 'Engineering' });

// 列出团队
const list = await teams.list();

// 创建团队成员(通过邮箱邀请用户)
const membership = await teams.createMembership({
    teamId: '[TEAM_ID]',
    roles: ['editor'],
    email: 'user@example.com',
});

// 列出团队成员
const members = await teams.listMemberships({ teamId: '[TEAM_ID]' });

// 更新团队成员角色
await teams.updateMembership({
    teamId: '[TEAM_ID]',
    membershipId: '[MEMBERSHIP_ID]',
    roles: ['admin'],
});

// 删除团队
await teams.delete({ teamId: '[TEAM_ID]' });
基于角色的访问控制: 设置权限时,使用
Role.team('[TEAM_ID]')
表示所有团队成员,或
Role.team('[TEAM_ID]', 'editor')
表示团队中的特定角色。

Real-time Subscriptions (client-side)

客户端实时订阅

typescript
// Subscribe to row changes
const unsubscribe = client.subscribe('databases.[DATABASE_ID].tables.[TABLE_ID].rows', (response) => {
    console.log(response.events);   // e.g. ['databases.*.tables.*.rows.*.create']
    console.log(response.payload);  // the affected resource
});

// Subscribe to file changes
client.subscribe('buckets.[BUCKET_ID].files', (response) => {
    console.log(response.payload);
});

// Subscribe to multiple channels
client.subscribe([
    'databases.[DATABASE_ID].tables.[TABLE_ID].rows',
    'buckets.[BUCKET_ID].files',
], (response) => { /* ... */ });

// Unsubscribe
unsubscribe();
Available channels:
ChannelDescription
account
Changes to the authenticated user's account
databases.[DB_ID].tables.[TABLE_ID].rows
All rows in a table
databases.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]
A specific row
buckets.[BUCKET_ID].files
All files in a bucket
buckets.[BUCKET_ID].files.[FILE_ID]
A specific file
teams
Changes to teams the user belongs to
teams.[TEAM_ID]
Changes to a specific team
memberships
Changes to the user's team memberships
memberships.[MEMBERSHIP_ID]
A specific membership
functions.[FUNCTION_ID].executions
Execution updates for a function
The
response
object includes:
events
(array of event strings),
payload
(the affected resource),
channels
(channels matched), and
timestamp
(ISO 8601).
typescript
// 订阅数据行变更
const unsubscribe = client.subscribe('databases.[DATABASE_ID].tables.[TABLE_ID].rows', (response) => {
    console.log(response.events);   // 例如:['databases.*.tables.*.rows.*.create']
    console.log(response.payload);  // 受影响的资源
});

// 订阅文件变更
client.subscribe('buckets.[BUCKET_ID].files', (response) => {
    console.log(response.payload);
});

// 订阅多个频道
client.subscribe([
    'databases.[DATABASE_ID].tables.[TABLE_ID].rows',
    'buckets.[BUCKET_ID].files',
], (response) => { /* ... */ });

// 取消订阅
unsubscribe();
可用频道:
频道描述
account
已认证用户的账户变更
databases.[DB_ID].tables.[TABLE_ID].rows
数据表中的所有数据行
databases.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID]
特定数据行
buckets.[BUCKET_ID].files
存储桶中的所有文件
buckets.[BUCKET_ID].files.[FILE_ID]
特定文件
teams
用户所属团队的变更
teams.[TEAM_ID]
特定团队的变更
memberships
用户的团队成员身份变更
memberships.[MEMBERSHIP_ID]
特定团队成员身份
functions.[FUNCTION_ID].executions
函数执行更新
response
对象包含:
events
(事件字符串数组)、
payload
(受影响的资源)、
channels
(匹配的频道)和
timestamp
(ISO 8601格式时间戳)。

Serverless Functions (server-side)

服务端无服务器函数

typescript
const functions = new Functions(client);

// Execute function
const execution = await functions.createExecution({
    functionId: '[FUNCTION_ID]',
    body: JSON.stringify({ key: 'value' })
});

// List executions
const executions = await functions.listExecutions({ functionId: '[FUNCTION_ID]' });
typescript
const functions = new Functions(client);

// 执行函数
const execution = await functions.createExecution({
    functionId: '[FUNCTION_ID]',
    body: JSON.stringify({ key: 'value' })
});

// 列出执行记录
const executions = await functions.listExecutions({ functionId: '[FUNCTION_ID]' });

Writing a Function Handler (Node.js runtime)

编写函数处理器(Node.js运行时)

When deploying your own Appwrite Function, the entry point file must export a default async function:
typescript
// src/main.js (or src/main.ts)
export default async ({ req, res, log, error }) => {
    // Request properties
    // req.body        — raw request body (string)
    // req.bodyJson    — parsed JSON body (object, or undefined if not JSON)
    // req.headers     — request headers (object)
    // req.method      — HTTP method (GET, POST, PUT, DELETE, PATCH)
    // req.path        — URL path (e.g. '/hello')
    // req.query       — parsed query parameters (object)
    // req.queryString — raw query string

    log('Processing request: ' + req.method + ' ' + req.path);

    if (req.method === 'GET') {
        return res.json({ message: 'Hello from Appwrite Function!' });
    }

    const data = req.bodyJson;
    if (!data?.name) {
        error('Missing name field');
        return res.json({ error: 'Name is required' }, 400);
    }

    // Response methods
    return res.json({ success: true });                    // JSON (sets Content-Type automatically)
    // return res.text('Hello');                           // plain text
    // return res.empty();                                 // 204 No Content
    // return res.redirect('https://example.com');         // 302 Redirect
    // return res.send('data', 200, { 'X-Custom': '1' }); // custom body, status, headers
};
部署自定义Appwrite函数时,入口文件必须导出一个默认的异步函数:
typescript
// src/main.js(或src/main.ts)
export default async ({ req, res, log, error }) => {
    // 请求属性
    // req.body        — 原始请求体(字符串)
    // req.bodyJson    — 解析后的JSON请求体(对象,若不是JSON则为undefined)
    // req.headers     — 请求头(对象)
    // req.method      — HTTP方法(GET, POST, PUT, DELETE, PATCH)
    // req.path        — URL路径(例如 '/hello')
    // req.query       — 解析后的查询参数(对象)
    // req.queryString — 原始查询字符串

    log('Processing request: ' + req.method + ' ' + req.path);

    if (req.method === 'GET') {
        return res.json({ message: 'Hello from Appwrite Function!' });
    }

    const data = req.bodyJson;
    if (!data?.name) {
        error('Missing name field');
        return res.json({ error: 'Name is required' }, 400);
    }

    // 响应方法
    return res.json({ success: true });                    // JSON响应(自动设置Content-Type)
    // return res.text('Hello');                           // 纯文本响应
    // return res.empty();                                 // 204 No Content响应
    // return res.redirect('https://example.com');         // 302重定向
    // return res.send('data', 200, { 'X-Custom': '1' }); // 自定义响应体、状态码和请求头
};

Server-Side Rendering (SSR) Authentication

服务端渲染(SSR)认证

SSR apps (Next.js, SvelteKit, Nuxt, Remix, Astro) use the server SDK (
node-appwrite
) to handle auth. You need two clients:
  • Admin client — uses an API key, creates sessions, bypasses rate limits (reusable singleton)
  • Session client — uses a session cookie, acts on behalf of a user (create per-request, never share)
typescript
import { Client, Account, OAuthProvider } from 'node-appwrite';

// Admin client (reusable)
const adminClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]')
    .setKey(process.env.APPWRITE_API_KEY);

// Session client (create per-request)
const sessionClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');

const session = req.cookies['a_session_[PROJECT_ID]'];
if (session) {
    sessionClient.setSession(session);
}
SSR应用(Next.js、SvelteKit、Nuxt、Remix、Astro)使用服务端SDK
node-appwrite
)处理认证。你需要两个客户端:
  • 管理员客户端 — 使用API密钥,可创建会话,绕过速率限制(可复用单例)
  • 会话客户端 — 使用会话Cookie,代表用户执行操作(每个请求创建一次,请勿共享)
typescript
import { Client, Account, OAuthProvider } from 'node-appwrite';

// 管理员客户端(可复用)
const adminClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]')
    .setKey(process.env.APPWRITE_API_KEY);

// 会话客户端(每个请求创建一次)
const sessionClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('[PROJECT_ID]');

const session = req.cookies['a_session_[PROJECT_ID]'];
if (session) {
    sessionClient.setSession(session);
}

Email/Password Login

邮箱/密码登录

typescript
app.post('/login', async (req, res) => {
    const account = new Account(adminClient);
    const session = await account.createEmailPasswordSession({
        email: req.body.email,
        password: req.body.password,
    });

    // Cookie name must be a_session_<PROJECT_ID>
    res.cookie('a_session_[PROJECT_ID]', session.secret, {
        httpOnly: true,
        secure: true,
        sameSite: 'strict',
        expires: new Date(session.expire),
        path: '/',
    });

    res.json({ success: true });
});
typescript
app.post('/login', async (req, res) => {
    const account = new Account(adminClient);
    const session = await account.createEmailPasswordSession({
        email: req.body.email,
        password: req.body.password,
    });

    // Cookie名称必须为a_session_<PROJECT_ID>
    res.cookie('a_session_[PROJECT_ID]', session.secret, {
        httpOnly: true,
        secure: true,
        sameSite: 'strict',
        expires: new Date(session.expire),
        path: '/',
    });

    res.json({ success: true });
});

Authenticated Requests

认证请求处理

typescript
app.get('/user', async (req, res) => {
    const session = req.cookies['a_session_[PROJECT_ID]'];
    if (!session) return res.status(401).json({ error: 'Unauthorized' });

    // Create a fresh session client per request
    const sessionClient = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('[PROJECT_ID]')
        .setSession(session);

    const account = new Account(sessionClient);
    const user = await account.get();
    res.json(user);
});
typescript
app.get('/user', async (req, res) => {
    const session = req.cookies['a_session_[PROJECT_ID]'];
    if (!session) return res.status(401).json({ error: '未授权' });

    // 为每个请求创建新的会话客户端
    const sessionClient = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('[PROJECT_ID]')
        .setSession(session);

    const account = new Account(sessionClient);
    const user = await account.get();
    res.json(user);
});

OAuth2 SSR Flow

OAuth2 SSR流程

typescript
// Step 1: Redirect to OAuth provider
app.get('/oauth', async (req, res) => {
    const account = new Account(adminClient);
    const redirectUrl = await account.createOAuth2Token({
        provider: OAuthProvider.Github,
        success: 'https://example.com/oauth/success',
        failure: 'https://example.com/oauth/failure',
    });
    res.redirect(redirectUrl);
});

// Step 2: Handle callback — exchange token for session
app.get('/oauth/success', async (req, res) => {
    const account = new Account(adminClient);
    const session = await account.createSession({
        userId: req.query.userId,
        secret: req.query.secret,
    });

    res.cookie('a_session_[PROJECT_ID]', session.secret, {
        httpOnly: true, secure: true, sameSite: 'strict',
        expires: new Date(session.expire), path: '/',
    });
    res.json({ success: true });
});
Cookie security: Always use
httpOnly
,
secure
, and
sameSite: 'strict'
to prevent XSS. The cookie name must be
a_session_<PROJECT_ID>
.
Forwarding user agent: Call
sessionClient.setForwardedUserAgent(req.headers['user-agent'])
to record the end-user's browser info for debugging and security.
typescript
// 步骤1:重定向到OAuth提供商
app.get('/oauth', async (req, res) => {
    const account = new Account(adminClient);
    const redirectUrl = await account.createOAuth2Token({
        provider: OAuthProvider.Github,
        success: 'https://example.com/oauth/success',
        failure: 'https://example.com/oauth/failure',
    });
    res.redirect(redirectUrl);
});

// 步骤2:处理回调 — 交换令牌获取会话
app.get('/oauth/success', async (req, res) => {
    const account = new Account(adminClient);
    const session = await account.createSession({
        userId: req.query.userId,
        secret: req.query.secret,
    });

    res.cookie('a_session_[PROJECT_ID]', session.secret, {
        httpOnly: true, secure: true, sameSite: 'strict',
        expires: new Date(session.expire), path: '/',
    });
    res.json({ success: true });
});
Cookie安全: 始终使用
httpOnly
secure
sameSite: 'strict'
以防止XSS攻击。Cookie名称必须为
a_session_<PROJECT_ID>
转发用户代理: 调用
sessionClient.setForwardedUserAgent(req.headers['user-agent'])
记录终端用户的浏览器信息,用于调试和安全分析。

Error Handling

错误处理

typescript
import { AppwriteException } from 'appwrite';
// Server-side: import from 'node-appwrite'

try {
    const doc = await tablesDB.getRow({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        rowId: '[ROW_ID]',
    });
} catch (err) {
    if (err instanceof AppwriteException) {
        console.log(err.message);   // human-readable error message
        console.log(err.code);      // HTTP status code (number)
        console.log(err.type);      // Appwrite error type string (e.g. 'document_not_found')
        console.log(err.response);  // full response body (object)
    }
}
Common error codes:
CodeMeaning
401
Unauthorized — missing or invalid session/API key
403
Forbidden — insufficient permissions for this action
404
Not found — resource does not exist
409
Conflict — duplicate ID or unique constraint violation
429
Rate limited — too many requests, retry after backoff
typescript
import { AppwriteException } from 'appwrite';
// 服务端:从 'node-appwrite' 导入

try {
    const doc = await tablesDB.getRow({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]',
        rowId: '[ROW_ID]',
    });
} catch (err) {
    if (err instanceof AppwriteException) {
        console.log(err.message);   // 人类可读的错误信息
        console.log(err.code);      // HTTP状态码(数字)
        console.log(err.type);      // Appwrite错误类型字符串(例如 'document_not_found')
        console.log(err.response);  // 完整响应体(对象)
    }
}
常见错误码:
状态码含义
401
未授权 — 缺少或无效的会话/API密钥
403
禁止访问 — 权限不足,无法执行该操作
404
未找到 — 资源不存在
409
冲突 — 重复ID或唯一约束违反
429
请求超限 — 请求次数过多,请稍后重试

Permissions & Roles (Critical)

权限与角色(重点)

Appwrite uses permission strings to control access to resources. Each permission pairs an action (
read
,
update
,
delete
,
create
, or
write
which grants create + update + delete) with a role target. By default, no user has access unless permissions are explicitly set at the document/file level or inherited from the collection/bucket settings. Permissions are arrays of strings built with the
Permission
and
Role
helpers.
typescript
import { Permission, Role } from 'appwrite';
// Server-side: import from 'node-appwrite'
Appwrite使用权限字符串控制资源访问。每个权限由操作(
read
update
delete
create
,或
write
,包含create+update+delete)和角色目标组成。默认情况下,所有用户均无访问权限,除非在文档/文件级别显式设置权限,或从集合/存储桶设置继承权限。权限是使用
Permission
Role
助手构建的字符串数组。
typescript
import { Permission, Role } from 'appwrite';
// 服务端:从 'node-appwrite' 导入

Database Row with Permissions

带权限的数据库行

typescript
const doc = await tablesDB.createRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: ID.unique(),
    data: { title: 'Hello World' },
    permissions: [
        Permission.read(Role.user('[USER_ID]')),     // specific user can read
        Permission.update(Role.user('[USER_ID]')),   // specific user can update
        Permission.read(Role.team('[TEAM_ID]')),     // all team members can read
        Permission.read(Role.any()),                 // anyone (including guests) can read
    ]
});
typescript
const doc = await tablesDB.createRow({
    databaseId: '[DATABASE_ID]',
    tableId: '[TABLE_ID]',
    rowId: ID.unique(),
    data: { title: 'Hello World' },
    permissions: [
        Permission.read(Role.user('[USER_ID]')),     // 指定用户可读取
        Permission.update(Role.user('[USER_ID]')),   // 指定用户可更新
        Permission.read(Role.team('[TEAM_ID]')),     // 所有团队成员可读取
        Permission.read(Role.any()),                 // 任何人(包括访客)可读取
    ]
});

File Upload with Permissions

带权限的文件上传

typescript
const file = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: document.getElementById('file-input').files[0],
    permissions: [
        Permission.read(Role.any()),
        Permission.update(Role.user('[USER_ID]')),
        Permission.delete(Role.user('[USER_ID]')),
    ]
});
When to set permissions: Set document/file-level permissions when you need per-resource access control. If all documents in a collection share the same rules, configure permissions at the collection/bucket level and leave document permissions empty.
Common mistakes:
  • Forgetting permissions — the resource becomes inaccessible to all users (including the creator)
  • Role.any()
    with
    write
    /
    update
    /
    delete
    — allows any user, including unauthenticated guests, to modify or remove the resource
  • Permission.read(Role.any())
    on sensitive data
    — makes the resource publicly readable
typescript
const file = await storage.createFile({
    bucketId: '[BUCKET_ID]',
    fileId: ID.unique(),
    file: document.getElementById('file-input').files[0],
    permissions: [
        Permission.read(Role.any()),
        Permission.update(Role.user('[USER_ID]')),
        Permission.delete(Role.user('[USER_ID]')),
    ]
});
权限设置时机: 当需要按资源粒度控制访问时,设置文档/文件级权限。如果集合中的所有文档共享相同规则,请在集合/存储桶级别配置权限,并留空文档权限。
常见错误:
  • 忘记设置权限 — 资源对所有用户(包括创建者)不可访问
  • write
    /
    update
    /
    delete
    使用
    Role.any()
    — 允许任何用户(包括未认证访客)修改或删除资源
  • 对敏感数据使用
    Permission.read(Role.any())
    — 使资源公开可读