Building a backend from scratch takes weeks. BaaS platforms provide databases, authentication, file storage, and real-time subscriptions out of the box. Three platforms lead the modern stack.
Architecture
Convex: Reactive database with server functions. Write TypeScript functions that automatically sync to the client in real-time. Not a traditional SQL database, more of a document store with relational capabilities.
Supabase: Open-source Firebase alternative built on PostgreSQL. Full SQL database with REST and real-time APIs, auth, storage, and edge functions.
Appwrite: Open-source BaaS with databases, auth, storage, functions, and messaging. Self-hosting or cloud-hosted. Language-agnostic.
Feature Comparison
| Feature | Convex | Supabase | Appwrite |
|---|---|---|---|
| Database | Document store (reactive) | PostgreSQL | MariaDB-based |
| Query language | TypeScript functions | SQL + PostgREST | SDKs + REST API |
| Real-time | Native (automatic) | Realtime channels | Realtime events |
| Authentication | Clerk/Auth0 integration | Built-in (GoTrue) | Built-in |
| File storage | Built-in | Built-in (S3-compatible) | Built-in |
| Server functions | Built-in (TypeScript) | Edge Functions (Deno) | Cloud Functions |
| Scheduled tasks | Built-in (cron) | pg_cron extension | Scheduled functions |
| Full-text search | Built-in | PostgreSQL FTS | Built-in |
| Self-hosting | No | Yes | Yes |
| Schema management | Code-defined | SQL migrations | Dashboard/SDK |
| Type safety | End-to-end TypeScript | Generated types (optional) | SDK types |
Developer Experience
Convex
// convex/messages.ts
export const list = query({
handler: async (ctx) => {
return await ctx.db.query("messages").order("desc").take(50)
},
})
export const send = mutation({
args: { body: v.string(), author: v.string() },
handler: async (ctx, args) => {
await ctx.db.insert("messages", args)
},
})
// React component
function Chat() {
const messages = useQuery(api.messages.list)
const sendMessage = useMutation(api.messages.send)
// messages auto-update in real-time, no setup needed
}
Key insight: Convex queries are reactive. When data changes, connected clients update automatically. No WebSocket setup, no subscription management, no cache invalidation.
Supabase
// Query
const { data: messages } = await supabase
.from('messages')
.select('*')
.order('created_at', { ascending: false })
.limit(50)
// Insert
await supabase.from('messages').insert({ body, author })
// Real-time subscription
supabase
.channel('messages')
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' },
(payload) => setMessages(prev => [payload.new, ...prev])
)
.subscribe()
Supabase gives you PostgreSQL power with a JavaScript client. Real-time requires explicit subscription setup.
Appwrite
// Query
const messages = await databases.listDocuments(
DATABASE_ID, COLLECTION_ID,
[Query.orderDesc('$createdAt'), Query.limit(50)]
)
// Insert
await databases.createDocument(
DATABASE_ID, COLLECTION_ID, ID.unique(),
{ body, author }
)
// Real-time
client.subscribe(`databases.${DATABASE_ID}.collections.${COLLECTION_ID}.documents`,
(response) => setMessages(prev => [response.payload, ...prev])
)
Appwrite works similarly to Supabase but with a document-oriented data model.
Pricing
Free Tiers
| Feature | Convex | Supabase | Appwrite |
|---|---|---|---|
| Database | 1M documents | 500 MB | 10 GB (self-hosted: unlimited) |
| Storage | 1 GB | 1 GB | 2 GB |
| Bandwidth | 1 GB/month | 5 GB/month | Unlimited (self-hosted) |
| Functions | 500K calls | 500K invocations | 750K executions |
| Auth | Via Clerk/Auth0 | 50K MAU | Unlimited |
Paid Plans
| Plan | Convex | Supabase | Appwrite |
|---|---|---|---|
| Entry | $25/month | $25/month | $15/month |
| Pro | Custom | $25/month + usage | Custom |
Performance
| Metric | Convex | Supabase | Appwrite |
|---|---|---|---|
| Read latency | 5-20ms | 10-50ms | 10-50ms |
| Write latency | 10-30ms | 10-50ms | 20-80ms |
| Real-time delivery | < 50ms | 100-500ms | 100-500ms |
| Cold start (functions) | None (always warm) | 200-500ms (edge) | 500ms-2s |
Convex's reactive system delivers real-time updates faster because it is designed around reactivity from the ground up.
When to Choose Each
Convex
- Real-time applications (chat, collaboration, live dashboards)
- TypeScript teams wanting end-to-end type safety
- Rapid prototyping (minimal boilerplate)
- Applications where reactivity matters (state syncs automatically)
- Serverless-first architecture
Supabase
- PostgreSQL is required (existing schema, extensions like pgvector)
- Row-level security for multi-tenant applications
- Self-hosting requirement (data sovereignty)
- Complex SQL queries (joins, aggregations, CTEs)
- Existing PostgreSQL expertise on the team
- Auth as a core feature (built-in, not third-party)
Appwrite
- Self-hosting is a hard requirement
- Multi-platform (web + mobile with native SDKs)
- Open-source commitment (fully open-source)
- Simple document storage (not complex relational queries)
- Budget-conscious (self-hosted = only infrastructure costs)
Our Approach
We evaluate BaaS platforms per project. For real-time collaborative applications, Convex is our first choice. For projects needing PostgreSQL, complex queries, or self-hosting, Supabase is our recommendation. We use Appwrite when clients have strong self-hosting requirements and simpler data models.
For most business websites and applications, we build with Next.js + PostgreSQL (Neon or Supabase) for maximum flexibility.
Contact us to discuss backend architecture for your application.