Firebase forces you into NoSQL, has terrible TypeScript support, and locks you into Google's ecosystem. Convex is a reactive backend platform with real-time queries, TypeScript-first design, and ACID transactions.
What Convex Gives You for Free
- Real-time database — UI updates automatically when data changes
- TypeScript end-to-end — server functions and client are fully typed
- ACID transactions — no stale reads, no write conflicts
- File storage — upload and serve files
- Scheduled functions — cron jobs and delayed execution
- Authentication — Clerk, Auth0, custom auth integration
- Free tier — generous limits for side projects
Quick Start
npm create convex@latest
Define Your Schema
// convex/schema.ts
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';
export default defineSchema({
tasks: defineTable({
text: v.string(),
isCompleted: v.boolean(),
userId: v.id('users'),
}).index('by_user', ['userId']),
users: defineTable({
name: v.string(),
email: v.string(),
}),
});
Server Functions (Queries & Mutations)
// convex/tasks.ts
import { query, mutation } from './_generated/server';
import { v } from 'convex/values';
// Real-time query — UI auto-updates when data changes
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))
.collect();
},
});
// Mutation — ACID transaction guaranteed
export const create = mutation({
args: { text: v.string(), userId: v.id('users') },
handler: async (ctx, args) => {
return await ctx.db.insert('tasks', {
text: args.text,
isCompleted: false,
userId: args.userId,
});
},
});
export const toggle = mutation({
args: { id: v.id('tasks') },
handler: async (ctx, args) => {
const task = await ctx.db.get(args.id);
if (!task) throw new Error('Task not found');
await ctx.db.patch(args.id, { isCompleted: !task.isCompleted });
},
});
React Client (Real-Time by Default)
import { useQuery, useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
export function TaskList({ userId }) {
// This query is REAL-TIME — UI updates automatically!
const tasks = useQuery(api.tasks.list, { userId });
const createTask = useMutation(api.tasks.create);
const toggleTask = useMutation(api.tasks.toggle);
return (
<div>
<form onSubmit={(e) => {
e.preventDefault();
const text = new FormData(e.target).get('text');
createTask({ text, userId });
}}>
<input name="text" placeholder="New task" />
<button>Add</button>
</form>
{tasks?.map(task => (
<div key={task._id} onClick={() => toggleTask({ id: task._id })}>
{task.isCompleted ? '✓' : '○'} {task.text}
</div>
))}
</div>
);
}
No refetch. No polling. No WebSocket setup. Data is just always current.
Convex vs Firebase vs Supabase
| Feature | Convex | Firebase | Supabase |
|---|---|---|---|
| Database | Document + relational | NoSQL only | PostgreSQL |
| Type safety | Full TypeScript | Manual | Manual |
| Real-time | Built-in, automatic | Built-in | Add-on |
| Transactions | ACID | Limited | Full |
| Server functions | TypeScript | JavaScript | SQL/Edge |
| Auth | Clerk/Auth0/custom | Firebase Auth | Built-in |
| File storage | Built-in | Built-in | Built-in |
The Verdict
Convex is what Firebase should have been: real-time, type-safe, transactional, and TypeScript-native. If you're building a real-time app and want the DX of Firebase without its limitations, Convex is the modern choice.
Need help building production web scrapers or data pipelines? I build custom solutions. Reach out: spinov001@gmail.com
Check out my awesome-web-scraping collection — 400+ tools for extracting web data.
Top comments (0)