There’s a moment every developer knows: you’re building a feature, things are going well, and then you need to add real-time updates. Or you need to invalidate a cache. Or you need to make sure two operations happen atomically. Suddenly, your “simple” feature becomes a week of infrastructure work.

We’ve been there more times than we’d like to admit. REST APIs, GraphQL subscriptions, Redis caching layers, database transactions, optimistic updates that don’t quite work right. It’s exhausting.

Then we tried Convex. And honestly? We’re not going back.

What Even Is Convex?

Convex is a backend-as-a-service, but that label undersells it. It’s more like “what if your backend just worked the way you always wished it did?”

How Convex Works

Your App
React / Next.js
React Native
Reactive Queries
Convex Backend
Functionsqueries · mutations · actions
DatabaseDocument DB with indexes
File StorageBuilt-in uploads
Scheduling
Auth
HTTP
Automatic caching & invalidation
TypeScript end-to-end
ACID transactions

Here’s the core idea: you write TypeScript functions that run on the server. These functions can query and mutate your database. When data changes, every client that’s subscribed to that data updates automatically. No WebSocket setup. No pub/sub configuration. No cache invalidation logic.

It sounds too good to be true. We thought so too.

The “Aha” Moment

Let me show you something that made us converts. Here’s what a typical data-fetching setup looks like with a REST API:

// api/users.ts - your API route
export async function GET(req: Request) {
  const users = await db.query.users.findMany();
  return Response.json(users);
}

// hooks/useUsers.ts - your custom hook
export function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(r => r.json()),
    staleTime: 5000,
  });
}

// When you create a user, you need to invalidate
const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] });
  },
});

Now here’s the same thing in Convex:

// convex/users.ts
export const list = query({
  handler: async (ctx) => {
    return await ctx.db.query("users").collect();
  },
});

export const create = mutation({
  args: { name: v.string(), email: v.string() },
  handler: async (ctx, args) => {
    await ctx.db.insert("users", args);
  },
});

// In your component - that's it
const users = useQuery(api.users.list);
const createUser = useMutation(api.users.create);

When you call createUser, every component using useQuery(api.users.list) updates automatically. No invalidation. No stale data. No extra code.

Real-Time Without the Headache

We recently built a collaborative feature where multiple users edit the same document. With a traditional stack, this would mean:

  1. Setting up WebSocket connections
  2. Building a message protocol
  3. Handling reconnection logic
  4. Managing optimistic updates
  5. Dealing with conflict resolution
  6. Writing a lot of tests

With Convex, we wrote this:

// convex/documents.ts
export const get = query({
  args: { id: v.id("documents") },
  handler: async (ctx, args) => {
    return await ctx.db.get(args.id);
  },
});

export const update = mutation({
  args: { id: v.id("documents"), content: v.string() },
  handler: async (ctx, args) => {
    await ctx.db.patch(args.id, { content: args.content });
  },
});

That’s it. Every user viewing the document sees changes in real-time. The subscription is automatic. Reconnection is handled. We moved on to building actual features.

Developer Experience

Lines of boilerplate-87%
Traditional
150 lines
Convex
20 lines
Time to real-time sync-99%
Traditional
480 min
Convex
5 min
Files for a CRUD feature-75%
Traditional
8 files
Convex
2 files
Type safety coverage+150%
Traditional
40 %
Convex
100 %
REST API + React Query / SWR
Convex

TypeScript All the Way Down

One thing that’s hard to appreciate until you experience it: Convex is TypeScript end-to-end. Your database schema, your functions, your client calls—it’s all typed.

// convex/schema.ts
export default defineSchema({
  users: defineTable({
    name: v.string(),
    email: v.string(),
    role: v.union(v.literal("admin"), v.literal("member")),
    createdAt: v.number(),
  }).index("by_email", ["email"]),
});

Now when you write a query or call it from the frontend, you get full autocomplete and type checking. Misspell a field name? TypeScript catches it. Pass the wrong type? TypeScript catches it. Change your schema? TypeScript tells you everywhere that needs updating.

This isn’t just nice-to-have. It’s a different way of working. You catch bugs at compile time instead of in production.

How It Stacks Up

We’ve used Firebase and Supabase on other projects. They’re good tools. But here’s how Convex compares:

Backend Comparison

FeatureConvexFirebaseSupabase
Real-time by defaultRequires setup
TypeScript end-to-end
ACID transactions
Automatic caching
Built-in schedulingCloud Functionspg_cron
File storage
Server functionsTypeScriptJS/TS/PythonSQL/Edge
Auth integrationClerk/Auth0/etcBuilt-inBuilt-in
Local developmentHot reloadEmulatorsDocker
Pricing modelUsage-basedUsage-basedUsage-based

The standout differences for us:

TypeScript end-to-end — Firebase and Supabase give you JavaScript/TypeScript clients, but the backend logic often involves different languages or paradigms. Convex is TypeScript from your schema definition to your React components.

Automatic caching and invalidation — This is the big one. With Convex, you never think about caching. It just works. With other tools, you’re constantly managing query keys, invalidation, and stale data.

ACID transactions — If you need two operations to succeed or fail together, Convex handles it. No distributed transaction complexity.

The Things That Surprised Us

A few things we didn’t expect:

Local development is seamless. Run npx convex dev and you have a fully functional backend with hot reload. Change a function, it’s live immediately. No Docker containers, no emulators, no configuration.

The dashboard is actually useful. You can browse your data, see function logs, debug issues, all from a clean interface. It’s not an afterthought.

Scheduled functions just work. Need to send a reminder email in 24 hours? One line:

await ctx.scheduler.runAfter(24 * 60 * 60 * 1000, api.emails.sendReminder, {
  userId: args.userId,
});

File storage is built in. Upload files, get URLs, no S3 configuration. It’s not a separate service to manage.

When Convex Might Not Be Right

We try to be honest about tools we recommend. Convex isn’t perfect for every situation:

  • If you need raw SQL — Convex uses a document database. If your app is deeply relational with complex joins, you might want Postgres.

  • If you’re locked into AWS/GCP — Convex is its own infrastructure. If you need everything on your cloud provider for compliance, that’s a consideration.

  • If you need fine-grained access patterns — Convex’s permissions model is good but might not cover very complex multi-tenant scenarios out of the box.

  • If you’re doing heavy data analytics — Convex is optimized for application data, not analytical workloads.

For most web and mobile apps, though? It’s hard to beat.

Our Stack Now

For new projects, our default stack has become:

  • Frontend: Next.js or React + Vite
  • Backend: Convex
  • Auth: Clerk (integrates beautifully with Convex)
  • Hosting: Vercel
  • Styling: Tailwind + shadcn/ui

This combination lets us move incredibly fast. We spend time building features, not infrastructure.

Getting Started

If you want to try Convex, it takes about five minutes:

# Create a new project
npm create convex@latest

# Or add to existing React app
npm install convex
npx convex init

The documentation is excellent. Start with the “Get Started” guide, then build something small. You’ll feel the difference immediately.

Final Thoughts

We’ve become the annoying friends who won’t stop talking about Convex. But there’s a reason: it genuinely changed how we think about building apps.

The backend shouldn’t be the hard part. It shouldn’t be where you spend your innovation tokens. You should be able to focus on what makes your product unique, not on caching strategies and real-time synchronization.

Convex lets us do that. We hope it can do the same for you.


Have questions about Convex or want to see how we’ve used it in our projects? Reach out—we’re happy to share more.