convex
2
总安装量
2
周安装量
#69932
全站排名
安装命令
npx skills add https://github.com/polarcoding85/convex-agent-skillz --skill convex
Agent 安装分布
amp
2
antigravity
2
mcpjam
1
claude-code
1
windsurf
1
zencoder
1
Skill 文档
Convex Backend Development
Core Architecture
Convex is a reactive database where queries are TypeScript functions. The sync engine (queries + mutations + database) is the heart of Convex â center your app around it.
Function Types
| Type | DB Access | Deterministic | Cached/Reactive | Use For |
|---|---|---|---|---|
query |
Read only | Yes | Yes | All reads, subscriptions |
mutation |
Read/Write | Yes | No | All writes (transactions) |
action |
Via ctx.run* | No | No | External APIs, LLMs, email |
httpAction |
Via ctx.run* | No | No | Webhooks, custom HTTP |
Key rule: Queries and mutations cannot make network requests. Actions cannot directly access the database.
Project Structure (Best Practice)
convex/
âââ _generated/ # Auto-generated types (commit this)
âââ schema.ts # Database schema
âââ model/ # Helper functions (most logic lives here)
â âââ users.ts
â âââ messages.ts
âââ users.ts # Thin wrappers exposing public API
âââ messages.ts
âââ crons.ts # Cron job definitions
âââ http.ts # HTTP action routes
Essential Patterns
1. Function Structure
// convex/messages.ts
import { query, mutation, internalMutation } from './_generated/server';
import { internal } from './_generated/api';
import { v } from 'convex/values';
// PUBLIC query with validators (always validate public functions)
export const list = query({
args: { channelId: v.id('channels') },
handler: async (ctx, { channelId }) => {
return await ctx.db
.query('messages')
.withIndex('by_channel', (q) => q.eq('channelId', channelId))
.order('desc')
.take(50);
}
});
// PUBLIC mutation with validators and auth check
export const send = mutation({
args: { channelId: v.id('channels'), body: v.string() },
handler: async (ctx, { channelId, body }) => {
const user = await ctx.auth.getUserIdentity();
if (!user) throw new Error('Unauthorized');
await ctx.db.insert('messages', {
channelId,
body,
authorId: user.subject
});
}
});
// INTERNAL mutation (for scheduling, crons, actions)
export const deleteOld = internalMutation({
args: { before: v.number() },
handler: async (ctx, { before }) => {
const old = await ctx.db
.query('messages')
.withIndex('by_createdAt', (q) => q.lt('_creationTime', before))
.take(100);
for (const msg of old) {
await ctx.db.delete(msg._id);
}
}
});
2. Helper Functions Pattern
Most logic should live in helper functions, NOT in query/mutation handlers:
// convex/model/users.ts
import { QueryCtx, MutationCtx } from '../_generated/server';
import { Doc } from '../_generated/dataModel';
export async function getCurrentUser(
ctx: QueryCtx
): Promise<Doc<'users'> | null> {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return null;
return await ctx.db
.query('users')
.withIndex('by_tokenIdentifier', (q) =>
q.eq('tokenIdentifier', identity.tokenIdentifier)
)
.unique();
}
export async function requireUser(ctx: QueryCtx): Promise<Doc<'users'>> {
const user = await getCurrentUser(ctx);
if (!user) throw new Error('Unauthorized');
return user;
}
3. Actions with Scheduling
// convex/ai.ts
import { action, internalMutation } from './_generated/server';
import { internal } from './_generated/api';
import { v } from 'convex/values';
export const summarize = action({
args: { documentId: v.id('documents') },
handler: async (ctx, { documentId }) => {
// Read data via internal query
const doc = await ctx.runQuery(internal.documents.get, { documentId });
// Call external API
const response = await fetch('https://api.openai.com/v1/...', {...});
const summary = await response.json();
// Write result via internal mutation
await ctx.runMutation(internal.documents.setSummary, {
documentId,
summary: summary.text
});
}
});
// Trigger action from mutation (not directly from client)
export const requestSummary = mutation({
args: { documentId: v.id('documents') },
handler: async (ctx, { documentId }) => {
const user = await ctx.auth.getUserIdentity();
if (!user) throw new Error('Unauthorized');
await ctx.db.patch(documentId, { status: 'processing' });
// Schedule action (runs after mutation commits)
await ctx.scheduler.runAfter(0, internal.ai.summarizeInternal, {
documentId
});
}
});
4. Application Errors
import { ConvexError } from 'convex/values';
export const assignRole = mutation({
args: { roleId: v.id('roles'), userId: v.id('users') },
handler: async (ctx, { roleId, userId }) => {
const existing = await ctx.db
.query('assignments')
.withIndex('by_role', (q) => q.eq('roleId', roleId))
.first();
if (existing) {
throw new ConvexError({
code: 'ROLE_TAKEN',
message: 'Role is already assigned'
});
}
await ctx.db.insert('assignments', { roleId, userId });
}
});
Critical Rules
DO â
- Use
internal.functions for allctx.run*,ctx.scheduler, and crons - Always validate args for public functions with
v.*validators - Always check auth in public functions:
ctx.auth.getUserIdentity() - Use indexes with
.withIndex()instead of.filter() - Await all promises (enable
no-floating-promisesESLint rule) - Keep actions small â put logic in queries/mutations
- Batch database operations in single mutations
- Use
ConvexErrorfor user-facing errors
DON’T â
- Don’t use
api.functions for scheduling (useinternal.) - Don’t use
.filter()on queries â use indexes or TypeScript filter - Don’t use
.collect()on unbounded queries (use.take()or pagination) - Don’t use
Date.now()in queries (breaks caching) - Don’t call actions directly from client (trigger via mutation + scheduler)
- Don’t make sequential
ctx.runQuery/runMutationcalls in actions (batch them) - Don’t use
ctx.runActionunless switching runtimes (use helper functions)
Reference Guides
For detailed patterns, see:
- FUNCTIONS.md â Queries, mutations, actions, internal functions
- VALIDATION.md â Argument validation, extended validators
- ERROR_HANDLING.md â ConvexError, application errors
- HTTP_ACTIONS.md â HTTP actions, CORS, webhooks
- RUNTIMES.md â Default vs Node.js runtime, bundling, debugging
- DATABASE.md â Schema, indexes, reading/writing data
- SEARCH.md â Full-text search, vector search, RAG patterns
- ADVANCED.md â System tables, schema philosophy, OCC
- AUTH.md â Row-level security, Convex Auth (first-party)
- SCHEDULING.md â Crons, scheduled functions, workflows
- FILE_STORAGE.md â Upload, store, serve, delete files
- NEXTJS.md â Next.js App Router, SSR, Server Actions
Auth Provider Skills (add to project as needed):
convex-authâ Universal auth patterns, storing users, debuggingconvex-clerkâ Clerk setup, webhooks, JWT configurationconvex-workosâ WorkOS AuthKit setup, auto-provisioning