convex-quickstart
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConvex Quickstart
Convex 快速入门
Get a production-ready Convex backend set up in minutes. This skill guides you through initializing Convex, creating your schema, setting up auth, and building your first CRUD operations.
只需几分钟即可搭建好生产可用的Convex后端。本指南将引导你完成Convex初始化、Schema创建、认证设置以及首个CRUD操作的构建。
When to Use
适用场景
- Starting a brand new project with Convex
- Adding Convex to an existing React/Next.js app
- Prototyping a new feature with real-time data
- Converting from another backend to Convex
- Teaching someone Convex for the first time
- 使用Convex启动全新项目
- 为现有React/Next.js应用添加Convex
- 基于实时数据原型化新功能
- 从其他后端迁移至Convex
- 首次向他人讲解Convex
Prerequisites Check
前置条件检查
Before starting, verify:
bash
node --version # v18 or higher
npm --version # v8 or higher开始前,请确认:
bash
node --version # v18 或更高版本
npm --version # v8 或更高版本Quick Start Flow
快速入门流程
Step 1: Install and Initialize
步骤1:安装与初始化
bash
undefinedbash
undefinedInstall Convex
安装Convex
npm install convex
npm install convex
Initialize (creates convex/ directory)
初始化(创建convex/目录)
npx convex dev
This command:
- Creates `convex/` directory
- Sets up authentication
- Starts development server
- Generates TypeScript typesnpx convex dev
该命令会:
- 创建`convex/`目录
- 配置认证
- 启动开发服务器
- 生成TypeScript类型定义Step 2: Create Schema
步骤2:创建Schema
Create :
convex/schema.tstypescript
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"]),
});创建:
convex/schema.tstypescript
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"]),
// 在此添加你的数据表
// 示例:任务表
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"]),
});Step 3: Set Up Authentication
步骤3:设置认证
We'll use WorkOS AuthKit, which provides a complete auth solution with minimal setup.
bash
npm install @workos-inc/authkit-react我们将使用WorkOS AuthKit,它能以极少的配置提供完整的认证解决方案。
bash
npm install @workos-inc/authkit-reactFor React/Vite Apps:
适用于React/Vite应用:
typescript
// 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>
<ConvexProvider client={convex}>
<YourApp />
</ConvexProvider>
</AuthKitProvider>
);
}typescript
// 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);
// 配置Convex使用WorkOS认证
convex.setAuth(useAuth);
function App() {
return (
<AuthKitProvider>
<ConvexProvider client={convex}>
<YourApp />
</ConvexProvider>
</AuthKitProvider>
);
}For Next.js Apps:
适用于Next.js应用:
bash
npm install @workos-inc/authkit-nextjstypescript
// 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>
);
}typescript
// 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>;
}bash
npm install @workos-inc/authkit-nextjstypescript
// 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>
);
}typescript
// 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>;
}Environment Variables:
环境变量:
bash
undefinedbash
undefined.env.local
.env.local
VITE_CONVEX_URL=https://your-deployment.convex.cloud
VITE_WORKOS_CLIENT_ID=your_workos_client_id
VITE_CONVEX_URL=https://your-deployment.convex.cloud
VITE_WORKOS_CLIENT_ID=your_workos_client_id
For Next.js:
适用于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
**Alternative auth providers:** If you need to use a different provider (Clerk, Auth0, custom JWT), see the [Convex auth documentation](https://docs.convex.dev/auth).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认证文档](https://docs.convex.dev/auth)。Step 4: Create Auth Helpers
步骤4:创建认证工具函数
Create :
convex/lib/auth.tstypescript
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;
}Create :
convex/users.tstypescript
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 ?? "",
});
},
});创建:
convex/lib/auth.tstypescript
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.tstypescript
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 ?? "",
});
},
});Step 5: Create Your First CRUD Operations
步骤5:创建首个CRUD操作
Create :
convex/tasks.tstypescript
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);
},
});创建:
convex/tasks.tstypescript
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { getCurrentUser } from "./lib/auth";
// 列出当前用户的所有任务
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();
},
});
// 获取单个任务
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;
},
});
// 创建任务
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(),
});
},
});
// 更新任务
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);
},
});
// 删除任务
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);
},
});Step 6: Use in Your React App
步骤6:在React应用中使用
typescript
// 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..." />
<button>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>
);
}typescript
// 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>
{/* 创建任务 */}
<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..." />
<button>Add</button>
</form>
{/* 任务列表 */}
{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>
);
}Step 7: Development vs Production
步骤7:开发环境与生产环境
For Development (use this!):
bash
undefined开发环境(推荐使用!):
bash
undefinedStart development server (NOT production!)
启动开发服务器(请勿用于生产!)
npx convex dev
npx convex dev
This runs locally and auto-reloads on changes
本地运行,变更时自动重载
Use this for all development work
所有开发工作请使用此命令
**For Production Deployment:**
```bash
**生产环境部署:**
```bashONLY use this when deploying to production!
仅在部署至生产环境时使用!
npx convex deploy
npx convex deploy
WARNING: This deploys to your production environment
警告:此命令会部署至你的生产环境
Don't use this during development
开发过程中请勿使用
**Important:** Always use `npx convex dev` during development. Only use `npx convex deploy` when you're ready to ship to production.
**重要提示:**
开发期间请始终使用`npx convex dev`。仅当准备好上线生产时,再使用`npx convex deploy`。Common Patterns
常见模式
Paginated Queries
分页查询
typescript
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;
},
});typescript
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;
},
});Scheduled Jobs
定时任务
typescript
// 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;typescript
// 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;Project Templates
项目模板
React + Vite + Convex
React + Vite + Convex
bash
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install convex @workos-inc/authkit-react
npx convex devbash
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install convex @workos-inc/authkit-react
npx convex devNext.js + Convex
Next.js + Convex
bash
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex devbash
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex devExpo (React Native) + Convex
Expo(React Native)+ Convex
bash
npx create-expo-app my-app
cd my-app
npm install convex
npx convex devbash
npx create-expo-app my-app
cd my-app
npm install convex
npx convex devChecklist
检查清单
- completed
npm install convex - running (use this, NOT deploy!)
npx convex dev - Schema created with proper indexes
- Auth provider configured (WorkOS/custom)
- helper implemented
getCurrentUser - User storage mutation created
- CRUD operations with auth checks
- Frontend integrated with hooks
- Tested queries and mutations locally
- When ready for production, use
npx convex deploy
- 已完成
npm install convex - 正在运行(请使用此命令,而非deploy!)
npx convex dev - 已创建带正确索引的Schema
- 已配置认证提供商(WorkOS/自定义)
- 已实现工具函数
getCurrentUser - 已创建用户存储mutation
- 已创建带认证检查的CRUD操作
- 前端已与hooks集成
- 已在本地测试查询与mutation
- 准备好生产部署时,使用
npx convex deploy
Next Steps
后续步骤
After quickstart:
- Add more tables to schema
- Implement relationships between tables
- Add file storage (Convex supports file uploads)
- Set up vector search for AI features
- Add cron jobs for scheduled tasks
- Configure production environment variables
完成快速入门后:
- 为Schema添加更多数据表
- 实现数据表间的关联关系
- 添加文件存储(Convex支持文件上传)
- 为AI功能设置向量搜索
- 添加定时任务
- 配置生产环境变量
Troubleshooting
故障排除
"Not authenticated" errors
"Not authenticated"错误
- Ensure auth provider is configured
- Call mutation on first sign-in
storeUser - Check is imported correctly
getCurrentUser
- 确保认证提供商已正确配置
- 首次登录时调用mutation
storeUser - 检查是否正确导入
getCurrentUser
Types not updating
类型未更新
- Run (regenerates types)
npx convex dev - Restart TypeScript server in editor
- 运行(重新生成类型)
npx convex dev - 重启编辑器中的TypeScript服务器
Slow queries
查询缓慢
- Add indexes to schema
- Use instead of
.withIndex().filter() - Check query patterns in dashboard
- 为Schema添加索引
- 使用替代
.withIndex().filter() - 在控制台中检查查询模式