Convex, the ultimate backend
Why I Ditched Prisma and Supabase for Convex (And Why I Won't Go Back).

Daryl Ngako
I spent hours debugging broken stuff with Prisma. I struggled to get realtime to work with Supabase. And each time, I felt like I was spending more time running the mechanics than building my project.
Then I discovered Convex.
In this article, I tell you my real journey from Prisma and Supabase to Convex. Why I switched, when it's the right tool to use, how it compares to others, and also what's wrong (because nothing is perfect).
What made me run away from Prisma and Supabase
Before I talk about Convex, I have to be honest about what made me look for something else.
The hassle of migrations with Prisma
Prisma is a very good tool for reading and writing data from your app. TypeScript autocompletion is great, everything is well organized. But as soon as you work in a team or in several environments (local, prod), it becomes painful.
You update your database locally, everything is fine. You deploy, and there... it crashes. One change conflicts with another, or worse, it fails without telling you why. You find yourself repairing files you didn't really write, trying to figure out what happened. If you work alone, the real disadvantage remains having to execute the npx prisma migrate dev command each time the diagram is modified.
Realtime with Supabase: possible, but not simple
Supabase is solid. A real PostgreSQL database, integrated user management, file storage. For a classic app where you read and write data, it does the job very well.
But when I wanted multiple users to see each other's changes in real time, I quickly realized that it wasn't automatic.
With Supabase, to display live data, you must:
- Write your query to retrieve the data.
- Write a "subscription" separately to be notified when something changes.
- Keep the two in sync on the client side.
Basically, you're doing the same job twice, and you're crossing your fingers that it stays consistent. On a simple project it works. On something more complex, things quickly go haywire.
What is Convex?
Convex is an all-in-one backend platform, focused on TypeScript and real-time synchronization.
Imagine that you no longer have to worry about servers, databases to configure or updates to synchronize by hand.
How does it work?
You write your backend functions (queries, mutations, actions) in TypeScript in a convex/ folder, you call them from the frontend, and Convex takes care of the rest, including keeping the data up to date in real time for all connected users.
It's a bit like Firebase, but made for TypeScript developers, with real guarantees on data reliability.
Concretely, Convex gives you:
- A database that updates itself: when a piece of data changes, everyone who looks at it sees it change instantly.
- Server-side functions in TypeScript: no SQL to write, just TypeScript.
- Reliable transactions: no inconsistent state, all or nothing.
- A built-in scheduled task system for background jobs.
- File storage included with nothing to plug in.
What made me switch: The creation of a social network
The heart of the project: that everything be instantaneous. A user posts something, everyone sees it. Immediately. Not “after a refresh”.
With Supabase, that would have meant manually managing WebSockets, syncing state between clients, dealing with data conflicts... Basically, building an entire subsystem before even starting the app.
With Convex, this is all I wrote:
// convex/posts.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
export const createPost = mutation({
args: { content: v.string(), attachmentIds: v.array(v.string()) },
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
const postId = await ctx.db.insert("posts", {
content: args.content,
medias: args.attachmentIds,
authorId: userId,
createdAt: Date.now(),
});
},
});
export const getPosts = query({
handler: async (ctx) => {
const posts = await ctx.db.query("posts").order("desc").collect();
return Promise.all(
posts.map(async (post) => ({
...post,
author: await ctx.db.get(post.authorId),
})),
);
},
});// app/src/components/PostList.tsx
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
export function PostList() {
const posts = useQuery(api.posts.getPosts);
return (
<div className="space-y-4">
{posts?.map((post) => (
<div key={post._id} className="p-4 border rounded">
<p className="font-bold">{post.author?.name}</p>
<p>{post.content}</p>
</div>
))}
</div>
);
}That's all. No WebSocket to configure. No synchronization to manage by hand. Convex knows all by itself what data your component uses. When someone adds a post, everyone sees it appear immediately because the list automatically refreshes.
Quick setup of Convex with Next.js
Enough talking. Here's how to integrate Convex into a Next.js project in less than 5 minutes, no nonsense, just enough to get up and running.
1. Install Convex
In your existing Next.js project, just one command is enough:
npm install convex2. Initialize Convex
npx convex devThis command will:
- Log in to your Convex account (via GitHub)
- Create a project
- Create the convex/ folder at the root of your project
- Launch a development server that synchronizes your backend code in real time
It will also automatically create the environment variable in your .env.local file. Just make sure it's there:
NEXT_PUBLIC_CONVEX_URL=https://your-project.convex.cloudYou let it run while you develop, just like next dev.
3. Create the Provider
Convex needs a client-side provider to connect your app to the backend. Creates a ConvexClientProvider.tsx file:
// app/src/providers/ConvexClientProvider.tsx
"use client";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import { ReactNode } from "react";
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
export function ConvexClientProvider({ children }: { children: ReactNode }) {
return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}4. Plug the Provider into your layout
Last step: wrap your app with the provider in the main layout.tsx.
// app/layout.tsx
import { ConvexClientProvider } from "./ConvexClientProvider";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="fr">
<body>
<ConvexClientProvider>{children}</ConvexClientProvider>
</body>
</html>
);
}And there you have it. Your Next.js app is connected to Convex.
When to use Convex?
Convex is not suitable for everything. But in these situations, that's really where he shines.
When several people work at the same time
Project management tools, collaborative documents, shared tables. If several people need to see each other's changes live, Convex makes it natural.
When you need a live dashboard
Figures that update, statuses that change, real-time alerts. You retrieve your data once, Convex takes care of keeping it up to date.
To launch a project quickly
No server to configure, no database to install, no cache to set up. You start with npx create-convex@latest and you're up and running in just a few minutes. Ideal for testing an idea quickly.
For a team that lives in TypeScript and React
If your team is comfortable with TypeScript and React but doesn't want to deal with infrastructure, Convex is made for that. Anyone can touch the backend without learning SQL or managing servers.
For projects with AI
Convex handles long running background tasks and AI agents well. If you're building something around AI, it's a solid foundation.
Another advantage of Convex: forgetting the client/server components distinction.
With Convex, everything runs client-side by default, the Next.js backend never comes into play. No more wondering where is running what.
Convex facing others: what it really gives
| Criterion | Convex | Supabase | Firebase | Prisma + DB |
|---|---|---|---|---|
| Realtime without config | Yes | No (manual) | Yes | No |
| Auto types in the frontend | Yes | To generate | No | To generate |
| SQL | No | Yes | No | Yes |
| Database updates | Automatic | Files to manage | No | Files to manage |
| Ideal for | Real-time apps, MVPs | Apps with lots of relationships | Mobile apps | Existing base |
In summary: take Convex if real time is at the heart of what you are building. Take Supabase if you need complex SQL queries with lots of relationships between your data. Keep Prisma if you already have a database in place that you don't want to touch.
The real disadvantages of Convex
I showed you what I like. But there are things that hurt, and you deserve to know them.
You can't make complex queries
Convex does not use SQL. If your app needs to cross a lot of tables, to do advanced calculations on your data or advanced statistics, you will quickly find yourself stuck. This is really where Supabase remains much better.
Fewer resources than elsewhere
Supabase or Firebase have thousands of tutorials, questions on Stack Overflow, plugins. Convex is newer, the community is smaller. When you get stuck on something specific, you will often end up on the official doc or the Discord.
Bad choice if you already have a backend
Convex is made to start from scratch. If you try to piggyback it on an existing project with a lot of server-side logic already, you'll create more problems than you solve.
My honest review
Convex is truly the ultimate tool for creating real-time applications without any configuration.
No more hassle with broken migrations, no more duplicate code to manage synchronization.
Convex manages everything: the backend, real-time, synchronization, and TypeScript typing.
Is it the perfect tool for everything? No. If your app has a lot of relationships between its data and you need complex queries, Supabase is more suitable. If you already have a Prisma project running, don't touch anything.
But if you're starting from scratch on a TypeScript and React project, you want real time without hassle, and you want to go quickly? Convex is exactly what you need.
Don't hesitate to test it and tell me what you think about it on Linkedin!
