关于 convex
面向使用 Convex 和 TypeScript 构建后端的开发者,提供查询、变更、操作及实时数据同步的编码指南。内容也涵盖索引、HTTP 路由、定时任务和文件上传。
主要功能
- 01编写查询、变更和操作
- 02使用索引与参数校验
- 03配置 HTTP 路由和定时任务
- 04处理文件上传
- 05使用实时订阅
适用场景
- 01实现 Convex 数据查询与写入
- 02构建外部 API 调用和副作用
- 03设置 Webhook 与周期任务
- 04为客户端实现文件上传和实时更新
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill convex安装后在 Claude Code 中输入/convex确认已加载。
详细介绍
Convex 开发指南
你精通 Convex 后端开发、TypeScript 和实时数据同步模式。
通用开发规范
代码风格与结构
- 使用简洁的 TypeScript,采用函数声明、迭代器和模块
- 使用描述性变量名,并包含辅助动词(例如,isLoading、hasError)
- 按导出的组件、子组件、辅助函数和静态类型组织代码
- 目录使用 dash-case,并采用命名导出
- 优先使用接口而非类型;使用联合类型代替枚举
- 使用函数组件和声明式 JSX 模式
错误处理
- 在函数中尽早通过守卫子句处理错误
- 适当地记录错误以便调试
- 提供对用户友好的错误消息
- 使用 Zod 进行表单验证
- 在 React 组件中实现恰当的错误边界
UI 框架集成
- 使用 Shadcn UI 和 Radix UI 作为组件基础
- 使用 Tailwind CSS 编写样式,采用响应式、移动端优先的设计
- 尽量减少 useClient、useEffect 和 useState 的使用
- 在适用的场景中使用 React Server Components
- 使用 Suspense 处理加载状态,并通过动态加载拆分代码
Convex 专用模式
查询
使用 query 构造函数组织查询:
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();
},
});
重要提示:优先使用 Convex 索引而非筛选器,以获得更好的性能。在 schema.ts 中使用 .index() 方法定义索引,然后通过 .withIndex() 查询。
突变
组织用于数据库写入的突变:
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(),
});
},
});
操作
使用操作进行外部 API 调用和副作用:
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;
},
});
使用索引定义架构
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 路由器
为 Webhook 和外部集成定义 HTTP 路由:
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;
计划任务
为定期任务实现 cron 作业:
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;
文件处理
文件上传分为三个步骤:
// 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,
});
},
});
最佳实践
- 始终为筛选或排序数据的查询使用索引
- 使用 Convex 验证器(
v.string()、v.number()等)验证参数 - 在需要身份验证的处理程序中尽早检查身份验证
- 对于不应向客户端公开的操作,使用内部函数
- 利用实时订阅——Convex 查询会在数据更改时自动更新
- 保持突变精简,专注于单一操作
- 使用操作处理副作用——切勿从查询或突变中调用外部 API
- 妥善处理错误,向用户提供恰当的错误消息
性能注意事项
- 尽可能使用
.withIndex()代替.filter() - 使用
.paginate()对大型结果集进行分页 - 预期只返回单个结果时,使用
.first()代替.collect() - 对频繁访问的数据考虑进行数据反规范化
- 使用 Convex 内置的缓存,避免自行实现缓存
评论
支持 Markdown