Technical Discovery
Open-source Firebase alternative on Postgres
| Feature | What |
|---|---|
| Database | Postgres + Row Level Security |
| Auth | OAuth, email, SSO/SAML |
| Storage | S3-compatible + CDN |
| Realtime | WebSocket subscriptions |
| Functions | Deno edge runtime |
Why: Ship fast, security at DB layer, Postgres scale
What we understand:
What we’d love to learn:
Your current state
For the EU app
What features are you planning to use?
Auth
Realtime
Storage
Functions
Compliance needs
Security requirements
Team
Timeline
What do you want to validate today?
| Traditional | Supabase |
|---|---|
| Set up RDS, manage backups | Managed, automatic |
| Build auth from scratch | Built-in (OAuth, SSO) |
| Write auth logic | RLS policies |
| Set up storage + CDN | Storage + Cloudflare |
| Build realtime | Built-in |
→ Focus on product, not plumbing
Row Level Security — authorization at database level
Request → JWT validated → RLS policy checked → filtered rows
| Requirement | Solution |
|---|---|
| Data residency | 6 EU regions |
| SOC 2 Type 2 | Annual audit, report available |
| DPA | Team/Enterprise |
| Right to erasure | Cascade delete |
Recommended: Frankfurt primary, London/Stockholm replicas
Your scale: 5-6M US + 2.5-3M EU MAUs
| Challenge | Solution |
|---|---|
| Connection limits | Supavisor pooling |
| Read traffic | Read replicas + geo-routing |
| Compute | Scale to 64-core |
Vercel: Use transaction mode pooling
| Plan | Uptime | Urgent Response | DPA |
|---|---|---|---|
| Pro | None | No | |
| Team | None | 24h | No |
| Enterprise | 99.9% | 1h 24/7 | Yes |
99.9% = ~43 min downtime/month — Credits up to 30% if missed
| Type | RPO | Use case |
|---|---|---|
| Daily backups | 24h | Standard |
| PITR | ~2 min | Precise recovery |
PITR: Restore to any second. $100/mo for 7 days.
EU Users → Supabase API (geo-routing)
│
┌───────────┼───────────┐
▼ ▼ ▼
Frankfurt London Stockholm
(Primary) (Replica) (Replica)
Recommendation: Enterprise plan, Frankfurt primary, PITR 7 days
Phase 1 (Month 1)
Phase 2 (Month 2-3)
Phase 3 (Month 4)
I’ll send:
Follow-ups:
| Feature | Pro | Team | Enterprise |
|---|---|---|---|
| SLA | None | None | 99.9% |
| Urgent support | 24h | 1h 24/7 | |
| SOC 2 | No | Yes | Yes |
| DPA | No | No | Yes |
| SSO | No | Yes | Yes |
| Backups | 7 days | 14 days | 30 days |
| Code | Location | Role |
|---|---|---|
| eu-central-1 | Frankfurt | Primary |
| eu-west-2 | London | Replica |
| eu-north-1 | Stockholm | Replica |
| eu-central-2 | Zurich | Available |
| eu-west-1 | Dublin | Available |
| eu-west-3 | Paris | Available |
| Resource | URL |
|---|---|
| Docs | supabase.com/docs |
| Pricing | supabase.com/pricing |
| Status | status.supabase.com |
| Security | supabase.com/security |
┌─────────────┐ HTTPS + JWT ┌─────────────┐
│ supabase-js │ ───────────────────▶ │ PostgREST │
│ (Browser) │ ◀─────────────────── │ (API Layer) │
└─────────────┘ JSON response └──────┬──────┘
│
SQL + JWT context
▼
┌─────────────┐
│ Postgres │
│ + RLS │
└─────────────┘
Postgres function reading session variable:
1. Login → JWT with 'sub' (UUID)
2. Request includes Bearer token
3. PostgREST: SET request.jwt.claims
4. auth.uid() reads claims->>'sub'
| Function | Returns |
|---|---|
auth.uid() |
User UUID |
auth.jwt() |
Full JWT |
auth.role() |
Role |
| Clause | Controls |
|---|---|
USING |
Which rows accessible |
WITH CHECK |
Which values valid |
JWT-based (fast):
Membership table (flexible):
1. Click "Sign in with Google"
2. Redirect to Google
3. Google returns auth code
4. Supabase exchanges for tokens
5. Extract email, discard Google tokens
6. Create Supabase JWT
7. Return to app
PKCE protects against code interception
| Method | Validates? |
|---|---|
getSession() |
No (trusts cookie) |
getUser() |
Yes (calls Auth) |
Rule: Use getUser() server-side for trusted identity
bucket: user-files
├── {user_id}/avatar.jpg
└── {user_id}/docs/resume.pdf
| Feature | Source | Persisted |
|---|---|---|
| Postgres Changes | DB | Yes |
| Broadcast | Client→Client | No |
| Presence | Client state | No |
Serverless invocations → Supavisor → Postgres
| Mode | Port | Use |
|---|---|---|
| Transaction | 6543 | Serverless |
| Session | 5432 | Migrations |
| Size | CPU | RAM | Connections |
|---|---|---|---|
| Small | 2 | 2GB | 90 |
| Medium | 2 | 4GB | 120 |
| Large | 2 | 8GB | 160 |
| XL | 4 | 16GB | 240 |
| 2XL | 8 | 32GB | 380 |
| 4XL | 16 | 64GB | 480 |
| Metric | Action threshold |
|---|---|
| CPU | >70% sustained |
| Memory | >80% sustained |
| Connections | >80% of limit |
| Replication lag | >10s |
| p95/p99 latency | Degrading |
// lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(URL, KEY, {
cookies: {
getAll: () => cookieStore.getAll(),
setAll: (c) => {
try {
c.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options));
} catch { /* Server Component - read-only */ }
}
}
});
}Supabase + Acme | Technical Discovery