components-guide
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConvex Components Guide
Convex 组件指南
Use components to encapsulate features and build maintainable, reusable backends.
使用组件封装功能,构建可维护、可复用的后端。
What Are Convex Components?
什么是Convex组件?
Components are self-contained mini-backends that bundle:
- Their own database schema
- Their own functions (queries, mutations, actions)
- Their own data (isolated tables)
- Clear API boundaries
Think of them as: npm packages for your backend, or microservices without the deployment complexity.
组件是独立的迷你后端,包含:
- 专属数据库模式
- 专属函数(查询、变更、操作)
- 专属数据(隔离的表)
- 清晰的API边界
你可以将其理解为: 后端的npm包,或是无需复杂部署的微服务。
Why Use Components?
为什么使用组件?
Traditional Approach (Monolithic)
传统单体方案
convex/
├── users.ts (500 lines)
├── files.ts (600 lines - upload, storage, permissions, rate limiting)
├── payments.ts (400 lines - Stripe, webhooks, billing)
├── notifications.ts (300 lines)
└── analytics.ts (200 lines)
Total: One big codebase, everything mixed togetherconvex/
├── users.ts (500行)
├── files.ts (600行 - 上传、存储、权限、限流)
├── payments.ts (400行 - Stripe、Webhook、计费)
├── notifications.ts (300行)
└── analytics.ts (200行)
总计:一个庞大的代码库,所有内容混杂在一起Component Approach (Encapsulated)
组件化方案(封装式)
convex/
├── components/
│ ├── storage/ (File uploads - reusable)
│ ├── billing/ (Payments - reusable)
│ ├── notifications/ (Alerts - reusable)
│ └── analytics/ (Tracking - reusable)
├── convex.config.ts (Wire components together)
└── domain/ (Your actual business logic)
├── users.ts (50 lines - uses components)
└── projects.ts (75 lines - uses components)
Total: Clean, focused, reusableconvex/
├── components/
│ ├── storage/ (文件上传 - 可复用)
│ ├── billing/ (支付 - 可复用)
│ ├── notifications/ (消息提醒 - 可复用)
│ └── analytics/ (行为追踪 - 可复用)
├── convex.config.ts (将组件关联起来)
└── domain/ (你的核心业务逻辑)
├── users.ts (50行 - 调用组件)
└── projects.ts (75行 - 调用组件)
总计:整洁、聚焦、可复用Quick Start
快速开始
1. Install a Component
1. 安装组件
bash
undefinedbash
undefinedOfficial components from npm
从npm安装官方组件
npm install @convex-dev/ratelimiter
undefinednpm install @convex-dev/ratelimiter
undefined2. Configure in convex.config.ts
2. 在convex.config.ts中配置
typescript
import { defineApp } from "convex/server";
import ratelimiter from "@convex-dev/ratelimiter/convex.config";
export default defineApp({
components: {
ratelimiter,
},
});typescript
import { defineApp } from "convex/server";
import ratelimiter from "@convex-dev/ratelimiter/convex.config";
export default defineApp({
components: {
ratelimiter,
},
});3. Use in Your Code
3. 在代码中使用
typescript
import { components } from "./_generated/api";
export const createPost = mutation({
handler: async (ctx, args) => {
// Use the component
await components.ratelimiter.check(ctx, {
key: `user:${ctx.user._id}`,
limit: 10,
period: 60000, // 10 requests per minute
});
return await ctx.db.insert("posts", args);
},
});typescript
import { components } from "./_generated/api";
export const createPost = mutation({
handler: async (ctx, args) => {
// 使用组件
await components.ratelimiter.check(ctx, {
key: `user:${ctx.user._id}`,
limit: 10,
period: 60000, // 每分钟10次请求
});
return await ctx.db.insert("posts", args);
},
});Sibling Components Pattern
同级组件模式
Multiple components working together at the same level:
typescript
// convex.config.ts
export default defineApp({
components: {
// Sibling components - each handles one concern
auth: authComponent,
storage: storageComponent,
payments: paymentsComponent,
emails: emailComponent,
analytics: analyticsComponent,
},
});多个组件在同一层级协同工作:
typescript
// convex.config.ts
export default defineApp({
components: {
// 同级组件 - 每个组件负责单一关注点
auth: authComponent,
storage: storageComponent,
payments: paymentsComponent,
emails: emailComponent,
analytics: analyticsComponent,
},
});Example: Complete Feature Using Siblings
示例:使用同级组件实现完整功能
typescript
// convex/subscriptions.ts
import { components } from "./_generated/api";
export const subscribe = mutation({
args: { plan: v.string() },
handler: async (ctx, args) => {
// 1. Verify authentication (auth component)
const user = await components.auth.getCurrentUser(ctx);
// 2. Create payment (payments component)
const subscription = await components.payments.createSubscription(ctx, {
userId: user._id,
plan: args.plan,
amount: getPlanAmount(args.plan),
});
// 3. Track conversion (analytics component)
await components.analytics.track(ctx, {
event: "subscription_created",
userId: user._id,
plan: args.plan,
});
// 4. Send confirmation (emails component)
await components.emails.send(ctx, {
to: user.email,
template: "subscription_welcome",
data: { plan: args.plan },
});
// 5. Store subscription in main app
await ctx.db.insert("subscriptions", {
userId: user._id,
paymentId: subscription.id,
plan: args.plan,
status: "active",
});
return subscription;
},
});What this achieves:
- ✅ Each component is single-purpose
- ✅ Components are reusable across features
- ✅ Easy to swap implementations (change email provider)
- ✅ Can update components independently
- ✅ Clear separation of concerns
typescript
// convex/subscriptions.ts
import { components } from "./_generated/api";
export const subscribe = mutation({
args: { plan: v.string() },
handler: async (ctx, args) => {
// 1. 验证身份(auth组件)
const user = await components.auth.getCurrentUser(ctx);
// 2. 创建支付订单(payments组件)
const subscription = await components.payments.createSubscription(ctx, {
userId: user._id,
plan: args.plan,
amount: getPlanAmount(args.plan),
});
// 3. 追踪转化行为(analytics组件)
await components.analytics.track(ctx, {
event: "subscription_created",
userId: user._id,
plan: args.plan,
});
// 4. 发送确认邮件(emails组件)
await components.emails.send(ctx, {
to: user.email,
template: "subscription_welcome",
data: { plan: args.plan },
});
// 5. 在主应用中存储订阅信息
await ctx.db.insert("subscriptions", {
userId: user._id,
paymentId: subscription.id,
plan: args.plan,
status: "active",
});
return subscription;
},
});实现的效果:
- ✅ 每个组件专注单一功能
- ✅ 组件可跨功能复用
- ✅ 轻松替换实现方案(比如更换邮件服务商)
- ✅ 可独立更新组件
- ✅ 关注点清晰分离
Official Components
官方组件
Browse Component Directory:
浏览组件目录:
Authentication
身份认证
- @convex-dev/better-auth - Better Auth integration
- @convex-dev/better-auth - Better Auth集成
Storage
存储
- @convex-dev/r2 - Cloudflare R2 file storage
- @convex-dev/storage - File upload/download
- @convex-dev/r2 - Cloudflare R2文件存储
- @convex-dev/storage - 文件上传/下载
Payments
支付
- @convex-dev/polar - Polar billing & subscriptions
- @convex-dev/polar - Polar计费与订阅
AI
人工智能
- @convex-dev/agent - AI agent workflows
- @convex-dev/embeddings - Vector storage & search
- @convex-dev/agent - AI Agent工作流
- @convex-dev/embeddings - 向量存储与搜索
Backend Utilities
后端工具
- @convex-dev/ratelimiter - Rate limiting
- @convex-dev/aggregate - Data aggregations
- @convex-dev/action-cache - Cache action results
- @convex-dev/sharded-counter - Distributed counters
- @convex-dev/migrations - Schema migrations
- @convex-dev/workflow - Workflow orchestration
- @convex-dev/ratelimiter - 限流
- @convex-dev/aggregate - 数据聚合
- @convex-dev/action-cache - 操作结果缓存
- @convex-dev/sharded-counter - 分布式计数器
- @convex-dev/migrations - 模式迁移
- @convex-dev/workflow - 工作流编排
Creating Your Own Component
创建自定义组件
When to Create a Component
何时创建组件
Good reasons:
- Feature is self-contained
- You'll reuse it across projects
- Want to share with team/community
- Complex feature with its own data model
- Third-party integration wrapper
Not good reasons:
- One-off business logic
- Tightly coupled to main app
- Simple utility functions
合适的场景:
- 功能独立完整
- 会在多个项目中复用
- 希望与团队/社区分享
- 拥有独立数据模型的复杂功能
- 第三方集成的封装
不合适的场景:
- 一次性业务逻辑
- 与主应用紧密耦合
- 简单工具函数
Structure
结构
bash
mkdir -p convex/components/notificationstypescript
// convex/components/notifications/convex.config.ts
import { defineComponent } from "convex/server";
export default defineComponent("notifications");typescript
// convex/components/notifications/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
notifications: defineTable({
userId: v.id("users"),
message: v.string(),
read: v.boolean(),
createdAt: v.number(),
})
.index("by_user", ["userId"])
.index("by_user_and_read", ["userId", "read"]),
});typescript
// convex/components/notifications/send.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const send = mutation({
args: {
userId: v.id("users"),
message: v.string(),
},
handler: async (ctx, args) => {
await ctx.db.insert("notifications", {
userId: args.userId,
message: args.message,
read: false,
createdAt: Date.now(),
});
},
});
export const markRead = mutation({
args: { notificationId: v.id("notifications") },
handler: async (ctx, args) => {
await ctx.db.patch(args.notificationId, { read: true });
},
});typescript
// convex/components/notifications/read.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
export const list = query({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
return await ctx.db
.query("notifications")
.withIndex("by_user", q => q.eq("userId", args.userId))
.order("desc")
.collect();
},
});
export const unreadCount = query({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
const unread = await ctx.db
.query("notifications")
.withIndex("by_user_and_read", q =>
q.eq("userId", args.userId).eq("read", false)
)
.collect();
return unread.length;
},
});bash
mkdir -p convex/components/notificationstypescript
// convex/components/notifications/convex.config.ts
import { defineComponent } from "convex/server";
export default defineComponent("notifications");typescript
// convex/components/notifications/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
notifications: defineTable({
userId: v.id("users"),
message: v.string(),
read: v.boolean(),
createdAt: v.number(),
})
.index("by_user", ["userId"])
.index("by_user_and_read", ["userId", "read"]),
});typescript
// convex/components/notifications/send.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const send = mutation({
args: {
userId: v.id("users"),
message: v.string(),
},
handler: async (ctx, args) => {
await ctx.db.insert("notifications", {
userId: args.userId,
message: args.message,
read: false,
createdAt: Date.now(),
});
},
});
export const markRead = mutation({
args: { notificationId: v.id("notifications") },
handler: async (ctx, args) => {
await ctx.db.patch(args.notificationId, { read: true });
},
});typescript
// convex/components/notifications/read.ts
import { query } from "./_generated/server";
import { v } from "convex/values";
export const list = query({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
return await ctx.db
.query("notifications")
.withIndex("by_user", q => q.eq("userId", args.userId))
.order("desc")
.collect();
},
});
export const unreadCount = query({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
const unread = await ctx.db
.query("notifications")
.withIndex("by_user_and_read", q =>
q.eq("userId", args.userId).eq("read", false)
)
.collect();
return unread.length;
},
});Use Your Component
使用自定义组件
typescript
// convex.config.ts
import { defineApp } from "convex/server";
import notifications from "./components/notifications/convex.config";
export default defineApp({
components: {
notifications, // Your local component
},
});typescript
// convex/tasks.ts - main app code
import { components } from "./_generated/api";
export const completeTask = mutation({
args: { taskId: v.id("tasks") },
handler: async (ctx, args) => {
const task = await ctx.db.get(args.taskId);
await ctx.db.patch(args.taskId, { completed: true });
// Use your component
await components.notifications.send(ctx, {
userId: task.userId,
message: `Task "${task.title}" completed!`,
});
},
});typescript
// convex.config.ts
import { defineApp } from "convex/server";
import notifications from "./components/notifications/convex.config";
export default defineApp({
components: {
notifications, // 你的本地组件
},
});typescript
// convex/tasks.ts - 主应用代码
import { components } from "./_generated/api";
export const completeTask = mutation({
args: { taskId: v.id("tasks") },
handler: async (ctx, args) => {
const task = await ctx.db.get(args.taskId);
await ctx.db.patch(args.taskId, { completed: true });
// 使用自定义组件
await components.notifications.send(ctx, {
userId: task.userId,
message: `任务 "${task.title}" 已完成!`,
});
},
});Component Communication Patterns
组件通信模式
✅ Parent → Component (Good)
✅ 主应用 → 组件(推荐)
typescript
// Main app calls component
await components.storage.upload(ctx, file);
await components.analytics.track(ctx, event);typescript
// 主应用调用组件
await components.storage.upload(ctx, file);
await components.analytics.track(ctx, event);✅ Parent → Multiple Siblings (Good)
✅ 主应用 → 多个同级组件(推荐)
typescript
// Main app orchestrates multiple components
await components.auth.verify(ctx);
const file = await components.storage.upload(ctx, data);
await components.notifications.send(ctx, message);typescript
// 主应用编排多个组件
await components.auth.verify(ctx);
const file = await components.storage.upload(ctx, data);
await components.notifications.send(ctx, message);✅ Component Receives Parent Data (Good)
✅ 组件接收主应用数据(推荐)
typescript
// Pass IDs from parent's tables to component
await components.audit.log(ctx, {
userId: user._id, // From parent's users table
action: "delete",
resourceId: task._id, // From parent's tasks table
});
// Component stores these as strings/IDs
// but doesn't access parent tables directlytypescript
// 将主应用表的ID传递给组件
await components.audit.log(ctx, {
userId: user._id, // 来自主应用的users表
action: "delete",
resourceId: task._id, // 来自主应用的tasks表
});
// 组件将这些数据存储为字符串/ID
// 但不会直接访问主应用的表❌ Component → Parent Tables (Bad)
❌ 组件 → 主应用表(不推荐)
typescript
// Inside component code - DON'T DO THIS
const user = await ctx.db.get(userId); // Error! Can't access parent tablestypescript
// 组件代码中 - 禁止这样做!
const user = await ctx.db.get(userId); // 错误!无法访问主应用表❌ Sibling → Sibling (Bad)
❌ 同级组件 → 同级组件(不推荐)
Components can't call each other directly. If you need this, they should be in the main app or refactor the design.
组件之间不能直接调用。如果需要这种场景,应将逻辑放在主应用中,或重新设计结构。
Real-World Examples
实际场景示例
Multi-Tenant SaaS
多租户SaaS
typescript
// convex.config.ts
export default defineApp({
components: {
auth: "@convex-dev/better-auth",
organizations: "./components/organizations",
billing: "./components/billing",
storage: "@convex-dev/r2",
analytics: "./components/analytics",
emails: "./components/emails",
},
});Each component:
- - User authentication & sessions
auth - - Tenant isolation & permissions
organizations - - Stripe integration & subscriptions
billing - - File uploads to R2
storage - - Event tracking & metrics
analytics - - Email sending via SendGrid
emails
typescript
// convex.config.ts
export default defineApp({
components: {
auth: "@convex-dev/better-auth",
organizations: "./components/organizations",
billing: "./components/billing",
storage: "@convex-dev/r2",
analytics: "./components/analytics",
emails: "./components/emails",
},
});每个组件的职责:
- - 用户认证与会话管理
auth - - 租户隔离与权限控制
organizations - - Stripe集成与订阅管理
billing - - R2文件上传
storage - - 事件追踪与指标统计
analytics - - 通过SendGrid发送邮件
emails
E-Commerce Platform
电商平台
typescript
export default defineApp({
components: {
cart: "./components/cart",
inventory: "./components/inventory",
orders: "./components/orders",
payments: "@convex-dev/polar",
shipping: "./components/shipping",
recommendations: "./components/recommendations",
},
});typescript
export default defineApp({
components: {
cart: "./components/cart",
inventory: "./components/inventory",
orders: "./components/orders",
payments: "@convex-dev/polar",
shipping: "./components/shipping",
recommendations: "./components/recommendations",
},
});AI Application
AI应用
typescript
export default defineApp({
components: {
agent: "@convex-dev/agent",
embeddings: "./components/embeddings",
documents: "./components/documents",
chat: "./components/chat",
workflow: "@convex-dev/workflow",
},
});typescript
export default defineApp({
components: {
agent: "@convex-dev/agent",
embeddings: "./components/embeddings",
documents: "./components/documents",
chat: "./components/chat",
workflow: "@convex-dev/workflow",
},
});Migration from Monolithic
从单体架构迁移
Step 1: Identify Features
Current monolith:
- File uploads (mixed with main app)
- Rate limiting (scattered everywhere)
- Analytics (embedded in functions)Step 2: Extract One Feature
bash
undefined步骤1:识别可提取的功能
当前单体架构包含:
- 文件上传(与主应用代码混杂)
- 限流(代码分散在各处)
- 统计分析(嵌入在各个函数中)步骤2:提取单个功能
bash
undefinedCreate component
创建组件目录
mkdir -p convex/components/storage
mkdir -p convex/components/storage
Move storage code to component
将存储相关代码迁移到组件中
Update imports in main app
更新主应用中的导入路径
**Step 3: Test Independently**
```bash
**步骤3:独立测试**
```bashComponent has its own tests
组件拥有独立的测试用例
No coupling to main app
与主应用无耦合
**Step 4: Repeat**
Extract other features incrementally.
**步骤4:重复操作**
逐步提取其他功能模块。Best Practices
最佳实践
1. Single Responsibility
1. 单一职责原则
Each component does ONE thing well:
- ✅ storage component handles files
- ✅ auth component handles authentication
- ❌ Don't create "utils" component with everything
每个组件只做好一件事:
- ✅ storage组件仅处理文件相关操作
- ✅ auth组件仅处理身份认证
- ❌ 不要创建包含所有工具的“utils”组件
2. Clear API Surface
2. 清晰的API接口
typescript
// Export only what's needed
export { upload, download, delete } from "./storage";
// Keep internals private
// (Don't export helper functions)typescript
// 仅导出必要的功能
export { upload, download, delete } from "./storage";
// 内部实现保持私有
// (不要导出辅助函数)3. Minimal Coupling
3. 最小化耦合
typescript
// ✅ Good: Pass data as arguments
await components.audit.log(ctx, {
userId: user._id,
action: "delete"
});
// ❌ Bad: Component accesses parent tables
// (Not even possible, but shows the principle)typescript
// ✅ 推荐:通过参数传递数据
await components.audit.log(ctx, {
userId: user._id,
action: "delete"
});
// ❌ 不推荐:组件直接访问主应用表
// (Convex本身禁止这种操作,此处仅作示例说明原则)4. Version Your Components
4. 组件版本化
json
{
"name": "@yourteam/notifications-component",
"version": "1.0.0"
}json
{
"name": "@yourteam/notifications-component",
"version": "1.0.0"
}5. Document Your Components
5. 组件文档化
Include README with:
- What the component does
- How to install
- How to use
- API reference
- Examples
为组件添加README,包含:
- 组件功能说明
- 安装方法
- 使用示例
- API参考
- 实际场景示例
Troubleshooting
常见问题排查
Component not found
组件未找到
bash
undefinedbash
undefinedMake sure component is in convex.config.ts
确保组件已在convex.config.ts中配置
Run: npx convex dev
执行:npx convex dev
undefinedundefinedCan't access parent tables
无法访问主应用表
This is by design! Components are sandboxed.
Pass data as arguments instead.这是设计使然!组件处于沙箱环境中。
请通过参数传递数据替代直接访问。Component conflicts
组件冲突
Each component has isolated tables.
Components can't see each other's data.每个组件都有独立的表空间。
组件之间无法互相访问数据。Learn More
更多学习资源
Checklist
检查清单
- Browse Component Directory for existing solutions
- Install components via npm:
npm install @convex-dev/component-name - Configure in
convex.config.ts - Use sibling components for feature encapsulation
- Create your own components for reusable features
- Keep components focused (single responsibility)
- Test components in isolation
- Document component APIs
- Version your components properly
Remember: Components are about encapsulation and reusability. When in doubt, prefer components over monolithic code!
- 浏览组件目录寻找现有解决方案
- 通过npm安装组件:
npm install @convex-dev/component-name - 在中配置组件
convex.config.ts - 使用同级组件进行功能封装
- 为可复用功能创建自定义组件
- 保持组件聚焦(单一职责)
- 独立测试组件
- 为组件编写API文档
- 正确为组件版本化
记住: 组件的核心是封装与复用。当你拿不定主意时,优先选择组件而非单体代码!