Convex
Convex is a full-stack backend platform that combines a reactive database, serverless functions, and real-time sync into a single TypeScript-native service. It replaces the need for a separate database, ORM, and API layer in most vibe-coded apps.
What Is Convex?
Convex gives you a backend where:
- Data is stored in a document database with strong consistency and ACID transactions
- Functions run as serverless queries and mutations — TypeScript code that runs on Convex's infrastructure
- Clients stay in sync automatically — queries re-run reactively when data changes, no polling required
- Auth, file storage, and scheduled jobs are built in
Production example: Pixola uses Convex for all user data, image metadata, and workflow state.
Key Features
Reactive Queries
Convex queries re-run automatically when the underlying data changes. Any component subscribing to a query gets a live update without WebSocket boilerplate.
// convex/tasks.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("tasks")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.order("desc")
.collect();
},
});
// app/tasks/page.tsx
"use client";
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
export default function TasksPage() {
const tasks = useQuery(api.tasks.list, { userId: currentUserId });
if (tasks === undefined) return <div>Loading...</div>;
return (
<ul>
{tasks.map((task) => (
<li key={task._id}>{task.title}</li>
))}
</ul>
);
}
Mutations and Transactions
Mutations are ACID-transactional. You read and write in the same function with full consistency guarantees.
// convex/tasks.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const create = mutation({
args: {
title: v.string(),
userId: v.id("users"),
},
handler: async (ctx, args) => {
const taskId = await ctx.db.insert("tasks", {
title: args.title,
userId: args.userId,
status: "todo",
createdAt: Date.now(),
});
return taskId;
},
});
Schema
Define your data model in convex/schema.ts. Convex validates documents against the schema at write time.
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
tasks: defineTable({
title: v.string(),
status: v.union(v.literal("todo"), v.literal("done")),
userId: v.id("users"),
createdAt: v.number(),
}).index("by_user", ["userId"]),
users: defineTable({
clerkId: v.string(),
email: v.string(),
name: v.optional(v.string()),
}).index("by_clerk_id", ["clerkId"]),
});
File Storage
Convex has built-in file storage. Generate upload URLs server-side, upload from the client, then store the storage ID in your document.
// convex/files.ts
import { mutation } from "./_generated/server";
export const generateUploadUrl = mutation(async (ctx) => {
return await ctx.storage.generateUploadUrl();
});
export const saveFile = mutation({
args: { storageId: v.id("_storage"), userId: v.id("users") },
handler: async (ctx, args) => {
await ctx.db.insert("files", {
storageId: args.storageId,
userId: args.userId,
uploadedAt: Date.now(),
});
},
});
Scheduled Functions
Run background jobs or cron tasks with Convex's built-in scheduler.
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
crons.daily(
"cleanup expired sessions",
{ hourUTC: 2, minuteUTC: 0 },
internal.sessions.cleanupExpired
);
export default crons;
Auth with Clerk
Convex integrates with Clerk out of the box. Configure the JWT issuer in Convex and use ctx.auth.getUserIdentity() in any function to get the authenticated user.
// convex/users.ts
import { mutation } from "./_generated/server";
export const upsertUser = mutation({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
const existing = await ctx.db
.query("users")
.withIndex("by_clerk_id", (q) => q.eq("clerkId", identity.subject))
.unique();
if (existing) return existing._id;
return await ctx.db.insert("users", {
clerkId: identity.subject,
email: identity.email ?? "",
name: identity.name,
});
},
});
Setting Up Convex in Next.js
1. Install and Initialize
npm install convex
npx convex dev
This creates your convex/ directory and connects to a new Convex project.
2. Provider Setup
// app/providers.tsx
"use client";
import { ConvexProvider, ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export function Providers({ children }: { children: React.ReactNode }) {
return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}
// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
3. Environment Variables
# .env.local
NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
For production on Vercel, set the build command to:
npx convex deploy --cmd 'npm run build'
This deploys Convex functions and the Next.js app together.
When to Use Convex
Good fit:
- Apps that need real-time updates (collaborative tools, live dashboards, social feeds)
- Full-stack TypeScript apps where you want one language end-to-end
- Vibe-coded apps where you want to skip writing API routes and ORMs
- Apps with complex background job requirements
Not a great fit:
- Apps already deeply invested in PostgreSQL and SQL queries
- Apps that need complex relational joins across many tables (Convex is document-oriented)
- Projects requiring self-hosted infrastructure
Pricing
Convex uses a usage-based model:
| Tier | Price | Limits |
|---|---|---|
| Free | $0/month | 1M function calls/month, 1 GB storage, 1 GB bandwidth |
| Pro | $25/month | 25M function calls, 50 GB storage, unlimited projects |
| Enterprise | Custom | Dedicated infrastructure, SLAs |
Most early-stage vibe-coded apps run comfortably on the free tier. Pro is suitable for production apps with active users.
See current pricing at convex.dev/pricing.
Convex in the Vibe-Coding Stack
| Layer | Tool |
|---|---|
| Auth | Clerk |
| Database + API | Convex |
| File Storage | Convex Storage or Cloudflare R2 |
| AI Functions | Convex Actions → OpenAI / Anthropic |
| Frontend | Next.js App Router |
| Hosting | Vercel |
Convex replaces Supabase, Prisma, tRPC, and a custom API layer. For most vibe-coded apps, it's the fastest path from zero to a working backend.
Related Articles
- Convex Setup Workflow — step-by-step CLI setup for connecting v0 projects
- Convex Diagnostics — debugging connection and schema issues