convex
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConvex Development Guidelines
Convex开发指南
You are an expert in Convex backend development, TypeScript, and real-time data synchronization patterns.
您是Convex后端开发、TypeScript和实时数据同步模式方面的专家。
General Development Specifications
通用开发规范
Code Style and Structure
代码风格与结构
- Write concise TypeScript using functional declarations, iterators, and modules
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
- Structure code with exported components, subcomponents, helpers, and static types
- Use dash-case for directories with named exports
- Prefer interfaces over types; avoid enums in favor of union types
- Use functional components with declarative JSX patterns
- 使用函数声明、迭代器和模块编写简洁的TypeScript代码
- 使用带有辅助动词的描述性变量名(例如:isLoading、hasError)
- 按照导出组件、子组件、辅助函数和静态类型组织代码结构
- 目录使用短横线命名法(dash-case),配合命名导出
- 优先使用接口而非类型别名;避免使用枚举,改用联合类型
- 使用函数组件和声明式JSX模式
Error Handling
错误处理
- Handle errors early in functions with guard clauses
- Log errors appropriately for debugging
- Provide user-friendly error messages
- Use Zod for form validation
- Implement proper error boundaries in React components
- 在函数中尽早使用守卫子句处理错误
- 合理记录错误以便调试
- 提供用户友好的错误提示信息
- 使用Zod进行表单验证
- 在React组件中实现适当的错误边界
UI Framework Integration
UI框架集成
- Use Shadcn UI and Radix UI for component primitives
- Style with Tailwind CSS using responsive, mobile-first design
- Minimize useClient, useEffect, and useState usage
- Leverage React Server Components where applicable
- Use Suspense for loading states and dynamic loading for code splitting
- 使用Shadcn UI和Radix UI作为组件基础
- 使用Tailwind CSS进行样式设计,采用响应式、移动优先的设计思路
- 尽量减少useClient、useEffect和useState的使用
- 尽可能利用React Server Components
- 使用Suspense处理加载状态,通过动态加载实现代码分割
Convex-Specific Patterns
Convex特定模式
Queries
查询
Structure queries using the constructor:
querytypescript
import { query } from "./_generated/server";
import { v } from "convex/values";
export const getItems = query({
args: {
status: v.optional(v.string()),
},
handler: async (ctx, args) => {
// ctx provides: db, storage, auth
const identity = await ctx.auth.getUserIdentity();
if (args.status) {
return await ctx.db
.query("items")
.withIndex("by_status", (q) => q.eq("status", args.status))
.collect();
}
return await ctx.db.query("items").collect();
},
});Important: Prefer Convex indexes over filters for better performance. Define indexes in schema.ts using the method, then query with .
.index().withIndex()使用构造函数构建查询:
querytypescript
import { query } from "./_generated/server";
import { v } from "convex/values";
export const getItems = query({
args: {
status: v.optional(v.string()),
},
handler: async (ctx, args) => {
// ctx提供:db, storage, auth
const identity = await ctx.auth.getUserIdentity();
if (args.status) {
return await ctx.db
.query("items")
.withIndex("by_status", (q) => q.eq("status", args.status))
.collect();
}
return await ctx.db.query("items").collect();
},
});重要提示:为获得更好的性能,优先使用Convex索引而非过滤器。在schema.ts中使用方法定义索引,然后通过进行查询。
.index().withIndex()Mutations
变更
Structure mutations for database writes:
typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const createItem = mutation({
args: {
title: v.string(),
description: v.optional(v.string()),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
return await ctx.db.insert("items", {
title: args.title,
description: args.description,
userId: identity.subject,
createdAt: Date.now(),
});
},
});构建用于数据库写入的变更操作:
typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const createItem = mutation({
args: {
title: v.string(),
description: v.optional(v.string()),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("未认证");
}
return await ctx.db.insert("items", {
title: args.title,
description: args.description,
userId: identity.subject,
createdAt: Date.now(),
});
},
});Actions
操作
Use actions for external API calls and side effects:
typescript
import { action } from "./_generated/server";
import { v } from "convex/values";
export const sendEmail = action({
args: {
to: v.string(),
subject: v.string(),
body: v.string(),
},
handler: async (ctx, args) => {
// Actions can call external APIs
const response = await fetch("https://api.email-service.com/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
});
return response.ok;
},
});使用操作处理外部API调用和副作用:
typescript
import { action } from "./_generated/server";
import { v } from "convex/values";
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.email-service.com/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
});
return response.ok;
},
});Schema Definition with Indexes
带索引的Schema定义
typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
items: defineTable({
title: v.string(),
description: v.optional(v.string()),
status: v.string(),
userId: v.string(),
createdAt: v.number(),
})
.index("by_status", ["status"])
.index("by_user", ["userId"])
.index("by_user_and_status", ["userId", "status"]),
});typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
items: defineTable({
title: v.string(),
description: v.optional(v.string()),
status: v.string(),
userId: v.string(),
createdAt: v.number(),
})
.index("by_status", ["status"])
.index("by_user", ["userId"])
.index("by_user_and_status", ["userId", "status"]),
});HTTP Router
HTTP路由
Define HTTP routes for webhooks and external integrations:
typescript
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
const http = httpRouter();
http.route({
path: "/webhook",
method: "POST",
handler: httpAction(async (ctx, request) => {
const body = await request.json();
// Process webhook
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}),
});
export default http;为Webhook和外部集成定义HTTP路由:
typescript
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
const http = httpRouter();
http.route({
path: "/webhook",
method: "POST",
handler: httpAction(async (ctx, request) => {
const body = await request.json();
// 处理Webhook
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}),
});
export default http;Scheduled Jobs
定时任务
Implement cron jobs for recurring tasks:
typescript
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// Run every hour
crons.interval(
"cleanup-old-items",
{ hours: 1 },
internal.tasks.cleanupOldItems
);
// Run at specific time (daily at midnight UTC)
crons.monthly(
"monthly-report",
{ day: 1, hourUTC: 0, minuteUTC: 0 },
internal.reports.generateMonthlyReport
);
export default crons;实现用于重复任务的Cron作业:
typescript
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// 每小时运行一次
crons.interval(
"cleanup-old-items",
{ hours: 1 },
internal.tasks.cleanupOldItems
);
// 在特定时间运行(UTC时间每日午夜)
crons.monthly(
"monthly-report",
{ day: 1, hourUTC: 0, minuteUTC: 0 },
internal.reports.generateMonthlyReport
);
export default crons;File Handling
文件处理
Three-step process for file uploads:
typescript
// 1. Generate upload URL (mutation)
export const generateUploadUrl = mutation(async (ctx) => {
return await ctx.storage.generateUploadUrl();
});
// 2. Client POSTs file to the URL
// const uploadUrl = await generateUploadUrl();
// const response = await fetch(uploadUrl, { method: "POST", body: file });
// const { storageId } = await response.json();
// 3. Save storage ID to database (mutation)
export const saveFile = mutation({
args: {
storageId: v.id("_storage"),
filename: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db.insert("files", {
storageId: args.storageId,
filename: args.filename,
});
},
});文件上传分为三个步骤:
typescript
// 1. 生成上传URL(变更操作)
export const generateUploadUrl = mutation(async (ctx) => {
return await ctx.storage.generateUploadUrl();
});
// 2. 客户端将文件POST到该URL
// const uploadUrl = await generateUploadUrl();
// const response = await fetch(uploadUrl, { method: "POST", body: file });
// const { storageId } = await response.json();
// 3. 将存储ID保存到数据库(变更操作)
export const saveFile = mutation({
args: {
storageId: v.id("_storage"),
filename: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db.insert("files", {
storageId: args.storageId,
filename: args.filename,
});
},
});Best Practices
最佳实践
- Always use indexes for queries that filter or sort data
- Validate arguments using Convex validators (,
v.string(), etc.)v.number() - Check authentication early in handlers that require it
- Use internal functions for operations that should not be exposed to clients
- Leverage real-time subscriptions - Convex queries automatically update when data changes
- Keep mutations small and focused on single operations
- Use actions for side effects - never call external APIs from queries or mutations
- Handle errors gracefully with proper error messages for users
- 始终使用索引:对需要过滤或排序数据的查询使用索引
- 验证参数:使用Convex验证器(、
v.string()等)验证参数v.number() - 尽早检查认证:在需要认证的处理函数中尽早检查用户认证状态
- 使用内部函数:对于不应暴露给客户端的操作,使用内部函数
- 利用实时订阅:Convex查询会在数据变化时自动更新
- 保持变更操作简洁:专注于单一操作
- 使用操作处理副作用:切勿在查询或变更操作中调用外部API
- 优雅处理错误:为用户提供合适的错误提示信息
Performance Considerations
性能考量
- Use instead of
.withIndex()whenever possible.filter() - Paginate large result sets using
.paginate() - Use instead of
.first()when expecting a single result.collect() - Consider data denormalization for frequently accessed data
- Use Convex's built-in caching - avoid implementing your own
- 尽可能使用而非
.withIndex().filter() - 使用对大型结果集进行分页
.paginate() - 当预期仅返回单个结果时,使用而非
.first().collect() - 对于频繁访问的数据,考虑数据反规范化
- 使用Convex内置的缓存机制 - 避免自行实现缓存