convex

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Convex 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
query
constructor:
typescript
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
.index()
method, then query with
.withIndex()
.
使用
query
构造函数构建查询:
typescript
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

最佳实践

  1. Always use indexes for queries that filter or sort data
  2. Validate arguments using Convex validators (
    v.string()
    ,
    v.number()
    , etc.)
  3. Check authentication early in handlers that require it
  4. Use internal functions for operations that should not be exposed to clients
  5. Leverage real-time subscriptions - Convex queries automatically update when data changes
  6. Keep mutations small and focused on single operations
  7. Use actions for side effects - never call external APIs from queries or mutations
  8. Handle errors gracefully with proper error messages for users
  1. 始终使用索引:对需要过滤或排序数据的查询使用索引
  2. 验证参数:使用Convex验证器(
    v.string()
    v.number()
    等)验证参数
  3. 尽早检查认证:在需要认证的处理函数中尽早检查用户认证状态
  4. 使用内部函数:对于不应暴露给客户端的操作,使用内部函数
  5. 利用实时订阅:Convex查询会在数据变化时自动更新
  6. 保持变更操作简洁:专注于单一操作
  7. 使用操作处理副作用:切勿在查询或变更操作中调用外部API
  8. 优雅处理错误:为用户提供合适的错误提示信息

Performance Considerations

性能考量

  • Use
    .withIndex()
    instead of
    .filter()
    whenever possible
  • Paginate large result sets using
    .paginate()
  • Use
    .first()
    instead of
    .collect()
    when expecting a single result
  • Consider data denormalization for frequently accessed data
  • Use Convex's built-in caching - avoid implementing your own
  • 尽可能使用
    .withIndex()
    而非
    .filter()
  • 使用
    .paginate()
    对大型结果集进行分页
  • 当预期仅返回单个结果时,使用
    .first()
    而非
    .collect()
  • 对于频繁访问的数据,考虑数据反规范化
  • 使用Convex内置的缓存机制 - 避免自行实现缓存