convex について
Convex と TypeScript でバックエンドを構築する開発者向けのコーディングガイドです。クエリ、ミューテーション、アクション、リアルタイム同期に加え、インデックス、HTTP ルート、定期ジョブ、ファイルアップロードを扱います。
主な機能
- 01クエリ、ミューテーション、アクションの作成
- 02インデックスと引数検証の利用
- 03HTTP ルートと定期ジョブの設定
- 04ファイルアップロードの処理
- 05リアルタイムサブスクリプションの利用
活用シーン
- 01Convex のデータ検索と書き込みの実装
- 02外部 API 呼び出しと副作用の構築
- 03Webhook と定期ジョブの設定
- 04クライアント向けファイルアップロードとリアルタイム更新
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill convexインストール後、Claude Code で/convexを入力して読み込まれたか確認します。
詳細
Convex 開発ガイドライン
Convex バックエンド開発、TypeScript、リアルタイムデータ同期のパターンに精通しています。
一般的な開発仕様
コードスタイルと構成
- 関数宣言、イテレーター、モジュールを使用して簡潔な TypeScript を記述します
- 補助動詞を使った説明的な変数名を使用します(例:isLoading、hasError)
- エクスポートするコンポーネント、サブコンポーネント、ヘルパー、静的型でコードを構成します
- 名前付きエクスポートを使用するディレクトリには、ダッシュケースを使用します
- type より interface を優先し、enum は避けてユニオン型を使用します
- 宣言的な JSX パターンを用いた関数コンポーネントを使用します
エラー処理
- ガード節を使って関数内で早期にエラーを処理します
- デバッグに適した方法でエラーをログに記録します
- ユーザーに分かりやすいエラーメッセージを提供します
- フォームのバリデーションには Zod を使用します
- React コンポーネントに適切なエラーバウンダリーを実装します
UI フレームワークの統合
- コンポーネントのプリミティブには Shadcn UI と Radix UI を使用します
- レスポンシブでモバイルファーストなデザインにし、Tailwind CSS でスタイリングします
- useClient、useEffect、useState の使用を最小限に抑えます
- 該当する場合は React Server Components を活用します
- コード分割には Suspense をローディング状態に使用し、動的ローディングを活用します
Convex 固有のパターン
クエリ
query コンストラクターを使用してクエリを構成します。
import { query } from "./_generated/server";
import { v } from "convex/values";
export const getItems = query({
args: {
status: v.optional(v.string()),
},
handler: async (ctx, args) => {
// ctx provides: db, storage, auth
const identity = await ctx.auth.getUserIdentity();
if (args.status) {
return await ctx.db
.query("items")
.withIndex("by_status", (q) => q.eq("status", args.status))
.collect();
}
return await ctx.db.query("items").collect();
},
});
重要:パフォーマンス向上のため、フィルターより Convex のインデックスを優先してください。schema.ts で .index() メソッドを使用してインデックスを定義し、.withIndex() でクエリを実行します。
ミューテーション
データベースへの書き込み用にミューテーションを構成します。
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const createItem = mutation({
args: {
title: v.string(),
description: v.optional(v.string()),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
return await ctx.db.insert("items", {
title: args.title,
description: args.description,
userId: identity.subject,
createdAt: Date.now(),
});
},
});
アクション
外部 API の呼び出しや副作用にはアクションを使用します。
import { action } from "./_generated/server";
import { v } from "convex/values";
export const sendEmail = action({
args: {
to: v.string(),
subject: v.string(),
body: v.string(),
},
handler: async (ctx, args) => {
// Actions can call external APIs
const response = await fetch("https://api.email-service.com/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
});
return response.ok;
},
});
インデックスを使用したスキーマ定義
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
items: defineTable({
title: v.string(),
description: v.optional(v.string()),
status: v.string(),
userId: v.string(),
createdAt: v.number(),
})
.index("by_status", ["status"])
.index("by_user", ["userId"])
.index("by_user_and_status", ["userId", "status"]),
});
HTTP ルーター
Webhook と外部連携のための HTTP ルートを定義します。
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
const http = httpRouter();
http.route({
path: "/webhook",
method: "POST",
handler: httpAction(async (ctx, request) => {
const body = await request.json();
// Process webhook
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}),
});
export default http;
スケジュール済みジョブ
定期的なタスクには cron ジョブを実装します。
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// Run every hour
crons.interval(
"cleanup-old-items",
{ hours: 1 },
internal.tasks.cleanupOldItems
);
// Run at specific time (daily at midnight UTC)
crons.monthly(
"monthly-report",
{ day: 1, hourUTC: 0, minuteUTC: 0 },
internal.reports.generateMonthlyReport
);
export default crons;
ファイル処理
ファイルアップロードの 3 段階のプロセス:
// 1. Generate upload URL (mutation)
export const generateUploadUrl = mutation(async (ctx) => {
return await ctx.storage.generateUploadUrl();
});
// 2. Client POSTs file to the URL
// const uploadUrl = await generateUploadUrl();
// const response = await fetch(uploadUrl, { method: "POST", body: file });
// const { storageId } = await response.json();
// 3. Save storage ID to database (mutation)
export const saveFile = mutation({
args: {
storageId: v.id("_storage"),
filename: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db.insert("files", {
storageId: args.storageId,
filename: args.filename,
});
},
});
ベストプラクティス
- データをフィルタリングまたはソートするクエリでは、必ずインデックスを使用します
- Convex バリデーター(
v.string()、v.number()など)を使用して、引数を検証します - 認証が必要なハンドラーでは、早い段階で認証を確認します
- クライアントに公開すべきでない操作には、内部関数を使用します
- リアルタイムサブスクリプションを活用します。Convex クエリはデータが変更されると自動的に更新されます
- ミューテーションは小さく保ち、単一の操作に集中させます
- 副作用にはアクションを使用します。クエリやミューテーションから外部 API を呼び出してはいけません
- エラーを適切に処理し、ユーザー向けの適切なエラーメッセージを表示します
パフォーマンスに関する考慮事項
- 可能な限り
.filter()の代わりに.withIndex()を使用します - 大きな結果セットは
.paginate()を使用してページネーションします - 結果が 1 件のみの想定では、
.collect()の代わりに.first()を使用します - 頻繁にアクセスされるデータについては、非正規化を検討します
- Convex 組み込みのキャッシュを使用し、独自のキャッシュ実装は避けます
コメント
Markdown 対応