convex

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Convex

Convex

You are an expert in Convex — the open-source, reactive backend platform where queries are TypeScript code. You have deep knowledge of schema design, function authoring (queries, mutations, actions), real-time data subscriptions, authentication, file storage, scheduling, and deployment workflows across React, Next.js, Angular, Vue, Svelte, React Native, and server-side environments.
您是Convex领域的专家——Convex是一款开源的响应式后端平台,其查询逻辑以TypeScript代码实现。您精通schema设计、函数编写(查询、变更、操作)、实时数据订阅、身份认证、文件存储、任务调度,以及在React、Next.js、Angular、Vue、Svelte、React Native和服务端环境中的部署工作流。

When to Use

使用场景

  • Use when building a new project with Convex as the backend
  • Use when adding Convex to an existing React, Next.js, Angular, Vue, Svelte, or React Native app
  • Use when designing schemas for a Convex document-relational database
  • Use when writing or debugging Convex functions (queries, mutations, actions)
  • Use when implementing real-time/reactive data patterns
  • Use when setting up authentication with Convex Auth or third-party providers (Clerk, Auth0, etc.)
  • Use when working with Convex file storage, scheduled functions, or cron jobs
  • Use when deploying or managing Convex projects
  • 当您以Convex作为后端构建新项目时
  • 当您为现有的React、Next.js、Angular、Vue、Svelte或React Native应用集成Convex时
  • 当您为Convex文档关系型数据库设计schema时
  • 当您编写或调试Convex函数(查询、变更、操作)时
  • 当您实现实时/响应式数据模式时
  • 当您通过Convex Auth或第三方提供商(Clerk、Auth0等)配置身份认证时
  • 当您使用Convex文件存储、定时函数或定时任务时
  • 当您部署或管理Convex项目时

Core Concepts

核心概念

Convex is a document-relational database with a fully managed backend. Key differentiators:
  • Reactive by default: Queries automatically re-run and push updates to all connected clients when underlying data changes
  • TypeScript-first: All backend logic — queries, mutations, actions, schemas — is written in TypeScript
  • ACID transactions: Serializable isolation with optimistic concurrency control
  • No infrastructure to manage: Serverless, scales automatically, zero config
  • End-to-end type safety: Types flow from schema → backend functions → client hooks
Convex是一款文档关系型数据库,搭配全托管后端。核心差异化特性:
  • 默认响应式:当底层数据发生变化时,查询会自动重新执行,并将更新推送给所有连接的客户端
  • TypeScript优先:所有后端逻辑——查询、变更、操作、schema——均使用TypeScript编写
  • ACID事务:支持可序列化隔离级别与乐观并发控制
  • 无需管理基础设施:Serverless架构,自动扩容,零配置
  • 端到端类型安全:类型从schema → 后端函数 → 客户端钩子全程流转

Function Types

函数类型

TypePurposeCan Read DBCan Write DBCan Call External APIsCached/Reactive
QueryRead data
MutationWrite data
ActionSide effectsvia
runQuery
via
runMutation
HTTP ActionWebhooks/custom endpointsvia
runQuery
via
runMutation
类型用途可读取数据库可写入数据库可调用外部API缓存/响应式
Query(查询)读取数据
Mutation(变更)写入数据
Action(操作)处理副作用通过
runQuery
通过
runMutation
HTTP Action(HTTP操作)Webhook/自定义端点通过
runQuery
通过
runMutation

Project Setup

项目设置

New Project (Next.js)

新项目(Next.js)

bash
npx create-next-app@latest my-app
cd my-app && npm install convex
npx convex dev
bash
npx create-next-app@latest my-app
cd my-app && npm install convex
npx convex dev

Add to Existing Project

集成到现有项目

bash
npm install convex
npx convex dev
The
npx convex dev
command:
  1. Prompts you to log in (GitHub)
  2. Creates a project and deployment
  3. Generates
    convex/
    folder for backend functions
  4. Syncs functions to your dev deployment in real-time
  5. Creates
    .env.local
    with
    CONVEX_DEPLOYMENT
    and
    NEXT_PUBLIC_CONVEX_URL
bash
npm install convex
npx convex dev
npx convex dev
命令会执行以下操作:
  1. 提示您登录(GitHub账号)
  2. 创建项目及部署实例
  3. 生成
    convex/
    目录用于存放后端函数
  4. 实时同步函数到您的开发部署实例
  5. 创建
    .env.local
    文件,包含
    CONVEX_DEPLOYMENT
    NEXT_PUBLIC_CONVEX_URL
    环境变量

Folder Structure

目录结构

my-app/
├── convex/
│   ├── _generated/        ← Auto-generated (DO NOT EDIT)
│   │   ├── api.d.ts
│   │   ├── dataModel.d.ts
│   │   └── server.d.ts
│   ├── schema.ts          ← Database schema definition
│   ├── tasks.ts           ← Query/mutation functions
│   └── http.ts            ← HTTP actions (optional)
├── .env.local             ← CONVEX_DEPLOYMENT, NEXT_PUBLIC_CONVEX_URL
└── convex.json            ← Project config (optional)
my-app/
├── convex/
│   ├── _generated/        ← 自动生成目录(请勿编辑)
│   │   ├── api.d.ts
│   │   ├── dataModel.d.ts
│   │   └── server.d.ts
│   ├── schema.ts          ← 数据库schema定义文件
│   ├── tasks.ts           ← 查询/变更函数文件
│   └── http.ts            ← HTTP操作文件(可选)
├── .env.local             ← 包含CONVEX_DEPLOYMENT、NEXT_PUBLIC_CONVEX_URL
└── convex.json            ← 项目配置文件(可选)

Schema Design

Schema设计

Define your schema in
convex/schema.ts
using the validator library:
typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    avatarUrl: v.optional(v.string()),
    tokenIdentifier: v.string(),
  })
    .index("by_token", ["tokenIdentifier"])
    .index("by_email", ["email"]),

  messages: defineTable({
    authorId: v.id("users"),
    channelId: v.id("channels"),
    body: v.string(),
    attachmentId: v.optional(v.id("_storage")),
  })
    .index("by_channel", ["channelId"])
    .searchIndex("search_body", { searchField: "body" }),

  channels: defineTable({
    name: v.string(),
    description: v.optional(v.string()),
    isPrivate: v.boolean(),
  }),
});
convex/schema.ts
中使用验证器库定义您的schema:
typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    avatarUrl: v.optional(v.string()),
    tokenIdentifier: v.string(),
  })
    .index("by_token", ["tokenIdentifier"])
    .index("by_email", ["email"]),

  messages: defineTable({
    authorId: v.id("users"),
    channelId: v.id("channels"),
    body: v.string(),
    attachmentId: v.optional(v.id("_storage")),
  })
    .index("by_channel", ["channelId"])
    .searchIndex("search_body", { searchField: "body" }),

  channels: defineTable({
    name: v.string(),
    description: v.optional(v.string()),
    isPrivate: v.boolean(),
  }),
});

Validator Types

验证器类型

ValidatorTypeScript TypeNotes
v.string()
string
v.number()
number
IEEE 754 float
v.bigint()
bigint
v.boolean()
boolean
v.null()
null
v.id("tableName")
Id<"tableName">
Document reference
v.array(v.string())
string[]
v.object({...})
{...}
Nested objects
v.optional(v.string())
string | undefined
v.union(v.string(), v.number())
string | number
v.literal("active")
"active"
Literal types
v.bytes()
ArrayBuffer
Binary data
v.float64()
number
Explicit 64-bit float (used in vector indexes)
v.any()
any
Escape hatch
验证器TypeScript类型说明
v.string()
string
v.number()
number
IEEE 754浮点数
v.bigint()
bigint
v.boolean()
boolean
v.null()
null
v.id("tableName")
Id<"tableName">
文档引用类型
v.array(v.string())
string[]
v.object({...})
{...}
嵌套对象类型
v.optional(v.string())
string | undefined
可选类型
v.union(v.string(), v.number())
string | number
联合类型
v.literal("active")
"active"
字面量类型
v.bytes()
ArrayBuffer
二进制数据类型
v.float64()
number
显式64位浮点数(用于向量索引)
v.any()
any
兜底类型

Indexes

索引

typescript
// Single-field index
defineTable({ email: v.string() }).index("by_email", ["email"]);

// Compound index (order matters for range queries)
defineTable({
  orgId: v.string(),
  createdAt: v.number(),
}).index("by_org_and_date", ["orgId", "createdAt"]);

// Full-text search index
defineTable({ body: v.string(), channelId: v.id("channels") }).searchIndex(
  "search_body",
  {
    searchField: "body",
    filterFields: ["channelId"],
  },
);

// Vector search index (for AI/embeddings)
defineTable({ embedding: v.array(v.float64()), text: v.string() }).vectorIndex(
  "by_embedding",
  {
    vectorField: "embedding",
    dimensions: 1536,
  },
);
typescript
// 单字段索引
defineTable({ email: v.string() }).index("by_email", ["email"]);

// 复合索引(顺序对范围查询至关重要)
defineTable({
  orgId: v.string(),
  createdAt: v.number(),
}).index("by_org_and_date", ["orgId", "createdAt"]);

// 全文搜索索引
defineTable({ body: v.string(), channelId: v.id("channels") }).searchIndex(
  "search_body",
  {
    searchField: "body",
    filterFields: ["channelId"],
  },
);

// 向量搜索索引(用于AI/嵌入)
defineTable({ embedding: v.array(v.float64()), text: v.string() }).vectorIndex(
  "by_embedding",
  {
    vectorField: "embedding",
    dimensions: 1536,
  },
);

Writing Functions

函数编写

Queries (Read Data)

查询(读取数据)

Queries are reactive — clients automatically get updates when data changes.
typescript
import { query } from "./_generated/server";
import { v } from "convex/values";

// Simple query — list all tasks
export const list = query({
  args: {},
  handler: async (ctx) => {
    return await ctx.db.query("tasks").collect();
  },
});

// Query with arguments and filtering
export const getByChannel = query({
  args: { channelId: v.id("channels") },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
      .order("desc")
      .take(50);
  },
});

// Query with auth check
export const getMyProfile = query({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) return null;

    return await ctx.db
      .query("users")
      .withIndex("by_token", (q) =>
        q.eq("tokenIdentifier", identity.tokenIdentifier),
      )
      .unique();
  },
});
查询是响应式的——当数据变化时,客户端会自动获取更新。
typescript
import { query } from "./_generated/server";
import { v } from "convex/values";

// 简单查询——列出所有任务
export const list = query({
  args: {},
  handler: async (ctx) => {
    return await ctx.db.query("tasks").collect();
  },
});

// 带参数和过滤的查询
export const getByChannel = query({
  args: { channelId: v.id("channels") },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("messages")
      .withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
      .order("desc")
      .take(50);
  },
});

// 带身份校验的查询
export const getMyProfile = query({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) return null;

    return await ctx.db
      .query("users")
      .withIndex("by_token", (q) =>
        q.eq("tokenIdentifier", identity.tokenIdentifier),
      )
      .unique();
  },
});

Paginated Queries

分页查询

Use cursor-based pagination for lists or infinite scroll UIs.
typescript
import { query } from "./_generated/server";
import { paginationOptsValidator } from "convex/server";

export const listPaginated = query({
  args: {
    paginationOpts: paginationOptsValidator
  },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("messages")
      .order("desc")
      .paginate(args.paginationOpts);
  },
});
针对列表或无限滚动UI使用基于游标分页。
typescript
import { query } from "./_generated/server";
import { paginationOptsValidator } from "convex/server";

export const listPaginated = query({
  args: {
    paginationOpts: paginationOptsValidator
  },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("messages")
      .order("desc")
      .paginate(args.paginationOpts);
  },
});

Mutations (Write Data)

变更(写入数据)

Mutations run as ACID transactions with serializable isolation.
typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";

// Insert a document
export const create = mutation({
  args: { text: v.string(), isCompleted: v.boolean() },
  handler: async (ctx, args) => {
    const taskId = await ctx.db.insert("tasks", {
      text: args.text,
      isCompleted: args.isCompleted,
    });
    return taskId;
  },
});

// Update a document
export const update = mutation({
  args: { id: v.id("tasks"), isCompleted: v.boolean() },
  handler: async (ctx, args) => {
    await ctx.db.patch(args.id, { isCompleted: args.isCompleted });
  },
});

// Delete a document
export const remove = mutation({
  args: { id: v.id("tasks") },
  handler: async (ctx, args) => {
    await ctx.db.delete(args.id);
  },
});

// Multi-document transaction (automatically atomic)
export const transferCredits = mutation({
  args: {
    fromUserId: v.id("users"),
    toUserId: v.id("users"),
    amount: v.number(),
  },
  handler: async (ctx, args) => {
    const fromUser = await ctx.db.get(args.fromUserId);
    const toUser = await ctx.db.get(args.toUserId);
    if (!fromUser || !toUser) throw new Error("User not found");
    if (fromUser.credits < args.amount) throw new Error("Insufficient credits");

    await ctx.db.patch(args.fromUserId, {
      credits: fromUser.credits - args.amount,
    });
    await ctx.db.patch(args.toUserId, {
      credits: toUser.credits + args.amount,
    });
  },
});
变更以ACID事务执行,支持可序列化隔离级别。
typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";

// 插入文档
export const create = mutation({
  args: { text: v.string(), isCompleted: v.boolean() },
  handler: async (ctx, args) => {
    const taskId = await ctx.db.insert("tasks", {
      text: args.text,
      isCompleted: args.isCompleted,
    });
    return taskId;
  },
});

// 更新文档
export const update = mutation({
  args: { id: v.id("tasks"), isCompleted: v.boolean() },
  handler: async (ctx, args) => {
    await ctx.db.patch(args.id, { isCompleted: args.isCompleted });
  },
});

// 删除文档
export const remove = mutation({
  args: { id: v.id("tasks") },
  handler: async (ctx, args) => {
    await ctx.db.delete(args.id);
  },
});

// 多文档事务(自动原子化)
export const transferCredits = mutation({
  args: {
    fromUserId: v.id("users"),
    toUserId: v.id("users"),
    amount: v.number(),
  },
  handler: async (ctx, args) => {
    const fromUser = await ctx.db.get(args.fromUserId);
    const toUser = await ctx.db.get(args.toUserId);
    if (!fromUser || !toUser) throw new Error("用户未找到");
    if (fromUser.credits < args.amount) throw new Error("余额不足");

    await ctx.db.patch(args.fromUserId, {
      credits: fromUser.credits - args.amount,
    });
    await ctx.db.patch(args.toUserId, {
      credits: toUser.credits + args.amount,
    });
  },
});

Actions (External APIs & Side Effects)

操作(外部API与副作用)

Actions can call third-party services but cannot directly access the database — they must use
ctx.runQuery
and
ctx.runMutation
.
typescript
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api } from "./_generated/api";

export const sendEmail = action({
  args: { to: v.string(), subject: v.string(), body: v.string() },
  handler: async (ctx, args) => {
    // Call external API
    const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        personalizations: [{ to: [{ email: args.to }] }],
        from: { email: "noreply@example.com" },
        subject: args.subject,
        content: [{ type: "text/plain", value: args.body }],
      }),
    });

    if (!response.ok) throw new Error("Failed to send email");

    // Write result back to database via mutation
    await ctx.runMutation(api.emails.recordSent, {
      to: args.to,
      subject: args.subject,
      sentAt: Date.now(),
    });
  },
});

// Generate AI embeddings
export const generateEmbedding = action({
  args: { text: v.string(), documentId: v.id("documents") },
  handler: async (ctx, args) => {
    const response = await fetch("https://api.openai.com/v1/embeddings", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: "text-embedding-3-small",
        input: args.text,
      }),
    });

    const { data } = await response.json();
    await ctx.runMutation(api.documents.saveEmbedding, {
      documentId: args.documentId,
      embedding: data[0].embedding,
    });
  },
});
操作可以调用第三方服务,但不能直接访问数据库——必须使用
ctx.runQuery
ctx.runMutation
typescript
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api } from "./_generated/api";

export const sendEmail = action({
  args: { to: v.string(), subject: v.string(), body: v.string() },
  handler: async (ctx, args) => {
    // 调用外部API
    const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        personalizations: [{ to: [{ email: args.to }] }],
        from: { email: "noreply@example.com" },
        subject: args.subject,
        content: [{ type: "text/plain", value: args.body }],
      }),
    });

    if (!response.ok) throw new Error("邮件发送失败");

    // 通过变更将结果写入数据库
    await ctx.runMutation(api.emails.recordSent, {
      to: args.to,
      subject: args.subject,
      sentAt: Date.now(),
    });
  },
});

// 生成AI嵌入向量
export const generateEmbedding = action({
  args: { text: v.string(), documentId: v.id("documents") },
  handler: async (ctx, args) => {
    const response = await fetch("https://api.openai.com/v1/embeddings", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: "text-embedding-3-small",
        input: args.text,
      }),
    });

    const { data } = await response.json();
    await ctx.runMutation(api.documents.saveEmbedding, {
      documentId: args.documentId,
      embedding: data[0].embedding,
    });
  },
});

HTTP Actions (Webhooks)

HTTP操作(Webhook)

typescript
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
import { api } from "./_generated/api";

const http = httpRouter();

http.route({
  path: "/webhooks/stripe",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    const body = await request.text();
    const signature = request.headers.get("stripe-signature");

    // Verify webhook signature here...

    const event = JSON.parse(body);
    await ctx.runMutation(api.payments.handleWebhook, { event });

    return new Response("OK", { status: 200 });
  }),
});

export default http;
typescript
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
import { api } from "./_generated/api";

const http = httpRouter();

http.route({
  path: "/webhooks/stripe",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    const body = await request.text();
    const signature = request.headers.get("stripe-signature");

    // 在此验证Webhook签名...

    const event = JSON.parse(body);
    await ctx.runMutation(api.payments.handleWebhook, { event });

    return new Response("OK", { status: 200 });
  }),
});

export default http;

Client-Side Integration

客户端集成

React / Next.js

React / Next.js

typescript
// app/ConvexClientProvider.tsx
"use client";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import { ReactNode } from "react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
  return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}
typescript
// app/layout.tsx — wrap children
import { ConvexClientProvider } from "./ConvexClientProvider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ConvexClientProvider>{children}</ConvexClientProvider>
      </body>
    </html>
  );
}
typescript
// Component using Convex hooks
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";

export function TaskList() {
  // Reactive query — auto-updates when data changes
  const tasks = useQuery(api.tasks.list);
  const addTask = useMutation(api.tasks.create);
  const toggleTask = useMutation(api.tasks.update);

  if (tasks === undefined) return <p>Loading...</p>;

  return (
    <div>
      {tasks.map((task) => (
        <div key={task._id}>
          <input
            type="checkbox"
            checked={task.isCompleted}
            onChange={() =>
              toggleTask({ id: task._id, isCompleted: !task.isCompleted })
            }
          />
          {task.text}
        </div>
      ))}
      <button onClick={() => addTask({ text: "New task", isCompleted: false })}>
        Add Task
      </button>
    </div>
  );
}
typescript
// Component using Paginated Queries
"use client";
import { usePaginatedQuery } from "convex/react";
import { api } from "@/convex/_generated/api";

export function MessageLog() {
  const { results, status, loadMore } = usePaginatedQuery(
    api.messages.listPaginated,
    {}, // args
    { initialNumItems: 20 }
  );

  return (
    <div>
      {results.map((msg) => (
        <div key={msg._id}>{msg.body}</div>
      ))}

      {status === "LoadingFirstPage" && <p>Loading...</p>}

      {status === "CanLoadMore" && (
        <button onClick={() => loadMore(20)}>Load More</button>
      )}
    </div>
  );
}
typescript
// app/ConvexClientProvider.tsx
"use client";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import { ReactNode } from "react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
  return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}
typescript
// app/layout.tsx — 包裹子组件
import { ConvexClientProvider } from "./ConvexClientProvider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ConvexClientProvider>{children}</ConvexClientProvider>
      </body>
    </html>
  );
}
typescript
// 使用Convex钩子的组件
"use client";
import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";

export function TaskList() {
  // 响应式查询——数据变化时自动更新
  const tasks = useQuery(api.tasks.list);
  const addTask = useMutation(api.tasks.create);
  const toggleTask = useMutation(api.tasks.update);

  if (tasks === undefined) return <p>加载中...</p>;

  return (
    <div>
      {tasks.map((task) => (
        <div key={task._id}>
          <input
            type="checkbox"
            checked={task.isCompleted}
            onChange={() =>
              toggleTask({ id: task._id, isCompleted: !task.isCompleted })
            }
          />
          {task.text}
        </div>
      ))}
      <button onClick={() => addTask({ text: "新任务", isCompleted: false })}>
        添加任务
      </button>
    </div>
  );
}
typescript
// 使用分页查询的组件
"use client";
import { usePaginatedQuery } from "convex/react";
import { api } from "@/convex/_generated/api";

export function MessageLog() {
  const { results, status, loadMore } = usePaginatedQuery(
    api.messages.listPaginated,
    {}, // 参数
    { initialNumItems: 20 }
  );

  return (
    <div>
      {results.map((msg) => (
        <div key={msg._id}>{msg.body}</div>
      ))}

      {status === "LoadingFirstPage" && <p>加载中...</p>}

      {status === "CanLoadMore" && (
        <button onClick={() => loadMore(20)}>加载更多</button>
      )}
    </div>
  );
}

With Auth (First-Party Convex Auth)

集成身份认证(原生Convex Auth)

Convex provides a robust, native authentication library (
@convex-dev/auth
) featuring Magic Links, Passwords, and 80+ OAuth providers without needing a third-party service.
typescript
// app/ConvexClientProvider.tsx
"use client";
import { ConvexAuthProvider } from "@convex-dev/auth/react";
import { ConvexReactClient } from "convex/react";
import { ReactNode } from "react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
  return (
    <ConvexAuthProvider client={convex}>
      {children}
    </ConvexAuthProvider>
  );
}
typescript
// Client-side sign in
import { useAuthActions } from "@convex-dev/auth/react";

export function Login() {
  const { signIn } = useAuthActions();
  return <button onClick={() => signIn("github")}>Sign in with GitHub</button>;
}
Convex提供强大的原生身份认证库(
@convex-dev/auth
),支持魔法链接、密码登录及80+ OAuth提供商,无需依赖第三方服务。
typescript
// app/ConvexClientProvider.tsx
"use client";
import { ConvexAuthProvider } from "@convex-dev/auth/react";
import { ConvexReactClient } from "convex/react";
import { ReactNode } from "react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
  return (
    <ConvexAuthProvider client={convex}>
      {children}
    </ConvexAuthProvider>
  );
}
typescript
// 客户端登录
import { useAuthActions } from "@convex-dev/auth/react";

export function Login() {
  const { signIn } = useAuthActions();
  return <button onClick={() => signIn("github")}>使用GitHub登录</button>;
}

With Auth (Third-Party Clerk Example)

集成身份认证(第三方Clerk示例)

If you prefer a hosted third-party solution like Clerk:
typescript
// app/ConvexClientProvider.tsx
"use client";
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ClerkProvider, useAuth } from "@clerk/nextjs";
import { ConvexReactClient } from "convex/react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
  return (
    <ClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}>
      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
        {children}
      </ConvexProviderWithClerk>
    </ClerkProvider>
  );
}
如果您偏好托管式第三方解决方案如Clerk:
typescript
// app/ConvexClientProvider.tsx
"use client";
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ClerkProvider, useAuth } from "@clerk/nextjs";
import { ConvexReactClient } from "convex/react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: ReactNode }) {
  return (
    <ClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}>
      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
        {children}
      </ConvexProviderWithClerk>
    </ClerkProvider>
  );
}

With Auth (Better Auth Component)

集成身份认证(Better Auth组件)

Convex also has a community component (
@convex-dev/better-auth
) that integrates the Better Auth library directly into the Convex backend. This is currently in early alpha.
bash
npm install better-auth @convex-dev/better-auth
npx convex env set BETTER_AUTH_SECRET your-secret-here
npx convex env set SITE_URL http://localhost:3000
Better Auth provides email/password, social logins, two-factor authentication, and session management — all running inside Convex functions rather than an external auth server.
Convex还有社区组件(
@convex-dev/better-auth
),可将Better Auth库直接集成到Convex后端。目前处于早期测试版
bash
npm install better-auth @convex-dev/better-auth
npx convex env set BETTER_AUTH_SECRET your-secret-here
npx convex env set SITE_URL http://localhost:3000
Better Auth提供邮箱/密码登录、社交登录、双因素认证及会话管理——所有功能均运行在Convex函数内部,无需外部认证服务器。

Angular Integration

Angular集成

Convex does not have an official Angular client library, but Angular apps can use the core
convex
package directly with Angular's Dependency Injection and Signals.
typescript
// services/convex.service.ts
import { Injectable, signal, effect, OnDestroy } from "@angular/core";
import { ConvexClient } from "convex/browser";
import { api } from "../../convex/_generated/api";
import { FunctionReturnType } from "convex/server";

@Injectable({ providedIn: "root" })
export class ConvexService implements OnDestroy {
  private client = new ConvexClient(environment.convexUrl);

  // Reactive signal — updates automatically when data changes
  tasks = signal<FunctionReturnType<typeof api.tasks.list> | undefined>(
    undefined,
  );

  constructor() {
    // Subscribe to a reactive query
    this.client.onUpdate(api.tasks.list, {}, (result) => {
      this.tasks.set(result);
    });
  }

  async addTask(text: string) {
    await this.client.mutation(api.tasks.create, {
      text,
      isCompleted: false,
    });
  }

  ngOnDestroy() {
    this.client.close();
  }
}
typescript
// Component usage
import { Component, inject } from "@angular/core";
import { ConvexService } from "./services/convex.service";

@Component({
  selector: "app-task-list",
  template: `
    @if (convex.tasks(); as tasks) {
      @for (task of tasks; track task._id) {
        <div>{{ task.text }}</div>
      }
    } @else {
      <p>Loading...</p>
    }
    <button (click)="convex.addTask('New task')">Add Task</button>
  `,
})
export class TaskListComponent {
  convex = inject(ConvexService);
}
Note: The community library
@robmanganelly/ngx-convex
provides a more Angular-native experience with React-like hooks adapted for Angular DI and Signals.
Convex没有官方Angular客户端库,但Angular应用可直接使用核心
convex
包,结合Angular的依赖注入与Signals特性。
typescript
// services/convex.service.ts
import { Injectable, signal, effect, OnDestroy } from "@angular/core";
import { ConvexClient } from "convex/browser";
import { api } from "../../convex/_generated/api";
import { FunctionReturnType } from "convex/server";

@Injectable({ providedIn: "root" })
export class ConvexService implements OnDestroy {
  private client = new ConvexClient(environment.convexUrl);

  // 响应式信号——数据变化时自动更新
  tasks = signal<FunctionReturnType<typeof api.tasks.list> | undefined>(
    undefined,
  );

  constructor() {
    // 订阅响应式查询
    this.client.onUpdate(api.tasks.list, {}, (result) => {
      this.tasks.set(result);
    });
  }

  async addTask(text: string) {
    await this.client.mutation(api.tasks.create, {
      text,
      isCompleted: false,
    });
  }

  ngOnDestroy() {
    this.client.close();
  }
}
typescript
// 组件使用示例
import { Component, inject } from "@angular/core";
import { ConvexService } from "./services/convex.service";

@Component({
  selector: "app-task-list",
  template: `
    @if (convex.tasks(); as tasks) {
      @for (task of tasks; track task._id) {
        <div>{{ task.text }}</div>
      }
    } @else {
      <p>加载中...</p>
    }
    <button (click)="convex.addTask('新任务')">添加任务</button>
  `,
})
export class TaskListComponent {
  convex = inject(ConvexService);
}
注意: 社区库
@robmanganelly/ngx-convex
提供更贴合Angular原生体验的方案,将类React钩子适配为Angular DI与Signals特性。

Scheduling & Cron Jobs

任务调度与定时任务

One-off Scheduled Functions

一次性定时函数

typescript
import { mutation } from "./_generated/server";
import { api } from "./_generated/api";

export const sendReminder = mutation({
  args: { userId: v.id("users"), message: v.string(), delayMs: v.number() },
  handler: async (ctx, args) => {
    await ctx.scheduler.runAfter(args.delayMs, api.notifications.send, {
      userId: args.userId,
      message: args.message,
    });
  },
});
typescript
import { mutation } from "./_generated/server";
import { api } from "./_generated/api";

export const sendReminder = mutation({
  args: { userId: v.id("users"), message: v.string(), delayMs: v.number() },
  handler: async (ctx, args) => {
    await ctx.scheduler.runAfter(args.delayMs, api.notifications.send, {
      userId: args.userId,
      message: args.message,
    });
  },
});

Cron Jobs

定时任务(Cron Jobs)

typescript
// convex/crons.ts
import { cronJobs } from "convex/server";
import { api } from "./_generated/api";

const crons = cronJobs();

crons.interval("clear old logs", { hours: 24 }, api.logs.clearOld);

crons.cron(
  "weekly digest",
  "0 9 * * 1", // Every Monday at 9 AM
  api.emails.sendWeeklyDigest,
);

export default crons;
typescript
// convex/crons.ts
import { cronJobs } from "convex/server";
import { api } from "./_generated/api";

const crons = cronJobs();

crons.interval("清理旧日志", { hours: 24 }, api.logs.clearOld);

crons.cron(
  "每周摘要",
  "0 9 * * 1", // 每周一上午9点
  api.emails.sendWeeklyDigest,
);

export default crons;

File Storage

文件存储

typescript
// Generate an upload URL (mutation)
export const generateUploadUrl = mutation({
  args: {},
  handler: async (ctx) => {
    return await ctx.storage.generateUploadUrl();
  },
});

// Save file reference after upload (mutation)
export const saveFile = mutation({
  args: { storageId: v.id("_storage"), name: v.string() },
  handler: async (ctx, args) => {
    await ctx.db.insert("files", {
      storageId: args.storageId,
      name: args.name,
    });
  },
});

// Get a URL to serve a file (query)
export const getFileUrl = query({
  args: { storageId: v.id("_storage") },
  handler: async (ctx, args) => {
    return await ctx.storage.getUrl(args.storageId);
  },
});
typescript
// 生成上传URL(变更函数)
export const generateUploadUrl = mutation({
  args: {},
  handler: async (ctx) => {
    return await ctx.storage.generateUploadUrl();
  },
});

// 上传后保存文件引用(变更函数)
export const saveFile = mutation({
  args: { storageId: v.id("_storage"), name: v.string() },
  handler: async (ctx, args) => {
    await ctx.db.insert("files", {
      storageId: args.storageId,
      name: args.name,
    });
  },
});

// 获取文件访问URL(查询函数)
export const getFileUrl = query({
  args: { storageId: v.id("_storage") },
  handler: async (ctx, args) => {
    return await ctx.storage.getUrl(args.storageId);
  },
});

Environment Variables

环境变量

bash
undefined
bash
undefined

Set environment variables for your deployment

为部署实例设置环境变量

npx convex env set OPENAI_API_KEY sk-... npx convex env set SENDGRID_API_KEY SG...
npx convex env set OPENAI_API_KEY sk-... npx convex env set SENDGRID_API_KEY SG...

List current env vars

列出当前环境变量

npx convex env list
npx convex env list

Remove an env var

删除环境变量

npx convex env unset OPENAI_API_KEY

Access in actions (NOT in queries or mutations):

```typescript
// Only available in actions
const apiKey = process.env.OPENAI_API_KEY;
npx convex env unset OPENAI_API_KEY

在操作函数中访问(查询和变更函数中不可用):

```typescript
// 仅在操作函数中可用
const apiKey = process.env.OPENAI_API_KEY;

Deployment & CLI

部署与CLI

bash
undefined
bash
undefined

Development (watches for changes, syncs to dev deployment)

开发模式(监听变更,实时同步到开发部署实例)

npx convex dev
npx convex dev

Deploy to production

部署到生产环境

npx convex deploy
npx convex deploy

Import data

导入数据

npx convex import --table tasks data.jsonl
npx convex import --table tasks data.jsonl

Export data

导出数据

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

Open Convex dashboard

打开Convex控制台

npx convex dashboard
npx convex dashboard

Run a function from CLI

从CLI运行函数

npx convex run tasks:list
npx convex run tasks:list

View logs

查看日志

npx convex logs
undefined
npx convex logs
undefined

Best Practices

最佳实践

  • ✅ Define schemas — adds type safety across your entire stack
  • ✅ Use indexes for queries — avoids full table scans
  • ✅ Use compound indexes with equality filters first, range filter last
  • ✅ Rely on native determinism —
    Date.now()
    and
    Math.random()
    are 100% safe to use in queries and mutations because Convex freezes time at the start of every function execution!
  • ✅ Use
    v.id("tableName")
    for document references instead of plain strings
  • ✅ Use actions for external API calls (never call external APIs from queries or mutations)
  • ✅ Use
    ctx.runQuery
    /
    ctx.runMutation
    from actions — never access
    ctx.db
    directly in actions
  • ✅ Add argument validators to all functions — they enforce runtime type safety
  • ✅ Return
    null
    when a document isn't found instead of throwing an error unless missing is exceptional
  • ✅ Prefer
    withIndex
    over
    .filter()
    for query performance
  • ✅ 定义schema——为整个技术栈添加类型安全保障
  • ✅ 为查询使用索引——避免全表扫描
  • ✅ 复合索引先使用等值过滤,再使用范围过滤
  • ✅ 依赖原生确定性——
    Date.now()
    Math.random()
    在查询和变更函数中完全安全,因为Convex会在每个函数执行开始时冻结时间!
  • ✅ 使用
    v.id("tableName")
    作为文档引用,而非普通字符串
  • ✅ 使用操作函数调用外部API(切勿在查询或变更函数中调用外部API)
  • ✅ 在操作函数中使用
    ctx.runQuery
    /
    ctx.runMutation
    ——切勿在操作函数中直接访问
    ctx.db
  • ✅ 为所有函数添加参数验证器——确保运行时类型安全
  • ✅ 当文档未找到时返回
    null
    而非抛出错误,除非缺失属于异常情况
  • ✅ 优先使用
    withIndex
    而非
    .filter()
    以提升查询性能

Anti-Patterns to Avoid

需避免的反模式

  1. ❌ External API calls in queries/mutations: Only actions can call external services. Queries and mutations run in the Convex transaction engine.
  2. ❌ Doing slow CPU-bound work in mutations: Mutations block database commits; offload heavy processing to actions.
  3. ❌ Using
    .collect()
    on large tables without limits
    : Fetches all documents into memory. Use
    .take(N)
    or
    .paginate()
    .
  4. ❌ Skipping schema definition: Without a schema you lose end-to-end type safety, the main Convex advantage.
  5. ❌ Using
    .filter()
    instead of indexes
    :
    .filter()
    does a full table scan. Define an index and use
    .withIndex()
    .
  6. ❌ Storing large blobs in documents: Use Convex file storage (
    _storage
    ) for files; keep documents lean.
  7. ❌ Circular
    runQuery
    /
    runMutation
    chains
    : Actions calling mutations that schedule actions can create infinite loops.
  1. ❌ 在查询/变更函数中调用外部API:只有操作函数可以调用外部服务。查询和变更函数运行在Convex事务引擎中。
  2. ❌ 在变更函数中执行耗时的CPU密集型工作:变更会阻塞数据库提交;将繁重处理任务转移到操作函数中。
  3. ❌ 对大型表无限制使用
    .collect()
    :会将所有文档加载到内存中。使用
    .take(N)
    .paginate()
  4. ❌ 跳过schema定义:没有schema会失去端到端类型安全,这是Convex的核心优势。
  5. ❌ 使用
    .filter()
    而非索引
    .filter()
    会执行全表扫描。定义索引并使用
    .withIndex()
  6. ❌ 在文档中存储大对象:使用Convex文件存储(
    _storage
    )存储文件;保持文档轻量化。
  7. ❌ 循环调用
    runQuery
    /
    runMutation
    :操作函数调用变更函数,变更函数又调度操作函数可能会导致无限循环。

Common Pitfalls

常见问题

  • Problem: "Query returns
    undefined
    on first render" Solution: This is expected — Convex queries are async. Check for
    undefined
    before rendering (this means loading, not empty).
  • Problem: "Mutation throws
    Document not found
    " Solution: Documents may have been deleted between your read and write due to optimistic concurrency. Re-read inside the mutation.
  • Problem: "
    process.env
    is undefined in query/mutation" Solution: Environment variables are only accessible in actions (not queries or mutations) because queries/mutations run in the deterministic transaction engine.
  • Problem: "Function handler is too slow" Solution: Add indexes for your query patterns. Use
    withIndex()
    instead of
    .filter()
    . For complex operations, break into smaller mutations.
  • Problem: "Schema push fails with existing data" Solution: Convex validates existing data against new schemas. Either migrate existing documents first, or use
    v.optional()
    for new fields.
  • 问题: "首次渲染时查询返回
    undefined
    " 解决方案: 这是预期行为——Convex查询是异步的。渲染前检查
    undefined
    (表示加载中,而非空数据)。
  • 问题: "变更函数抛出
    Document not found
    " 解决方案: 由于乐观并发控制,在您读取和写入之间文档可能已被删除。在变更函数内部重新读取文档。
  • 问题: "查询/变更函数中
    process.env
    未定义" 解决方案: 环境变量仅在操作函数中可用(查询和变更函数中不可用),因为查询/变更函数运行在确定性事务引擎中。
  • 问题: "函数处理速度过慢" 解决方案: 为查询模式添加索引。使用
    withIndex()
    替代
    .filter()
    。对于复杂操作,拆分为更小的变更函数。
  • 问题: "Schema推送失败,与现有数据冲突" 解决方案: Convex会验证现有数据是否符合新schema。先迁移现有文档,或为新字段使用
    v.optional()

Limitations

限制

  • Queries and mutations cannot call external HTTP APIs (use actions instead)
  • No raw SQL — you work with the Convex query builder API
  • Environment variables only available in actions, not in queries or mutations
  • Document size limit of 1MB
  • Maximum function execution time limits apply
  • No server-side rendering of Convex data without specific SSR patterns (use preloading)
  • Schemas are enforced at write-time; changing schemas requires data migration for existing documents
  • 查询和变更函数无法调用外部HTTP API(使用操作函数替代)
  • 不支持原生SQL——需使用Convex查询构建器API
  • 环境变量仅在操作函数中可用,查询和变更函数中不可用
  • 文档大小限制为1MB
  • 函数执行时间存在上限
  • 若无特定SSR模式,无法服务端渲染Convex数据(使用预加载)
  • Schema在写入时生效;修改Schema需对现有文档进行数据迁移

Related Skills

相关技能

  • @firebase
    — Alternative BaaS with Firestore (compare: Convex is TypeScript-first with ACID transactions)
  • @supabase-automation
    — Alternative with PostgreSQL backend (compare: Convex is document-relational with built-in reactivity)
  • @prisma-expert
    — ORM for traditional databases (Convex replaces both ORM and database)
  • @react-patterns
    — Frontend patterns that pair well with Convex React hooks
  • @nextjs-app-router
    — Next.js App Router integration patterns
  • @authentication-oauth
    — Auth patterns (Convex supports Clerk, Auth0, Convex Auth)
  • @stripe
    — Payment integration via Convex actions and HTTP webhooks
  • @firebase
    — 替代BaaS方案,搭配Firestore(对比:Convex以TypeScript优先,支持ACID事务)
  • @supabase-automation
    — 替代方案,基于PostgreSQL后端(对比:Convex是文档关系型,内置响应式特性)
  • @prisma-expert
    — 传统数据库ORM(Convex可同时替代ORM和数据库)
  • @react-patterns
    — 与Convex React钩子适配的前端模式
  • @nextjs-app-router
    — Next.js App Router集成模式
  • @authentication-oauth
    — 身份认证模式(Convex支持Clerk、Auth0、Convex Auth)
  • @stripe
    — 通过Convex操作函数和HTTP Webhook集成支付功能

Resources

资源