Communitygithub.com

agents-inc/skills

Appwrite 後端即服務 — 認證、TablesDB、儲存、函式、即時通訊、權限模型、型別客戶端

skills 是什麼?

此技能提供 Appwrite 這個開源 BaaS 平台的操作模式,涵蓋認證、資料庫 (TablesDB)、檔案儲存、無伺服器函式與即時通訊等核心功能。它強調**安全預設**原則:每個資料列與檔案都必須明確設定權限,否則預設不開放任何存取。開發者需區分客戶端 (`appwrite`) 與伺服器端 (`node-appwrite`) SDK,並使用 API 金鑰進行後端認證。技能文件提供了完整的 TypeScript 範例,包括客戶端/伺服器初始化、增刪改查 (CRUD)、錯誤處理 (AppwriteException)、權限模型 (Permission + Role)、認證流程與即時訂閱,幫助開發者避免常見陷阱(如漏設權限或混淆 SDK)。支援平台包括 Appwrite Cloud 與自託管版本。

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

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Appwrite 模式

快速指南: 使用 Appwrite 作為開源 BaaS,處理認證、結構化資料 (TablesDB)、檔案儲存、無伺服器函式與即時訂閱。務必從共用的 Client 實例初始化服務類別、明確設定每個資料列與檔案的權限(預設無任何存取權限),並且只在後端使用 API 金鑰認證的伺服器 SDK (node-appwrite)。


<critical_requirements>

關鍵:使用此技能前

所有程式碼必須遵循 CLAUDE.md 中的專案慣例(kebab-case、具名匯出、匯入順序、import type、具名常數)

(你必須在每個資料列與檔案上明確設定權限——Appwrite 預設不授予任何存取權限,省略權限會使資料無法存取)

(你必須在伺服器端使用 node-appwrite 搭配 API 金鑰認證,在客戶端使用 appwrite 搭配 Session 認證——絕不要在客戶端套件中暴露 API 金鑰)

(你必須在每次 SDK 呼叫時檢查 AppwriteException——Appwrite 會拋出例外,不會回傳 { data, error } 元組)

(你必須使用 ID.unique() 來產生自動 ID——傳入原始字串會建立自訂 ID,而非自動產生的 ID)

(你必須對新專案使用 TablesDB API——舊版 Databases/collections/documents API 已棄用,僅接收安全性修補)

</critical_requirements>


自動偵測: Appwrite, appwrite, node-appwrite, TablesDB, Account, Databases, Permission, Role, ID.unique, Query.equal, createEmailPasswordSession, realtime.subscribe, Channel.files, Channel.tablesdb, APPWRITE_FUNCTION

使用時機:

  • 使用 TypeScript 設定 Appwrite 客戶端或伺服器 SDK
  • 實作認證(電子郵件/密碼、OAuth、魔術連結、手機 OTP、匿名 Session)
  • 對 TablesDB 表格執行 CRUD 操作(資料列、查詢、權限)
  • 上傳並提供來自 Storage 儲存桶的檔案,並搭配存取控制
  • 建立由事件、排程或 HTTP 觸發的無伺服器函式
  • 訂閱資料列、檔案或帳戶事件的即時變更
  • 管理團隊成員資格與角色型權限

涵蓋的主要模式:

  • 使用型別服務類別的客戶端與伺服器 SDK 初始化
  • 認證流程:註冊、登入、OAuth、魔術連結、Session 管理
  • 使用 Query 類別進行篩選、分頁、排序的 TablesDB 操作
  • 權限模型:Permission.read(Role.any())、Role.user()、Role.team()
  • 儲存:上傳、下載、預覽(含圖片轉換)、儲存桶權限
  • 無伺服器函式:export default async ({ req, res, log, error }) => {}
  • 透過 Channel 輔助函式與 realtime.subscribe() 進行即時訂閱

不應使用的情況:

  • 直接資料庫連線或 SQL 查詢(Appwrite 僅提供 API,無原始 SQL)
  • 跨多個表格的複雜關聯式聯結(Appwrite 支援關聯,但不支援任意的 SQL JOIN)
  • 需要伺服器端即時通訊的應用程式(Appwrite Realtime 僅限客戶端 SDK)

詳細資源:

客戶端與查詢:

認證:

  • examples/auth.md — 完整的認證流程、OAuth、魔術連結、Session、團隊

儲存與函式:

即時通訊:


理念

Appwrite 是一個開源後端即服務平台,透過統一的 SDK 提供認證、資料庫 (TablesDB)、檔案儲存、無伺服器函式與即時通訊。它可以自託管或使用 Appwrite Cloud。

核心原則:

  1. 預設安全 — 未經明確權限,任何內容都無法存取。每個資料列與檔案都必須設定權限,否則對所有使用者都不可見。這與「預設開放」相反——請將其視為強制步驟,而非事後補救。
  2. 服務類別架構 — 所有 SDK 互動都透過從共用 Client 實例化的服務類別(Account、TablesDB、Storage、Functions、Teams、Realtime)進行。此模式在客戶端與伺服器 SDK 中保持一致。
  3. 兩種 SDK 拆分 — appwrite(客戶端)對瀏覽器使用 Session 認證。node-appwrite(伺服器)對後端使用 API 金鑰認證。它們共享相同的 API 形狀,但認證機制不同。切勿混用。
  4. 例外,而非元組 — Appwrite 在失敗時會拋出 AppwriteException,而非 { data, error } 元組。務必將呼叫包在 try/catch 中。
  5. TablesDB 是未來 — Appwrite 1.8 引入了 TablesDB(表格/資料列/欄位)作為現代 API。舊版 Databases API(集合/文件/屬性)仍然可用但已棄用。新功能只會出現在 TablesDB 中。
  6. ID 產生 — 使用 ID.unique() 產生伺服器端獨特 ID。如果直接傳入字串,它會變成自訂 ID(必須在表格內全域唯一)。

何時使用 Appwrite:

  • 開源、可自託管的 BaaS,可完全控制資料
  • 需要在一個平台中整合認證、資料庫、儲存與函式的專案
  • 團隊需要可自託管且無供應商鎖定的 BaaS
  • 受益於細粒度文件/資料列級權限的應用程式

不應使用的情況:

  • 需要聯結、檢視與預存程序的複雜 SQL 應用程式
  • 需要伺服器端即時通訊的消費者應用(Appwrite Realtime 僅限客戶端 SDK——無伺服器端 SDK 支援)
  • 需要本機優先同步的離線優先應用程式(Appwrite 無內建離線同步功能)

核心模式

模式 1:客戶端 SDK 設定 (瀏覽器)

使用端點與專案 ID 初始化 Appwrite 客戶端。所有服務類別共享一個 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);

為何良好: 單一客戶端實例在所有服務間共享、設定的具名常數、每個服務的具名匯出、環境變數將設定與程式碼分離

// 錯誤:硬編碼設定、重新建立客戶端
import { Client, Account } from "appwrite";

const account = new Account(
  new Client()
    .setEndpoint("https://cloud.appwrite.io/v1") // 硬編碼
    .setProject("abc123"), // 硬編碼專案 ID
);

為何錯誤: 硬編碼的端點與專案 ID、每個服務建立新 Client 浪費資源、無共用設定


模式 2:伺服器 SDK 設定 (Node.js)

伺服器 SDK 使用 API 金鑰認證——切勿在客戶端程式碼中暴露 API 金鑰。

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

為何良好: 使用 .setKey() 進行 API 金鑰認證(僅限伺服器)、Users 服務提供管理員使用者管理(客戶端 SDK 中不可用)、具名常數

使用時機: API 路由、無伺服器函式、管理腳本、Webhook。絕不要從客戶端程式碼匯入此模組。


模式 3:錯誤處理 (AppwriteException)

Appwrite 會拋出 AppwriteException——它不會回傳 { data, error } 元組。務必使用 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 — 人類可讀的訊息
      // error.code — HTTP 狀態碼 (401, 404, 等)
      // error.type — 機器可讀的錯誤類型字串
      throw new Error(`Appwrite 錯誤 ${error.code}: ${error.message}`);
    }
    throw error;
  }
}

為何良好: 型別收窄的 AppwriteException 提供了 code、message、type;重新拋出未知錯誤;使用 Models 命名空間明確指定回傳型別

// 錯誤:假設回傳 { data, error } 元組
const { data, error } = await account.get(); // 錯誤——Appwrite 會拋出例外,而非回傳元組
if (error) {
  /* ... */
}

為何錯誤: Appwrite 不會回傳錯誤元組——這段程式碼會靜默失敗,因為 account.get() 要不是回傳資料,就是拋出例外


模式 4:TablesDB 操作 (CRUD)

使用 TablesDB 進行所有資料庫操作。建立時需要資料列 ID,請使用 ID.unique() 進行自動產生。

建立一個資料列

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

使用查詢列出資料列

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 — Todo 資料列的陣列
// result.total — 符合的總數

更新一個資料列

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

刪除一個資料列

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

為何良好: 資料庫/表格 ID 使用具名常數、使用 ID.unique() 產生自動 ID、建立時明確設定權限、使用 Query 類別進行型別安全的篩選、使用泛型型別參數指定資料列形狀


模式 5:權限模型

Appwrite 預設不授予任何存取權限。你必須為每個資料列與檔案設定權限。

Permission + Role 語法

import { Permission, Role } from "appwrite";

// 公開讀取,擁有者完整存取
const publicReadPermissions = [
  Permission.read(Role.any()),
  Permission.update(Role.user(userId)),
  Permission.delete(Role.user(userId)),
];

// 團隊存取
const teamPermissions = [
  Permission.read(Role.team(teamId)),
  Permission.update(Role.team(teamId, "admin")),
  Permission.delete(Role.team(teamId, "owner")),
];

// 僅限已驗證使用者
const verifiedOnlyPermissions = [Permission.read(Role.users("verified"))];

常用 Role 輔助函式

Role.any(); // 任何人(包括訪客)
Role.guests(); // 僅未認證使用者
Role.users(); // 所有已認證使用者
Role.users("verified"); // 僅已驗證使用者
Role.user(userId); // 特定使用者(依 ID)
Role.team(teamId); // 團隊的所有成員
Role.team(teamId, "admin"); // 具有 "admin" 角色的團隊成員
Role.member(membershipId); // 特定的團隊成員資格
Role.label("premium"); // 具有 "premium" 標籤的使用者

為何良好: 每個資源都明確設定權限、Role 輔助函式是型別安全的、團隊角色可實現細粒度存取

// 錯誤:建立資料列時未設定權限
const row = await tablesDB.createRow({
  databaseId: DATABASE_ID,
  tableId: TABLE_ID,
  rowId: ID.unique(),
  data: { title: "秘密" },
  // 未設定權限——此資料列對所有人都不可見!
});

為何錯誤: 沒有權限,資料列存在於資料庫中,但沒有使用者可以讀取、更新或刪除它(連建立者也不行)——這是最常見的 Appwrite 錯誤


模式 6:認證流程

使用 Account 服務進行所有認證操作。

電子郵件/密碼註冊與登入

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

// 註冊:建立帳戶 + 建立 Session(兩個步驟)
const user = await account.create({
  userId: ID.unique(),
  email,
  password,
  name,
});
const session = await account.createEmailPasswordSession({ email, password });

// 登入
const session = await account.createEmailPasswordSession({ email, password });

// 登出(目前裝置)
await account.deleteSession({ sessionId: "current" });

為何良好: 兩步驟註冊(建立帳戶 + 建立 Session)、所有 SDK 呼叫使用物件參數、使用 deleteSession({ sessionId: "current" }) 僅登出目前裝置

完整認證流程(包括 OAuth、魔術連結、匿名 Session、團隊與密碼重設)請參閱 examples/auth.md。


模式 7:儲存操作

使用 Storage 服務上傳、下載與預覽檔案。

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

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

// 上傳並設定權限
await storage.createFile({
  bucketId: BUCKET_ID,
  fileId: ID.unique(),
  file,
  permissions: [
    Permission.read(Role.user(userId)),
    Permission.update(Role.user(userId)),
  ],
});

// 含轉換的圖片預覽
const url = storage.getFilePreview({

…

[為簡潔起見已截斷]

Individual skills in this repo

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

相關技能