Communitygithub.com

agents-inc/skills — Appwrite バックエンドサービス

Appwrite のオープンソース BaaS — 認証、TablesDB、ストレージ、Functions、リアルタイム、パーミッションモデル、型付きクライアント

skills — Appwrite バックエンドサービス とは?

このスキルは、Appwrite をオープンソースのバックエンド・アズ・ア・サービス(BaaS)として活用するための包括的なパターン集です。認証(メール/パスワード、OAuth、マジックURL、電話OTP、匿名セッション)、TablesDB を使った構造化データの CRUD、ファイルストレージ、サーバーレス関数、リアルタイムサブスクリプションをカバーします。クライアント SDK(`appwrite`)とサーバー SDK(`node-appwrite`)の適切な使い分け、すべてのリソースに対する明示的なパーミッション設定の重要性、`AppwriteException` を try/catch で確実に処理する方法を強調しています。TablesDB を新しいプロジェクトの標準 API として推奨し、型安全なクエリと `ID.unique()` による自動 ID 生成を徹底します。Claude Code、Cursor、Codex などの AI エージェントが、Appwrite を利用する TypeScript プロジェクトで安全かつ効率的なコードを生成するための実践的なガイドです。

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

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Appwrite パターン

クイックガイド: Appwrite をオープンソースの BaaS として、認証、構造化データ(TablesDB)、ファイルストレージ、サーバーレス関数、リアルタイムサブスクリプションに活用しましょう。常に共有の Client インスタンスからサービスクラスを初期化し、すべての行に対して明示的にパーミッションを設定し(デフォルトでは何もアクセスできません)、サーバーサイドでは API キー認証を使用したサーバー SDK(node-appwrite)のみを使用してください。


<critical_requirements>

重要: このスキルを使用する前に

すべてのコードは CLAUDE.md のプロジェクト規約に従うこと(kebab-case、名前付きエクスポート、インポート順序、import type、名前付き定数)

(すべての行とファイルに対して明示的にパーミッションを設定する必要があります — Appwrite はデフォルトでアクセスを許可しないため、パーミッションを省略するとデータにアクセスできなくなります)

(サーバーサイドでは node-appwrite を API キー認証で使用し、クライアントサイドでは appwrite をセッション認証で使用する必要があります — クライアントバンドルに API キーを絶対に公開しないでください)

(すべての SDK 呼び出しで AppwriteException を常にチェックする必要があります — Appwrite は例外をスローし、{ data, error } タプルを返しません)

(自動生成 ID には必ず ID.unique() を使用してください — 生の文字列を渡すとカスタム 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、マジックURL、電話OTP、匿名セッション)を実装する場合
  • TablesDB テーブルで CRUD(行、クエリ、パーミッション)を実行する場合
  • アクセス制御付きのストレージバケットにファイルをアップロードおよび提供する場合
  • イベント、スケジュール、HTTP によってトリガーされるサーバーレス関数を構築する場合
  • 行、ファイル、またはアカウントイベントのリアルタイム変更を購読する場合
  • チームメンバーシップとロールベースのパーミッションを管理する場合

カバーされる主要パターン:

  • 型付きサービスクラスを使用したクライアントおよびサーバー SDK の初期化
  • 認証フロー: サインアップ、サインイン、OAuth、マジックURL、セッション管理
  • 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 結合は不可)
  • サーバーサイドのリアルタイムを必要とするアプリケーション(Appwrite Realtime はクライアント SDK のみ)

詳細リソース:

  • 決定フレームワークとアンチパターンについては、reference.md を参照してください

クライアントとクエリ:

  • examples/core.md — クライアントセットアップ、TablesDB CRUD、エラーハンドリング、パーミッション

認証:

  • examples/auth.md — 完全な認証フロー、OAuth、マジックURL、セッション、チーム

ストレージと関数:

  • examples/storage.md — ファイルのアップロード、ダウンロード、プレビュー、バケットパーミッション
  • examples/functions.md — サーバーレス関数、トリガー、関数内での SDK 使用法

リアルタイム:

  • examples/realtime.md — チャンネルサブスクリプション、イベントフィルタリング、クリーンアップ

哲学

Appwrite は、認証、データベース(TablesDB)、ファイルストレージ、サーバーレス関数、リアルタイムを統一された SDK を通じて提供するオープンソースのバックエンド・アズ・ア・サービスです。セルフホストまたは Appwrite Cloud 経由で使用できます。

コア原則:

  1. デフォルトでセキュア — 明示的なパーミッションがなければ何にもアクセスできません。すべての行とファイルにパーミッションを設定する必要があります。設定しないと、すべてのユーザーから見えなくなります。これは「デフォルトでオープン」の逆であり、必須のステップとして扱ってください。後回しにしてはいけません。
  2. サービスクラスアーキテクチャ — すべての SDK 操作は、共有の Client からインスタンス化されたサービスクラス(Account、TablesDB、Storage、Functions、Teams、Realtime)を介して行われます。このパターンはクライアント SDK とサーバー SDK で一貫しています。
  3. 2つのSDKの分割 — appwrite(クライアント)はブラウザ用のセッションベース認証を使用します。node-appwrite(サーバー)はバックエンド用の API キー認証を使用します。これらは同じ API 形状を共有しますが、認証メカニズムが異なります。決して混在させないでください。
  4. 例外、タプルではない — Appwrite は失敗時に AppwriteException をスローし、{ data, error } タプルは返しません。常に try/catch で呼び出しをラップしてください。
  5. TablesDB が未来 — Appwrite 1.8 で TablesDB(テーブル/行/列)が最新の API として導入されました。レガシーの Databases API(コレクション/ドキュメント/属性)は引き続き動作しますが、非推奨です。新機能は TablesDB にのみ追加されます。
  6. ID 生成 — サーバー生成の一意の ID には ID.unique() を使用してください。文字列を直接渡すとカスタム ID になります(テーブル内でグローバルに一意である必要があります)。

Appwrite を使用するタイミング:

  • データを完全に制御できるオープンソースのセルフホスト可能な BaaS
  • 認証、データベース、ストレージ、関数を1つのプラットフォームで必要とするプロジェクト
  • ベンダーロックインのないセルフホスト可能な 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);

なぜ良いか: API キー認証用の .setKey()(サーバー専用)、管理者ユーザー管理用の 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 用の ID.unique()、作成時の明示的なパーミッション設定、型安全なフィルタリング用の 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」ラベルを持つユーザー

なぜ良いか: すべてのリソースに対する明示的なパーミッション、ロールヘルパーは型安全、チームロールによりきめ細かなアクセス制御が可能

// 悪い例: パーミッションなしで行を作成
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";

// サインアップ: アカウント作成 + セッション作成(2ステップ)
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" });

なぜ良いか: 2ステップのサインアップ(アカウント作成 + セッション作成)、すべての SDK 呼び出しでオブジェクトパラメータ、現在のデバイスのみの deleteSession({ sessionId: "current" })

完全な認証フロー(OAuth、マジックURL、匿名セッション、チーム、パスワードリセットを含む)については、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({
  bucketId: BUCKET_ID,
  fileId: fileId,
  width: PREVIEW_WIDTH,
  height: PREVIEW_HEIGHT,
});

なぜ良いか: パーミッション付きのアップロード、クライアントサイドでの画像変換プレビュー、名前付き定数

詳細な例については examples/storage.md を参照してください。


パターン 8: サーバーレス関数

Appwrite Functions はイベント、スケジュール、HTTP リクエストでトリガーできます。

// 関数エントリポイント
export default async ({ req, res, log, error }) => {
  try {
    log("関数が呼び出されました");
    
    // リクエストデータにアクセス
    const { body, headers, method, path } = req;
    
    // レスポンスを返す
    return res.json({
      message: "成功",
      timestamp: new Date().toISOString(),
    });
  } catch (e) {
    error("関数エラー:", e);
    return res.json({ error: "内部エラー" }, 500);
  }
};

なぜ良いか: 関数に渡される req/res オブジェクト、ログ記録用の log/error、型安全な応答、エラーハンドリング

Appwrite SDK を関数内で使用するには、サーバー SDK を初期化します(パターン 2 を参照)。詳細な例については examples/functions.md を参照してください。


パターン 9: リアルタイムサブスクリプション

Realtime サービスと Channel ヘルパーを使用して、変更を購読します。

import { Channel } from "appwrite";

// テーブルの変更を購読
const unsubscribe = realtime.subscribe(
  [Channel.tablesdb("main", "todos")],
  (response) => {
    // response.events — イベントタイプの配列(例: ["tablesdb.main.todos.*.create"])
    // response.payload — 変更された行データ
    console.log("テーブル変更:", response.payload);
  }
);

// 後で購読解除
unsubscribe();

なぜ良いか: チャンネル構築用の Channel ヘルパー、購読解除用の unsubscribe 関数、型安全な応答

詳細な例については examples/realtime.md を参照してください。


パターン 10: 型安全なクエリ

Query クラスはテーブル操作のための型安全なクエリビルダーを提供します。

import { Query } from "appwrite";

// 等価フィルター
Query.equal("status", "active");

// 範囲フィルター
Query.lessThan("price", 100);
Query.greaterThan("price", 10);

// 並べ替え
Query.orderAsc("name");
Query.orderDesc("$createdAt");

// ページネーション
Query.limit(10);
Query.offset(20);

// カーソルベースのページネーション
Query.cursorAfter("lastDocumentId");
Query.cursorBefore("firstDocumentId");

// 複合クエリ
const results = await tablesDB.listRows({
  databaseId: DATABASE_ID,
  tableId: TABLE_ID,
  queries: [
    Query.equal("status", "active"),
    Query.greaterThan("price", 50),
    Query.orderDesc("price"),
    Query.limit(10),
  ],
});

なぜ良いか: 型安全なクエリメソッド、標準的な SQL に類似した構文、ページネーションと並べ替えのサポート、複合条件


パターン 11: 環境変数と設定

設定は常にコードから分離してください。

// lib/config.ts
export const config = {
  appwrite: {
    endpoint: process.env.APPWRITE_ENDPOINT || "https://cloud.appwrite.io/v1",
    projectId: process.env.APPWRITE_PROJECT_ID!,
    apiKey: process.env.APPWRITE_API_KEY, // サーバーサイドのみ
  },
  tables: {
    main: {
      databaseId: process.env.APPWRITE_MAIN_DATABASE_ID!,
      todos: process.env.APPWRITE_TODOS_TABLE_ID!,
      users: process.env.APPWRITE_USERS_TABLE_ID!,
    },
  },
  storage: {
    avatars: process.env.APPWRITE_AVATARS_BUCKET_ID!,
    uploads: process.env.APPWRITE_UPLOADS_BUCKET_ID!,
  },
} as const;

なぜ良いか: 一元管理された設定、環境変数から自動的に読み込み、変更防止用の as const、サーバーサイドのみの apiKey の明示的な分離

// 悪い例: コード全体に散らばった設定
const row = await tablesDB.createRow({
  databaseId: "main", // ハードコード
  tableId: "todos", // ハードコード
  // ...
});

なぜ悪いか: ハードコードされた値は環境間の切り替えを困難にし、リファクタリングを複雑にし、誤った値の使用リスクを高める


まとめ

これらのパターンは、Appwrite を TypeScript プロジェクトで使用するための安全でスケーラブルな基盤を提供します。主なポイント:

  1. 常にパーミッションを設定する — パーミッションのない行やファイルは事実上存在しません
  2. 正しい SDK を使用する — クライアントには appwrite、サーバーには node-appwrite
  3. 例外をキャッチする — すべての SDK 呼び出しを try/catch でラップする
  4. TablesDB を使用する — 新しいプロジェクトではレガシー API よりも優先する
  5. ID.unique() を使用する — サーバー生成の ID のため
  6. 設定をコードから分離する — 環境変数と一元管理された設定オブジェクトを使用する
  7. 型安全性を活用する — ジェネリック型と Models 名前空間を使用して型安全を保つ

詳細な実装例については、各サブディレクトリの examples/ ファイルを参照してください。


ライセンス

MIT

Individual skills in this repo

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

関連スキル