SecureStartKit

Database

Supabase security model and data access patterns.

Security Architecture

This template uses a security-first approach:

  • RLS is enabled on all tables
  • No RLS policies are created (deny all by default)
  • The anon key has zero data access
  • All queries use createAdminClient() (service role key)
  • Data access happens exclusively in Server Actions and API Routes

This means even if someone gets your anon key, they cannot read or write any data.

Data Access Pattern

Never use the browser Supabase client for data queries. Always use Server Actions:

// actions/your-feature.ts
'use server'

import { createAdminClient } from '@/lib/supabase/server'

export async function getData() {
  const supabase = createAdminClient()
  const { data, error } = await supabase
    .from('your_table')
    .select('*')

  if (error) throw error
  return data
}

Database Schema

The schema is defined in supabase/schema.sql:

profiles

Extends auth.users with app-specific fields:

  • id (references auth.users)
  • full_name
  • avatar_url
  • billing_address, payment_method
  • Auto-created via database trigger on signup

customers

Maps Supabase users to Stripe customers:

  • id (references auth.users)
  • stripe_customer_id

subscriptions

Tracks active subscriptions:

  • id (Stripe subscription ID)
  • user_id, status, price_id
  • current_period_start, current_period_end
  • cancel_at_period_end

purchases

Tracks one-time payments:

  • id, user_id, stripe_session_id
  • amount, currency, status

Adding Tables

  1. Add the SQL to supabase/schema.sql
  2. Run the migration in Supabase SQL Editor
  3. Update types in lib/supabase/database.types.ts
  4. Create Server Actions in actions/