appwrite-typescript
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAppwrite TypeScript SDK
Appwrite TypeScript SDK
Installation
安装
bash
undefinedbash
undefinedWeb
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
undefinednpm install node-appwrite
undefinedSetting 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(not the deprecatedTablesDBclass) for all new code. Only useDatabasesif the existing codebase already relies on it or the user explicitly requests it.Databases
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 counttypescript
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 contenttypescript
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: Usefor all team members orRole.team('[TEAM_ID]')for a specific team role when setting permissions.Role.team('[TEAM_ID]', 'editor')
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:
| Channel | Description |
|---|---|
| Changes to the authenticated user's account |
| All rows in a table |
| A specific row |
| All files in a bucket |
| A specific file |
| Changes to teams the user belongs to |
| Changes to a specific team |
| Changes to the user's team memberships |
| A specific membership |
| Execution updates for a function |
The object includes: (array of event strings), (the affected resource), (channels matched), and (ISO 8601).
responseeventspayloadchannelstimestamptypescript
// 订阅数据行变更
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();可用频道:
| 频道 | 描述 |
|---|---|
| 已认证用户的账户变更 |
| 数据表中的所有数据行 |
| 特定数据行 |
| 存储桶中的所有文件 |
| 特定文件 |
| 用户所属团队的变更 |
| 特定团队的变更 |
| 用户的团队成员身份变更 |
| 特定团队成员身份 |
| 函数执行更新 |
responseeventspayloadchannelstimestampServerless 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 () to handle auth. You need two clients:
node-appwrite- 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, andsecureto prevent XSS. The cookie name must besameSite: 'strict'.a_session_<PROJECT_ID>
Forwarding user agent: Callto record the end-user's browser info for debugging and security.sessionClient.setForwardedUserAgent(req.headers['user-agent'])
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以防止XSS攻击。Cookie名称必须为sameSite: 'strict'。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:
| Code | Meaning |
|---|---|
| Unauthorized — missing or invalid session/API key |
| Forbidden — insufficient permissions for this action |
| Not found — resource does not exist |
| Conflict — duplicate ID or unique constraint violation |
| 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); // 完整响应体(对象)
}
}常见错误码:
| 状态码 | 含义 |
|---|---|
| 未授权 — 缺少或无效的会话/API密钥 |
| 禁止访问 — 权限不足,无法执行该操作 |
| 未找到 — 资源不存在 |
| 冲突 — 重复ID或唯一约束违反 |
| 请求超限 — 请求次数过多,请稍后重试 |
Permissions & Roles (Critical)
权限与角色(重点)
Appwrite uses permission strings to control access to resources. Each permission pairs an action (, , , , or 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 and helpers.
readupdatedeletecreatewritePermissionRoletypescript
import { Permission, Role } from 'appwrite';
// Server-side: import from 'node-appwrite'Appwrite使用权限字符串控制资源访问。每个权限由操作(、、、,或,包含create+update+delete)和角色目标组成。默认情况下,所有用户均无访问权限,除非在文档/文件级别显式设置权限,或从集合/存储桶设置继承权限。权限是使用和助手构建的字符串数组。
readupdatedeletecreatewritePermissionRoletypescript
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)
withRole.any()/write/update— allows any user, including unauthenticated guests, to modify or remove the resourcedelete on sensitive data — makes the resource publicly readablePermission.read(Role.any())
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())