convex-expert-2025

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Convex Expert Guide - December 2025

Convex后端开发专家指南 - 2025年12月

What is Convex?

什么是Convex?

Convex is a full-stack TypeScript backend platform with:
  • Reactive database - Real-time subscriptions, automatic cache invalidation
  • Serverless functions - Queries, mutations, actions with TypeScript
  • ACID transactions - Every mutation is a transaction, automatic conflict resolution
  • Type safety - End-to-end types from schema to React hooks
  • Built-in features - Auth, file storage, scheduling, vector search, workflows
Convex是一个全栈TypeScript后端平台,具备以下特性:
  • 响应式数据库 - 实时订阅、自动缓存失效
  • 无服务器函数 - 基于TypeScript的查询、变更、操作
  • ACID事务 - 每个变更都是一次事务,自动冲突解决
  • 类型安全 - 从模式到React Hooks的端到端类型支持
  • 内置功能 - 身份验证、文件存储、调度、向量搜索、工作流

Function Types

函数类型

TypePurposeDatabaseExternal APIsDeterministic
query
Read data✅ Read✅ Required
mutation
Write data✅ Read/Write✅ Required
action
Side effectsVia
runQuery
/
runMutation
httpAction
HTTP endpointsVia ctx
类型用途数据库访问外部API调用是否确定性
query
读取数据✅ 支持读取❌ 不支持✅ 必须是确定性
mutation
写入数据✅ 支持读写❌ 不支持✅ 必须是确定性
action
处理副作用通过
runQuery
/
runMutation
支持
✅ 支持❌ 非确定性
httpAction
HTTP端点通过ctx支持✅ 支持❌ 非确定性

Quick Patterns

快速示例

Schema Definition

模式定义

typescript
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    role: v.union(v.literal("admin"), v.literal("user")),
    profileId: v.optional(v.id("profiles")),
  })
    .index("by_email", ["email"])
    .index("by_role", ["role"]),

  messages: defineTable({
    authorId: v.id("users"),
    body: v.string(),
    channel: v.string(),
  })
    .index("by_channel", ["channel"])
    .searchIndex("search_body", { searchField: "body" }),
});
typescript
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    role: v.union(v.literal("admin"), v.literal("user")),
    profileId: v.optional(v.id("profiles")),
  })
    .index("by_email", ["email"])
    .index("by_role", ["role"]),

  messages: defineTable({
    authorId: v.id("users"),
    body: v.string(),
    channel: v.string(),
  })
    .index("by_channel", ["channel"])
    .searchIndex("search_body", { searchField: "body" }),
});

Query

查询函数

typescript
// convex/messages.ts
import { query } from "./_generated/server";
import { v } from "convex/values";

export const list = query({
  args: { channel: v.string(), limit: v.optional(v.number()) },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channel", args.channel))
      .order("desc")
      .take(args.limit ?? 50);
  },
});
typescript
// convex/messages.ts
import { query } from "./_generated/server";
import { v } from "convex/values";

export const list = query({
  args: { channel: v.string(), limit: v.optional(v.number()) },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channel", args.channel))
      .order("desc")
      .take(args.limit ?? 50);
  },
});

Mutation

变更函数

typescript
// convex/messages.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const send = mutation({
  args: { body: v.string(), channel: v.string() },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Unauthorized");

    return await ctx.db.insert("messages", {
      authorId: identity.subject,
      body: args.body,
      channel: args.channel,
    });
  },
});
typescript
// convex/messages.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const send = mutation({
  args: { body: v.string(), channel: v.string() },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Unauthorized");

    return await ctx.db.insert("messages", {
      authorId: identity.subject,
      body: args.body,
      channel: args.channel,
    });
  },
});

Action

操作函数

typescript
// convex/ai.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api, internal } from "./_generated/api";

export const generateResponse = action({
  args: { prompt: v.string(), threadId: v.id("threads") },
  handler: async (ctx, args) => {
    // Call external API
    const response = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
      body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: args.prompt }] }),
    });
    const data = await response.json();
    
    // Write result via mutation
    await ctx.runMutation(internal.messages.saveAIResponse, {
      threadId: args.threadId,
      content: data.choices[0].message.content,
    });
  },
});
typescript
// convex/ai.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api, internal } from "./_generated/api";

export const generateResponse = action({
  args: { prompt: v.string(), threadId: v.id("threads") },
  handler: async (ctx, args) => {
    // 调用外部API
    const response = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
      body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: args.prompt }] }),
    });
    const data = await response.json();
    
    // 通过变更函数写入结果
    await ctx.runMutation(internal.messages.saveAIResponse, {
      threadId: args.threadId,
      content: data.choices[0].message.content,
    });
  },
});

React Integration

React集成

typescript
// app/page.tsx
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

export default function Chat() {
  const messages = useQuery(api.messages.list, { channel: "general" });
  const sendMessage = useMutation(api.messages.send);

  if (messages === undefined) return <div>Loading...</div>;

  return (
    <div>
      {messages.map((msg) => <p key={msg._id}>{msg.body}</p>)}
      <button onClick={() => sendMessage({ body: "Hello!", channel: "general" })}>
        Send
      </button>
    </div>
  );
}
typescript
// app/page.tsx
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

export default function Chat() {
  const messages = useQuery(api.messages.list, { channel: "general" });
  const sendMessage = useMutation(api.messages.send);

  if (messages === undefined) return <div>加载中...</div>;

  return (
    <div>
      {messages.map((msg) => <p key={msg._id}>{msg.body}</p>)}
      <button onClick={() => sendMessage({ body: "Hello!", channel: "general" })}>
        发送
      </button>
    </div>
  );
}

Reference Files

参考文档

Load based on task:
  • Functions, schema, validators: See functions-and-schema.md
  • Database queries, indexes, pagination: See database-patterns.md
  • React hooks, Next.js, TanStack Query: See client-integration.md
  • Convex Auth, Clerk, Auth0: See authentication.md
  • File storage, cron, scheduling: See file-storage-scheduling.md
  • AI agents, vector search, workflows: See ai-agents-workflows.md
  • Components, security, optimization: See components-best-practices.md
根据任务需求选择查看:
  • 函数、模式、验证器:查看functions-and-schema.md
  • 数据库查询、索引、分页:查看database-patterns.md
  • React Hooks、Next.js、TanStack Query:查看client-integration.md
  • Convex Auth、Clerk、Auth0:查看authentication.md
  • 文件存储、定时任务、调度:查看file-storage-scheduling.md
  • AI Agent、向量搜索、工作流:查看ai-agents-workflows.md
  • 组件、安全、优化:查看components-best-practices.md

Core Concepts

核心概念

Reactivity

响应性

  • useQuery
    creates a WebSocket subscription
  • UI updates automatically when data changes
  • No manual cache invalidation needed
  • All subscriptions update atomically
  • useQuery
    会创建WebSocket订阅
  • 数据变化时UI自动更新
  • 无需手动处理缓存失效
  • 所有订阅都会原子性更新

Determinism

确定性

  • Queries and mutations must be deterministic
  • No
    Math.random()
    ,
    Date.now()
    , or
    fetch
    allowed
  • Use
    _creationTime
    instead of
    Date.now()
  • Actions are the escape hatch for non-deterministic work
  • 查询和变更函数必须是确定性的
  • 不允许使用
    Math.random()
    Date.now()
    fetch
  • _creationTime
    替代
    Date.now()
  • 操作函数是处理非确定性逻辑的出口

Transactions

事务

  • Every mutation is an ACID transaction
  • Automatic optimistic concurrency control
  • Retries on conflicts
  • No BEGIN/COMMIT needed
  • 每个变更函数都是ACID事务
  • 自动乐观并发控制
  • 冲突时自动重试
  • 无需手动编写BEGIN/COMMIT

Type Safety

类型安全

  • Schema generates TypeScript types
  • Doc<"tableName">
    for document types
  • Id<"tableName">
    for ID types
  • End-to-end type checking from schema to React
  • 模式会自动生成TypeScript类型
  • 使用
    Doc<"tableName">
    表示文档类型
  • 使用
    Id<"tableName">
    表示ID类型
  • 从模式到React实现端到端类型检查

Validator Reference

验证器参考

ValidatorTypeScript TypeExample
v.string()
string
"hello"
v.number()
number
42
v.boolean()
boolean
true
v.null()
null
null
v.id("table")
Id<"table">
Document ID
v.array(v.string())
string[]
["a", "b"]
v.object({...})
{...}
{ name: "x" }
v.optional(v.X())
X | undefined
Optional field
v.union(v.X(), v.Y())
X | Y
Union type
v.literal("x")
"x"
Exact value
v.any()
any
Any value
v.bytes()
ArrayBuffer
Binary data
v.record(k, v)
Record<K, V>
Key-value map
验证器TypeScript类型示例
v.string()
string
"hello"
v.number()
number
42
v.boolean()
boolean
true
v.null()
null
null
v.id("table")
Id<"table">
文档ID
v.array(v.string())
string[]
["a", "b"]
v.object({...})
{...}
{ name: "x" }
v.optional(v.X())
X | undefined
可选字段
v.union(v.X(), v.Y())
X | Y
联合类型
v.literal("x")
"x"
精确值
v.any()
any
任意值
v.bytes()
ArrayBuffer
二进制数据
v.record(k, v)
Record<K, V>
键值对映射

Decision Framework

决策框架

When to Use Each Function Type

函数类型选择指南

Need to read data?
├─ Yes → query
│   └─ Need real-time updates? → useQuery (React)
│   └─ One-time fetch? → fetchQuery (Server)
└─ No
    └─ Need to write data?
        ├─ Yes → mutation
        │   └─ Also need external API? → mutation + scheduler.runAfter(0, action)
        └─ No
            └─ Need external API? → action
                └─ Need durability? → Workflow component
需要读取数据?
├─ 是 → 使用query
│   └─ 需要实时更新? → 使用useQuery(React端)
│   └─ 一次性获取? → 使用fetchQuery(服务端)
└─ 否
    └─ 需要写入数据?
        ├─ 是 → 使用mutation
        │   └─ 同时需要调用外部API? → mutation + scheduler.runAfter(0, action)
        └─ 否
            └─ 需要调用外部API? → 使用action
                └─ 需要持久性? → 使用工作流组件

Internal vs Public Functions

内部函数 vs 公共函数

Use CaseFunction Type
Client can call
query
,
mutation
,
action
Backend only
internalQuery
,
internalMutation
,
internalAction
Scheduled workInternal functions
Security-sensitiveInternal functions
使用场景函数类型
客户端可直接调用
query
,
mutation
,
action
仅后端可用
internalQuery
,
internalMutation
,
internalAction
定时任务内部函数
安全敏感逻辑内部函数

Project Structure

项目结构

my-app/
├── convex/
│   ├── _generated/           # Auto-generated (don't edit)
│   │   ├── api.d.ts
│   │   ├── api.js
│   │   ├── dataModel.d.ts
│   │   └── server.d.ts
│   ├── schema.ts             # Database schema
│   ├── auth.ts               # Auth configuration
│   ├── users.ts              # User functions
│   ├── messages.ts           # Message functions
│   ├── crons.ts              # Scheduled jobs
│   ├── http.ts               # HTTP endpoints
│   └── model/                # Business logic (recommended)
│       ├── users.ts
│       └── messages.ts
├── app/                      # Next.js app
│   ├── ConvexClientProvider.tsx
│   └── page.tsx
└── .env.local
    └── NEXT_PUBLIC_CONVEX_URL=...
my-app/
├── convex/
│   ├── _generated/           # 自动生成(请勿编辑)
│   │   ├── api.d.ts
│   │   ├── api.js
│   │   ├── dataModel.d.ts
│   │   └── server.d.ts
│   ├── schema.ts             # 数据库模式文件
│   ├── auth.ts               # 身份验证配置
│   ├── users.ts              # 用户相关函数
│   ├── messages.ts           # 消息相关函数
│   ├── crons.ts              # 定时任务
│   ├── http.ts               # HTTP端点
│   └── model/                # 业务逻辑(推荐目录)
│       ├── users.ts
│       └── messages.ts
├── app/                      # Next.js应用目录
│   ├── ConvexClientProvider.tsx
│   └── page.tsx
└── .env.local
    └── NEXT_PUBLIC_CONVEX_URL=...

CLI Commands

CLI命令

bash
undefined
bash
undefined

Initialize Convex in project

在项目中初始化Convex

npx convex init
npx convex init

Start development server (watches for changes)

启动开发服务器(监听文件变化)

npx convex dev
npx convex dev

Deploy to production

部署到生产环境

npx convex deploy
npx convex deploy

Open dashboard

打开控制台

npx convex dashboard
npx convex dashboard

Run a function manually

手动运行函数

npx convex run messages:list '{"channel": "general"}'
npx convex run messages:list '{"channel": "general"}'

Import data

导入数据

npx convex import --table messages data.json
npx convex import --table messages data.json

Export data

导出数据

npx convex export --path ./backup
undefined
npx convex export --path ./backup
undefined

Environment Variables

环境变量

bash
undefined
bash
undefined

.env.local (development)

.env.local(开发环境)

NEXT_PUBLIC_CONVEX_URL=https://your-project.convex.cloud
NEXT_PUBLIC_CONVEX_URL=https://your-project.convex.cloud

Set in Convex dashboard for production

在Convex控制台配置(生产环境)

OPENAI_API_KEY=sk-... CLERK_SECRET_KEY=sk_...

Access in functions:
```typescript
const apiKey = process.env.OPENAI_API_KEY;
OPENAI_API_KEY=sk-... CLERK_SECRET_KEY=sk_...

在函数中访问环境变量:
```typescript
const apiKey = process.env.OPENAI_API_KEY;