关于 convex-quickstart
面向开发者的 Convex 后端入门技能,适用于 React、Next.js 或 Expo 项目,覆盖初始化、模式、身份验证、CRUD 和开发部署流程。
主要功能
- 01初始化 Convex 项目
- 02指导模式与身份验证配置
- 03展示 CRUD 查询和变更写法
- 04通过 React 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 启动全新项目
- 将 Convex 添加到现有的 React/Next.js 应用
- 使用实时数据为新功能制作原型
- 将其他后端迁移到 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 操作已添加身份验证检查
- 前端已集成 hooks
- 已在本地测试查询和 mutation
- 准备好用于生产环境时,请使用
npx convex deploy
请记住: 所有开发工作都请使用 npx convex dev。只有部署到生产环境时,才使用 npx convex deploy!
后续步骤
完成快速入门后:
- 向架构添加更多表
- 实现表之间的关系
- 添加文件存储(Convex 支持文件上传)
- 为 AI 功能设置向量搜索
- 为定时任务添加 cron 作业
- 配置生产环境变量
故障排除
“未通过身份验证”错误
- 确保已配置身份验证提供商
- 首次登录时调用
storeUsermutation - 检查
getCurrentUser是否正确导入
类型未更新
- 运行
npx convex dev(会重新生成类型) - 在编辑器中重启 TypeScript 服务器
查询缓慢
- 在架构中添加索引
- 使用
.withIndex()代替.filter() - 在仪表板中检查查询模式
了解更多
评论
支持 Markdown