| name | convex-backend |
| description | Build reactive backends with Convex functions, schema validation, auth integration, and deployment workflows. Use when building real-time apps with type-safe server functions and automatic caching. |
| license | MIT |
| metadata | {"author":"devops-skills","version":"1.0"} |
Convex Backend
Use Convex to build type-safe backend logic with realtime data sync.
When to Use This Skill
Use this skill when:
- Building real-time collaborative apps (chat, dashboards, multiplayer)
- Need a backend with zero infrastructure management
- Want type-safe server functions with automatic caching
- Building AI apps that need reactive data (agent status, streaming results)
- Prototyping quickly with a managed database + functions
Prerequisites
- Node.js 18+
- npm or pnpm
- Convex account (free tier: 1M function calls/month)
Quick Start
npm install convex
npx convex dev
npm create convex@latest
Schema Definition
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
users: defineTable({
name: v.string(),
email: v.string(),
role: v.union(v.literal("admin"), v.literal("member")),
avatarUrl: v.optional(v.string()),
createdAt: v.number(),
})
.index("by_email", ["email"])
.index("by_role", ["role"]),
messages: defineTable({
userId: v.id("users"),
channelId: v.id("channels"),
body: v.string(),
attachments: v.optional(v.array(v.string())),
createdAt: v.number(),
})
.index("by_channel", ["channelId", "createdAt"])
.index("by_user", ["userId"]),
channels: defineTable({
name: v.string(),
description: v.optional(v.string()),
isPrivate: v.boolean(),
}),
});
Queries (Real-Time Reads)
import { query } from "./_generated/server";
import { v } from "convex/values";
export const listByChannel = query({
args: {
channelId: v.id("channels"),
limit: v.optional(v.number()),
},
handler: async (ctx, args) => {
const messages = await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
.order("desc")
.take(args.limit ?? 50);
return Promise.all(
messages.map(async (msg) => {
const user = await ctx.db.get(msg.userId);
return { ...msg, user: user ? { name: user.name, : user. } : };
})
);
},
});
Mutations (Writes)
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const send = mutation({
args: {
channelId: v.id("channels"),
body: v.string(),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
const user = await ctx.db
.query("users")
.withIndex("by_email", (q) => q.eq("email", identity.email!))
.unique();
if (!user) throw new Error("User not found");
return await ctx.db.insert("messages", {
userId: user.,
: args.,
: args.,
: .(),
});
},
});
Actions (External APIs, AI)
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api } from "./_generated/api";
export const generateResponse = action({
args: { prompt: v.string(), channelId: v.id("channels") },
handler: async (ctx, args) => {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.ANTHROPIC_API_KEY!,
"anthropic-version": "2023-06-01",
},
body: JSON.stringify({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: args.prompt }],
}),
});
const data = await response.();
aiMessage = data.[].;
ctx.(api.., {
: args.,
: aiMessage,
});
aiMessage;
},
});
Scheduled Functions (Cron Jobs)
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
crons.interval("cleanup old messages", { hours: 1 }, internal.maintenance.cleanupOldMessages);
crons.cron("daily report", "0 0 * * *", internal.reports.generateDailyReport);
export default crons;
Auth Integration
export default {
providers: [
{
domain: process.env.AUTH_DOMAIN,
applicationID: "convex",
},
],
};
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ClerkProvider, useAuth } from "@clerk/clerk-react";
function App() {
return (
<ClerkProvider publishableKey={CLERK_KEY}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<MyApp />
</ConvexProviderWithClerk>
</ClerkProvider>
);
}
React Client Usage
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
export function Chat({ channelId }: { channelId: string }) {
const messages = useQuery(api.messages.listByChannel, { channelId });
const sendMessage = useMutation(api.messages.send);
const handleSend = async (body: string) => {
await sendMessage({ channelId, body });
};
if (messages === undefined) return <div>Loading...</div>;
return (
<div>
{messages.map((msg) => (
<div key={msg._id}>
<strong>{msg.user?.name}</strong>: {msg.body}
</div>
))}
</div>
);
}
Deployment
npx convex deploy
npx convex deploy --env-file .env.production
npx convex env set ANTHROPIC_API_KEY sk-ant-...
npx convex env list
npx convex logs
npx convex logs --follow
npx convex run messages:listByChannel '{"channelId": "abc123"}'
File Storage
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
export const generateUploadUrl = mutation(async (ctx) => {
return await ctx.storage.generateUploadUrl();
});
export const getFileUrl = query({
args: { storageId: v.id("_storage") },
handler: async (ctx, args) => {
return await ctx.storage.getUrl(args.storageId);
},
});
Best Practices
- Define schema and validation before writing functions
- Keep mutations idempotent where possible
- Use auth identity checks in every privileged query/mutation
- Add indexes early for high-read collections
- Use
internal functions for server-only logic (crons, webhooks)
- Store secrets in Convex environment variables, never in code
- Use optimistic updates in the React client for instant UI feedback
Troubleshooting
| Issue | Solution |
|---|
| Function timeout | Actions have 10min limit; break into smaller steps |
| Query too slow | Add database index matching your query pattern |
| Type errors | Run npx convex dev to regenerate types |
| Auth not working | Check auth.config.ts and provider domain |
| Deploy fails | Check npx convex logs, verify env vars are set |
Related Skills