Convex backend
Skill BagelHole/DevOps-Security-Agent-Skills/infrastructure/platforms/convex-backend
Agent-ready DevOps, security, infrastructure, and compliance knowledge base with 80+ skills across Kubernetes, Terraform, AWS/Azure/GCP, AI platform operations, container hardening, SOC2/ISO27001, and incident response—plus ready-to-run scripts, templates, and playbooks for SRE, platform, and security teams.
npx -y skills add BagelHole/DevOps-Security-Agent-Skills --skill convex-backendAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
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.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.0 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it
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
# Initialize Convex in an existing project
npm install convex
npx convex dev # Start local development (syncs with cloud)
# In a new project
npm create convex@latest
Schema Definition
// convex/schema.ts
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)
// convex/messages.ts
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);
// Resolve user data for each message
return Promise.all(
messages.map(async (msg) => {
const user = await ctx.db.get(msg.userId);
return { ...msg, user: user ? { name: user.name, avatarUrl: user.avatarUrl } : null };
})
);
},
});
Mutations (Writes)
// convex/messages.ts
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");
// Find or create user
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._id,
channelId: args.channelId,
body: args.body,
createdAt: Date.now(),
});
},
});
Actions (External APIs, AI)
// convex/ai.ts
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) => {
// Call external AI API
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.json();
const aiMessage = data.content[0].text;
// Save AI response as a message via mutation
await ctx.runMutation(api.messages.send, {
channelId: args.channelId,
body: aiMessage,
});
return aiMessage;
},
});
Scheduled Functions (Cron Jobs)
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// Run every hour
crons.interval("cleanup old messages", { hours: 1 }, internal.maintenance.cleanupOldMessages);
// Run daily at midnight UTC
crons.cron("daily report", "0 0 * * *", internal.reports.generateDailyReport);
export default crons;
Auth Integration
// convex/auth.config.ts
export default {
providers: [
{
domain: process.env.AUTH_DOMAIN,
applicationID: "convex",
},
],
};
// React client setup
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
// src/components/Chat.tsx
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
export function Chat({ channelId }: { channelId: string }) {
// Real-time query — auto-updates when data changes
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
# Deploy to production
npx convex deploy
# Deploy with environment variables
npx convex deploy --env-file .env.production
# Set environment variables
npx convex env set ANTHROPIC_API_KEY sk-ant-...
npx convex env list
# View logs
npx convex logs
npx convex logs --follow
# Run a function manually
npx convex run messages:listByChannel '{"channelId": "abc123"}'
File Storage
// convex/files.ts
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
internalfunctions 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
- firebase-app-platform — Alternative managed backend
- vercel-deployments — Frontend hosting
- agent-observability — Instrument AI-driven backend flows
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most data backend skills give in ~1.9k tokens
Counted across 229 of the 229 authors here whose files we hold, read 2026-08-07
- Separate business logic into service layersin 22 of 229, across 15 files
- Retry failures with exponential backoffin 21 of 229, across 14 files
- Select only needed database columnsin 20 of 229, across 13 files
- Abstract data access into repository classesin 19 of 229, across 12 files
- Use centralized error handlersin 17 of 229, across 10 files
- Use AsNoTracking for read-only queriesin 16 of 229, across 4 files
- Use async/await for all I/O operationsin 16 of 229, across 5 files
- Implement structured loggingin 15 of 229, across 4 files
- Use dependency injection for all servicesin 14 of 229, across 2 files
- Use resource-based URLs for REST APIsin 13 of 229, across 7 files
- Invalidate cache after data changesin 13 of 229, across 9 files
- Use a dependency injection containerin 12 of 229, across 4 files
Said here and by no other author read
- define schema and validation before writing functions
- keep mutations idempotent
- add auth identity checks in privileged functions
- add indexes early for high-read collections
- use internal functions for server-only logic
- store secrets in convex environment variables
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.