convex-quickstart について
React、Next.js、Expo プロジェクトで Convex バックエンドを導入する開発者向けのガイドスキルです。初期化からスキーマ、認証、CRUD、開発と本番の手順まで扱います。
主な機能
- 01Convex プロジェクトの初期化
- 02スキーマと認証設定の案内
- 03CRUD クエリとミューテーションの例
- 04React hooks による Convex 連携
- 05開発用と本番用コマンドの区別
活用シーン
- 01新しい Convex アプリの開始
- 02既存 React アプリへの Convex 追加
- 03リアルタイムデータ機能の試作
- 04実際の構成を通じた Convex の学習
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ get-convex/ convex-agent-plugins --skill convex-quickstartインストール後、Claude Code で/convex-quickstartを入力して読み込まれたか確認します。
詳細
Convex クイックスタート
本番環境に対応した Convex バックエンドを数分でセットアップできます。このスキルでは、Convex の初期化、スキーマの作成、認証のセットアップ、最初の CRUD 操作の構築を順を追って説明します。
使用する場面
- Convex を使った新しいプロジェクトを始めるとき
- 既存の React/Next.js アプリに Convex を追加するとき
- リアルタイムデータを使った新機能をプロトタイプするとき
- 別のバックエンドから Convex に移行するとき
- Convex を初めて誰かに教えるとき
前提条件の確認
始める前に、次の項目を確認してください。
node --version # v18 or higher
npm --version # v8 or higher
クイックスタートの流れ
ステップ 1:インストールと初期化
# Install Convex
npm install convex
# Initialize (creates convex/ directory)
npx convex dev
このコマンドでは、次の処理を行います。
convex/ディレクトリを作成します- 認証をセットアップします
- 開発サーバーを起動します
- TypeScript 型を生成します
ステップ 2:スキーマを作成する
convex/schema.ts を作成します。
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
users: defineTable({
tokenIdentifier: v.string(),
name: v.string(),
email: v.string(),
}).index("by_token", ["tokenIdentifier"]),
// Add your tables here
// Example: Tasks table
tasks: defineTable({
userId: v.id("users"),
title: v.string(),
completed: v.boolean(),
createdAt: v.number(),
})
.index("by_user", ["userId"])
.index("by_user_and_completed", ["userId", "completed"]),
});
ステップ 3:認証をセットアップする
セットアップを最小限に抑えながら、認証機能一式を提供する WorkOS AuthKit を使用します。
npm install @workos-inc/authkit-react
React/Vite アプリの場合:
// src/main.tsx
import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react";
import { ConvexReactClient } from "convex/react";
import { ConvexProvider } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
// Configure Convex to use WorkOS auth
convex.setAuth(useAuth);
function App() {
return (
<AuthKitProvider clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}>
<ConvexProvider client={convex}>
<YourApp />
</ConvexProvider>
</AuthKitProvider>
);
}
Next.js アプリの場合:
npm install @workos-inc/authkit-nextjs
// app/layout.tsx
import { AuthKitProvider } from "@workos-inc/authkit-nextjs";
import { ConvexClientProvider } from "./ConvexClientProvider";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<AuthKitProvider>
<ConvexClientProvider>
{children}
</ConvexClientProvider>
</AuthKitProvider>
</body>
</html>
);
}
// app/ConvexClientProvider.tsx
"use client";
import { ConvexReactClient } from "convex/react";
import { ConvexProvider } from "convex/react";
import { useAuth } from "@workos-inc/authkit-nextjs";
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export function ConvexClientProvider({ children }: { children: React.ReactNode }) {
const { getToken } = useAuth();
convex.setAuth(async () => {
return await getToken();
});
return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}
環境変数:
# .env.local
VITE_CONVEX_URL=https://your-deployment.convex.cloud
VITE_WORKOS_CLIENT_ID=your_workos_client_id
# For Next.js:
NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
NEXT_PUBLIC_WORKOS_CLIENT_ID=your_workos_client_id
WORKOS_API_KEY=your_workos_api_key
WORKOS_COOKIE_PASSWORD=generate_a_random_32_character_string
認証プロバイダーの代替案: 別のプロバイダー(Clerk、Auth0、カスタム JWT)を使う場合は、Convex の認証ドキュメントを参照してください。
ステップ 4:認証ヘルパーを作成する
convex/lib/auth.ts を作成します。
import { QueryCtx, MutationCtx } from "../_generated/server";
import { Doc } from "../_generated/dataModel";
export async function getCurrentUser(
ctx: QueryCtx | MutationCtx
): Promise<Doc<"users">> {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
const user = await ctx.db
.query("users")
.withIndex("by_token", q =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
if (!user) {
throw new Error("User not found");
}
return user;
}
convex/users.ts を作成します。
import { mutation } from "./_generated/server";
export const store = mutation({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
const existing = await ctx.db
.query("users")
.withIndex("by_token", q =>
q.eq("tokenIdentifier", identity.tokenIdentifier)
)
.unique();
if (existing) return existing._id;
return await ctx.db.insert("users", {
tokenIdentifier: identity.tokenIdentifier,
name: identity.name ?? "Anonymous",
email: identity.email ?? "",
});
},
});
ステップ 5:最初の CRUD 操作を作成する
convex/tasks.ts を作成します。
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { getCurrentUser } from "./lib/auth";
// List all tasks for current user
export const list = query({
args: {},
handler: async (ctx) => {
const user = await getCurrentUser(ctx);
return await ctx.db
.query("tasks")
.withIndex("by_user", q => q.eq("userId", user._id))
.order("desc")
.collect();
},
});
// Get a single task
export const get = query({
args: { taskId: v.id("tasks") },
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx);
const task = await ctx.db.get(args.taskId);
if (!task) throw new Error("Task not found");
if (task.userId !== user._id) throw new Error("Unauthorized");
return task;
},
});
// Create a task
export const create = mutation({
args: { title: v.string() },
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx);
return await ctx.db.insert("tasks", {
userId: user._id,
title: args.title,
completed: false,
createdAt: Date.now(),
});
},
});
// Update a task
export const update = mutation({
args: {
taskId: v.id("tasks"),
title: v.optional(v.string()),
completed: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx);
const task = await ctx.db.get(args.taskId);
if (!task) throw new Error("Task not found");
if (task.userId !== user._id) throw new Error("Unauthorized");
const updates: any = {};
if (args.title !== undefined) updates.title = args.title;
if (args.completed !== undefined) updates.completed = args.completed;
await ctx.db.patch(args.taskId, updates);
},
});
// Delete a task
export const remove = mutation({
args: { taskId: v.id("tasks") },
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx);
const task = await ctx.db.get(args.taskId);
if (!task) throw new Error("Task not found");
if (task.userId !== user._id) throw new Error("Unauthorized");
await ctx.db.delete(args.taskId);
},
});
ステップ 6:React アプリで使用する
// app/tasks/page.tsx
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
export default function TasksPage() {
const tasks = useQuery(api.tasks.list);
const create = useMutation(api.tasks.create);
const update = useMutation(api.tasks.update);
const remove = useMutation(api.tasks.remove);
if (!tasks) return <div>Loading...</div>;
return (
<div>
<h1>Tasks</h1>
{/* Create task */}
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
create({ title: formData.get("title") as string });
(e.target as HTMLFormElement).reset();
}}>
<input name="title" placeholder="New task" required />
<button type="submit">Add</button>
</form>
{/* Task list */}
{tasks.map(task => (
<div key={task._id}>
<input
type="checkbox"
checked={task.completed}
onChange={(e) => update({
taskId: task._id,
completed: e.target.checked
})}
/>
<span>{task.title}</span>
<button onClick={() => remove({ taskId: task._id })}>
Delete
</button>
</div>
))}
</div>
);
}
ステップ 7:開発環境と本番環境
開発環境の場合(こちらを使ってください!):
# Start development server (NOT production!)
npx convex dev
# This runs locally and auto-reloads on changes
# Use this for all development work
本番環境へのデプロイの場合:
# ONLY use this when deploying to production!
npx convex deploy
# WARNING: This deploys to your production environment
# Don't use this during development
重要: 開発中は必ず npx convex dev を使用してください。npx convex deploy は、本番環境にリリースする準備が整ったときにのみ使用してください。
よく使われるパターン
ページネーション付きクエリ
export const listPaginated = query({
args: {
cursor: v.optional(v.string()),
limit: v.number(),
},
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx);
const results = await ctx.db
.query("tasks")
.withIndex("by_user", q => q.eq("userId", user._id))
.order("desc")
.paginate({ cursor: args.cursor, limit: args.limit });
return results;
},
});
ステータスによる絞り込み
export const listByStatus = query({
args: {
completed: v.boolean(),
},
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx);
return await ctx.db
.query("tasks")
.withIndex("by_user_and_completed", q =>
q.eq("userId", user._id).eq("completed", args.completed)
)
.collect();
},
});
スケジュール済みジョブ
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
crons.daily(
"cleanup-old-tasks",
{ hourUTC: 0, minuteUTC: 0 },
internal.tasks.cleanupOld
);
export default crons;
// convex/tasks.ts
export const cleanupOld = internalMutation({
handler: async (ctx) => {
const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
const oldTasks = await ctx.db
.query("tasks")
.filter(q =>
q.and(
q.eq(q.field("completed"), true),
q.lt(q.field("createdAt"), thirtyDaysAgo)
)
)
.collect();
for (const task of oldTasks) {
await ctx.db.delete(task._id);
}
},
});
プロジェクトテンプレート
React + Vite + Convex
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install convex @workos-inc/authkit-react
npx convex dev
Next.js + Convex
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex dev
Expo(React Native)+ Convex
npx create-expo-app my-app
cd my-app
npm install convex
npx convex dev
チェックリスト
-
npm install convexを実行済み -
npx convex devを実行中(こちらを使用してください。deploy は使用しないでください!) - 適切なインデックスを含むスキーマを作成済み
- 認証プロバイダーを設定済み(WorkOS/カスタム)
-
getCurrentUserヘルパーを実装済み - ユーザー保存用の mutation を作成済み
- 認証チェック付きの CRUD 操作を実装済み
- フックを使ってフロントエンドと連携済み
- クエリと mutation をローカルでテスト済み
- 本番環境への準備が整ったら、
npx convex deployを使用する
忘れないでください: 開発作業ではすべて npx convex dev を使用してください。npx convex deploy は、本番環境にデプロイするときにのみ使用してください!
次のステップ
クイックスタートの後に行うこと:
- スキーマにテーブルを追加する
- テーブル間のリレーションを実装する
- ファイルストレージを追加する(Convex はファイルのアップロードをサポートしています)
- AI 機能向けにベクトル検索をセットアップする
- スケジュールされたタスク用に cron ジョブを追加する
- 本番環境の環境変数を設定する
トラブルシューティング
「Not authenticated」エラー
- 認証プロバイダーが設定されていることを確認します
- 初回サインイン時に
storeUsermutation を呼び出します getCurrentUserが正しくインポートされていることを確認します
型が更新されない
npx convex devを実行します(型が再生成されます)- エディターで TypeScript サーバーを再起動します
クエリが遅い
- スキーマにインデックスを追加します
.filter()の代わりに.withIndex()を使用します- ダッシュボードでクエリのパターンを確認します