convex
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConvex
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
函数类型
| Type | Purpose | Can Read DB | Can Write DB | Can Call External APIs | Cached/Reactive |
|---|---|---|---|---|---|
| Query | Read data | ✅ | ❌ | ❌ | ✅ |
| Mutation | Write data | ✅ | ✅ | ❌ | ❌ |
| Action | Side effects | via | via | ✅ | ❌ |
| HTTP Action | Webhooks/custom endpoints | via | via | ✅ | ❌ |
| 类型 | 用途 | 可读取数据库 | 可写入数据库 | 可调用外部API | 缓存/响应式 |
|---|---|---|---|---|---|
| Query(查询) | 读取数据 | ✅ | ❌ | ❌ | ✅ |
| Mutation(变更) | 写入数据 | ✅ | ✅ | ❌ | ❌ |
| Action(操作) | 处理副作用 | 通过 | 通过 | ✅ | ❌ |
| HTTP Action(HTTP操作) | Webhook/自定义端点 | 通过 | 通过 | ✅ | ❌ |
Project Setup
项目设置
New Project (Next.js)
新项目(Next.js)
bash
npx create-next-app@latest my-app
cd my-app && npm install convex
npx convex devbash
npx create-next-app@latest my-app
cd my-app && npm install convex
npx convex devAdd to Existing Project
集成到现有项目
bash
npm install convex
npx convex devThe command:
npx convex dev- Prompts you to log in (GitHub)
- Creates a project and deployment
- Generates folder for backend functions
convex/ - Syncs functions to your dev deployment in real-time
- Creates with
.env.localandCONVEX_DEPLOYMENTNEXT_PUBLIC_CONVEX_URL
bash
npm install convex
npx convex devnpx convex dev- 提示您登录(GitHub账号)
- 创建项目及部署实例
- 生成目录用于存放后端函数
convex/ - 实时同步函数到您的开发部署实例
- 创建文件,包含
.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 using the validator library:
convex/schema.tstypescript
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(),
}),
});在中使用验证器库定义您的schema:
convex/schema.tstypescript
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
验证器类型
| Validator | TypeScript Type | Notes |
|---|---|---|
| | |
| | IEEE 754 float |
| | |
| | |
| | |
| | Document reference |
| | |
| | Nested objects |
| | |
| | |
| | Literal types |
| | Binary data |
| | Explicit 64-bit float (used in vector indexes) |
| | Escape hatch |
| 验证器 | TypeScript类型 | 说明 |
|---|---|---|
| | |
| | IEEE 754浮点数 |
| | |
| | |
| | |
| | 文档引用类型 |
| | |
| | 嵌套对象类型 |
| | 可选类型 |
| | 联合类型 |
| | 字面量类型 |
| | 二进制数据类型 |
| | 显式64位浮点数(用于向量索引) |
| | 兜底类型 |
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 and .
ctx.runQueryctx.runMutationtypescript
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.runQueryctx.runMutationtypescript
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 () featuring Magic Links, Passwords, and 80+ OAuth providers without needing a third-party service.
@convex-dev/authtypescript
// 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提供强大的原生身份认证库(),支持魔法链接、密码登录及80+ OAuth提供商,无需依赖第三方服务。
@convex-dev/authtypescript
// 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 () that integrates the Better Auth library directly into the Convex backend. This is currently in early alpha.
@convex-dev/better-authbash
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:3000Better Auth provides email/password, social logins, two-factor authentication, and session management — all running inside Convex functions rather than an external auth server.
Convex还有社区组件(),可将Better Auth库直接集成到Convex后端。目前处于早期测试版。
@convex-dev/better-authbash
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:3000Better Auth提供邮箱/密码登录、社交登录、双因素认证及会话管理——所有功能均运行在Convex函数内部,无需外部认证服务器。
Angular Integration
Angular集成
Convex does not have an official Angular client library, but Angular apps can use the core package directly with Angular's Dependency Injection and Signals.
convextypescript
// 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 libraryprovides a more Angular-native experience with React-like hooks adapted for Angular DI and Signals.@robmanganelly/ngx-convex
Convex没有官方Angular客户端库,但Angular应用可直接使用核心包,结合Angular的依赖注入与Signals特性。
convextypescript
// 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);
}注意: 社区库提供更贴合Angular原生体验的方案,将类React钩子适配为Angular DI与Signals特性。@robmanganelly/ngx-convex
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
undefinedbash
undefinedSet 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
undefinedbash
undefinedDevelopment (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
undefinednpx convex logs
undefinedBest 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 — and
Date.now()are 100% safe to use in queries and mutations because Convex freezes time at the start of every function execution!Math.random() - ✅ Use for document references instead of plain strings
v.id("tableName") - ✅ Use actions for external API calls (never call external APIs from queries or mutations)
- ✅ Use /
ctx.runQueryfrom actions — never accessctx.runMutationdirectly in actionsctx.db - ✅ Add argument validators to all functions — they enforce runtime type safety
- ✅ Return when a document isn't found instead of throwing an error unless missing is exceptional
null - ✅ Prefer over
withIndexfor query performance.filter()
- ✅ 定义schema——为整个技术栈添加类型安全保障
- ✅ 为查询使用索引——避免全表扫描
- ✅ 复合索引先使用等值过滤,再使用范围过滤
- ✅ 依赖原生确定性——和
Date.now()在查询和变更函数中完全安全,因为Convex会在每个函数执行开始时冻结时间!Math.random() - ✅ 使用作为文档引用,而非普通字符串
v.id("tableName") - ✅ 使用操作函数调用外部API(切勿在查询或变更函数中调用外部API)
- ✅ 在操作函数中使用/
ctx.runQuery——切勿在操作函数中直接访问ctx.runMutationctx.db - ✅ 为所有函数添加参数验证器——确保运行时类型安全
- ✅ 当文档未找到时返回而非抛出错误,除非缺失属于异常情况
null - ✅ 优先使用而非
withIndex以提升查询性能.filter()
Anti-Patterns to Avoid
需避免的反模式
- ❌ External API calls in queries/mutations: Only actions can call external services. Queries and mutations run in the Convex transaction engine.
- ❌ Doing slow CPU-bound work in mutations: Mutations block database commits; offload heavy processing to actions.
- ❌ Using on large tables without limits: Fetches all documents into memory. Use
.collect()or.take(N)..paginate() - ❌ Skipping schema definition: Without a schema you lose end-to-end type safety, the main Convex advantage.
- ❌ Using instead of indexes:
.filter()does a full table scan. Define an index and use.filter()..withIndex() - ❌ Storing large blobs in documents: Use Convex file storage () for files; keep documents lean.
_storage - ❌ Circular /
runQuerychains: Actions calling mutations that schedule actions can create infinite loops.runMutation
- ❌ 在查询/变更函数中调用外部API:只有操作函数可以调用外部服务。查询和变更函数运行在Convex事务引擎中。
- ❌ 在变更函数中执行耗时的CPU密集型工作:变更会阻塞数据库提交;将繁重处理任务转移到操作函数中。
- ❌ 对大型表无限制使用:会将所有文档加载到内存中。使用
.collect()或.take(N)。.paginate() - ❌ 跳过schema定义:没有schema会失去端到端类型安全,这是Convex的核心优势。
- ❌ 使用而非索引:
.filter()会执行全表扫描。定义索引并使用.filter()。.withIndex() - ❌ 在文档中存储大对象:使用Convex文件存储()存储文件;保持文档轻量化。
_storage - ❌ 循环调用/
runQuery:操作函数调用变更函数,变更函数又调度操作函数可能会导致无限循环。runMutation
Common Pitfalls
常见问题
-
Problem: "Query returnson first render" Solution: This is expected — Convex queries are async. Check for
undefinedbefore rendering (this means loading, not empty).undefined -
Problem: "Mutation throws" Solution: Documents may have been deleted between your read and write due to optimistic concurrency. Re-read inside the mutation.
Document not found -
Problem: "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.
process.env -
Problem: "Function handler is too slow" Solution: Add indexes for your query patterns. Useinstead of
withIndex(). For complex operations, break into smaller mutations..filter() -
Problem: "Schema push fails with existing data" Solution: Convex validates existing data against new schemas. Either migrate existing documents first, or usefor new fields.
v.optional()
-
问题: "首次渲染时查询返回" 解决方案: 这是预期行为——Convex查询是异步的。渲染前检查
undefined(表示加载中,而非空数据)。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
相关技能
- — Alternative BaaS with Firestore (compare: Convex is TypeScript-first with ACID transactions)
@firebase - — Alternative with PostgreSQL backend (compare: Convex is document-relational with built-in reactivity)
@supabase-automation - — ORM for traditional databases (Convex replaces both ORM and database)
@prisma-expert - — Frontend patterns that pair well with Convex React hooks
@react-patterns - — Next.js App Router integration patterns
@nextjs-app-router - — Auth patterns (Convex supports Clerk, Auth0, Convex Auth)
@authentication-oauth - — Payment integration via Convex actions and HTTP webhooks
@stripe
- — 替代BaaS方案,搭配Firestore(对比:Convex以TypeScript优先,支持ACID事务)
@firebase - — 替代方案,基于PostgreSQL后端(对比:Convex是文档关系型,内置响应式特性)
@supabase-automation - — 传统数据库ORM(Convex可同时替代ORM和数据库)
@prisma-expert - — 与Convex React钩子适配的前端模式
@react-patterns - — Next.js App Router集成模式
@nextjs-app-router - — 身份认证模式(Convex支持Clerk、Auth0、Convex Auth)
@authentication-oauth - — 通过Convex操作函数和HTTP Webhook集成支付功能
@stripe