Supabase + Acme

Technical Discovery

Erfi Anugrah

Agenda

  1. Intros
  2. Discovery — your EU expansion goals
  3. How Supabase addresses your needs
  4. Proposed approach
  5. Q&A + next steps

Supabase in 30 Seconds

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

Discovery

Your EU Expansion

What we understand:

  • Greenfield Next.js app for European market
  • ~5-6M MAUs in US, expecting 2.5-3M in EU
  • Target go-live: 3-6 months

What we’d love to learn:

  • What’s driving the timeline?
  • What does success look like in 6 months?

Technical Landscape

Your current state

  • What does your US architecture look like?
  • Any pain points you want to avoid?

For the EU app

  • What are the core data models?
  • Expected request volume?
  • Latency requirements?

Feature Requirements

What features are you planning to use?

Auth

  • Email/password
  • Social logins
  • SSO/SAML
  • Existing IdP

Realtime

  • Notifications
  • Live feeds
  • Collaboration
  • Presence

Storage

  • User uploads
  • Documents
  • Media files
  • CDN delivery

Functions

  • Custom logic
  • Integrations
  • Webhooks
  • Cron jobs

Security & Compliance

Compliance needs

  • GDPR / data residency
  • SOC 2 report
  • Other certifications

Security requirements

  • Security review process
  • Vendor requirements
  • Network restrictions

Team & Timeline

Team

  • Engineering team size
  • Postgres experience level
  • DevOps/Platform support

Timeline

  • Hard deadline vs target
  • MVP scope definition
  • Phased rollout vs big bang

Blockers

What do you want to validate today?

  • Biggest concerns about Supabase
  • Potential deal-breakers
  • Alternatives you’ve evaluated

How Supabase Helps

Speed to Market

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

Security Model

Row Level Security — authorization at database level

Request → JWT validated → RLS policy checked → filtered rows
  • Every query filtered before execution
  • Can’t bypass via API bugs
  • No backend auth code needed

→ Deep-dive: RLS

EU Compliance

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

→ EU regions

Scaling

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

→ Pooling | → Compute tiers

SLAs

Plan Uptime Urgent Response DPA
Pro None Email No
Team None 24h No
Enterprise 99.9% 1h 24/7 Yes

99.9% = ~43 min downtime/month — Credits up to 30% if missed

Disaster Recovery

Type RPO Use case
Daily backups 24h Standard
PITR ~2 min Precise recovery

PITR: Restore to any second. $100/mo for 7 days.

Proposed Approach

Architecture

EU Users → Supabase API (geo-routing)
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
   Frankfurt    London     Stockholm
   (Primary)   (Replica)   (Replica)

Recommendation: Enterprise plan, Frankfurt primary, PITR 7 days

Implementation

Phase 1 (Month 1)

  • Project setup
  • Schema + RLS
  • Auth
  • CI/CD

Phase 2 (Month 2-3)

  • Data models
  • Storage
  • Realtime
  • Edge Functions

Phase 3 (Month 4)

  • Load testing
  • Security review
  • Monitoring
  • Go-live

Next Steps

I’ll send:

  • Summary email with recommendations
  • EU compliance docs
  • SOC 2 report (NDA)

Follow-ups:

Questions?

Appendix

Plans

Feature Pro Team Enterprise
SLA None None 99.9%
Urgent support Email 24h 1h 24/7
SOC 2 No Yes Yes
DPA No No Yes
SSO No Yes Yes
Backups 7 days 14 days 30 days

← Back

EU Regions

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

← Back

Links

Resource URL
Docs supabase.com/docs
Pricing supabase.com/pricing
Status status.supabase.com
Security supabase.com/security

Technical Deep-Dive

Request Flow

┌─────────────┐     HTTPS + JWT      ┌─────────────┐
│ supabase-js │ ───────────────────▶ │  PostgREST  │
│  (Browser)  │ ◀─────────────────── │ (API Layer) │
└─────────────┘     JSON response    └──────┬──────┘
                                            │
                                     SQL + JWT context
                                            ▼
                                     ┌─────────────┐
                                     │  Postgres   │
                                     │   + RLS     │
                                     └─────────────┘

← Back

auth.uid()

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

RLS Policies

-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Policy
CREATE POLICY "own_posts" ON posts
FOR ALL TO authenticated
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());
Clause Controls
USING Which rows accessible
WITH CHECK Which values valid

← Back

Multi-Tenant Patterns

JWT-based (fast):

USING (tenant_id = (auth.jwt()->>'tenant_id')::uuid)

Membership table (flexible):

USING (tenant_id IN (
  SELECT tenant_id FROM members WHERE user_id = auth.uid()
))

OAuth Flow

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

getSession vs getUser

Method Validates?
getSession() No (trusts cookie)
getUser() Yes (calls Auth)

Rule: Use getUser() server-side for trusted identity

Storage

bucket: user-files
├── {user_id}/avatar.jpg
└── {user_id}/docs/resume.pdf
CREATE POLICY "own_files" ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (
  (storage.foldername(name))[1] = auth.uid()::text
);

Realtime

Feature Source Persisted
Postgres Changes DB Yes
Broadcast Client→Client No
Presence Client state No
supabase.channel("posts")
  .on("postgres_changes", 
      { event: "INSERT", table: "posts" },
      (payload) => console.log(payload.new))
  .subscribe();

Connection Pooling

Serverless invocations → Supavisor → Postgres
Mode Port Use
Transaction 6543 Serverless
Session 5432 Migrations

← Back

Compute Tiers

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

← Back

Monitoring

Metric Action threshold
CPU >70% sustained
Memory >80% sustained
Connections >80% of limit
Replication lag >10s
p95/p99 latency Degrading

SSR (Next.js)

// 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 */ }
      }
    }
  });
}

CLI

supabase start          # Local stack
supabase db reset       # Reset + migrations
supabase db push        # Push to prod
supabase gen types typescript --local