convex-quickstart

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Convex 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
undefined
bash
undefined

Install 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 types
npx convex dev

该命令会:
- 创建`convex/`目录
- 配置认证
- 启动开发服务器
- 生成TypeScript类型定义

Step 2: Create Schema

步骤2:创建Schema

Create
convex/schema.ts
:
typescript
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.ts
typescript
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-react

For 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-nextjs
typescript
// 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-nextjs
typescript
// 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
undefined
bash
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.ts
:
typescript
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.ts
:
typescript
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.ts
typescript
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
typescript
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.ts
:
typescript
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.ts
typescript
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
undefined

Start 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

**生产环境部署:**
```bash

ONLY 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 dev
bash
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

Next.js + Convex

bash
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex dev
bash
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex dev

Expo (React Native) + Convex

Expo(React Native)+ Convex

bash
npx create-expo-app my-app
cd my-app
npm install convex
npx convex dev
bash
npx create-expo-app my-app
cd my-app
npm install convex
npx convex dev

Checklist

检查清单

  • npm install convex
    completed
  • npx convex dev
    running (use this, NOT deploy!)
  • Schema created with proper indexes
  • Auth provider configured (WorkOS/custom)
  • getCurrentUser
    helper implemented
  • 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
  • npx convex dev
    正在运行(请使用此命令,而非deploy!)
  • 已创建带正确索引的Schema
  • 已配置认证提供商(WorkOS/自定义)
  • 已实现
    getCurrentUser
    工具函数
  • 已创建用户存储mutation
  • 已创建带认证检查的CRUD操作
  • 前端已与hooks集成
  • 已在本地测试查询与mutation
  • 准备好生产部署时,使用
    npx convex deploy

Next Steps

后续步骤

After quickstart:
  1. Add more tables to schema
  2. Implement relationships between tables
  3. Add file storage (Convex supports file uploads)
  4. Set up vector search for AI features
  5. Add cron jobs for scheduled tasks
  6. Configure production environment variables
完成快速入门后:
  1. 为Schema添加更多数据表
  2. 实现数据表间的关联关系
  3. 添加文件存储(Convex支持文件上传)
  4. 为AI功能设置向量搜索
  5. 添加定时任务
  6. 配置生产环境变量

Troubleshooting

故障排除

"Not authenticated" errors

"Not authenticated"错误

  • Ensure auth provider is configured
  • Call
    storeUser
    mutation on first sign-in
  • Check
    getCurrentUser
    is imported correctly
  • 确保认证提供商已正确配置
  • 首次登录时调用
    storeUser
    mutation
  • 检查
    getCurrentUser
    是否正确导入

Types not updating

类型未更新

  • Run
    npx convex dev
    (regenerates types)
  • Restart TypeScript server in editor
  • 运行
    npx convex dev
    (重新生成类型)
  • 重启编辑器中的TypeScript服务器

Slow queries

查询缓慢

  • Add indexes to schema
  • Use
    .withIndex()
    instead of
    .filter()
  • Check query patterns in dashboard
  • 为Schema添加索引
  • 使用
    .withIndex()
    替代
    .filter()
  • 在控制台中检查查询模式

Learn More

更多学习资源