Files
2026-01-22 16:24:12 +00:00

11 KiB

Local Development Guide

Overview

Developing locally with Supabase provides a complete development environment that mirrors production, enabling fast iteration and testing without affecting your production database.

Prerequisites

Required Software

Install Supabase CLI

npm install -g supabase
# or
brew install supabase/tap/supabase

Initial Setup

1. Initialize Project

# In your project root
supabase init

This creates:

supabase/
├── config.toml      # Supabase configuration
├── seed.sql         # Seed data for development
└── migrations/      # Database migrations

2. Configure Project

Edit supabase/config.toml:

[api]
enabled = true
port = 54321
schemas = ["public", "graphql_public"]
extra_search_path = ["public", "extensions"]

[db]
port = 54322
major_version = 15

[db.pooler]
enabled = false
port = 54329
pool_mode = "transaction"
default_pool_size = 20
max_client_conn = 100

[studio]
enabled = true
port = 54323

[inbucket]
enabled = true
port = 54324
smtp_port = 54325
pop3_port = 54326

[auth]
enabled = true
site_url = "http://localhost:3000"
additional_redirect_urls = ["https://localhost:3000"]
jwt_expiry = 3600
enable_refresh_token_rotation = true
refresh_token_reuse_interval = 10

[auth.email]
enable_signup = true
double_confirm_changes = true
enable_confirmations = false

[auth.external.google]
enabled = false
client_id = ""
secret = ""
redirect_uri = ""

3. Start Local Services

supabase start

Services started:

4. Get Connection Details

supabase status

Save the output, especially:

  • API URL
  • anon key (for client)
  • service_role key (for admin operations)

Development Workflow

Environment Variables

Create .env.local:

NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-from-status
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-from-status

Add to .gitignore:

.env.local
.env*.local

Database Migrations

Create Migration

# Create new migration
supabase migration new create_posts_table

Edit supabase/migrations/YYYYMMDDHHMMSS_create_posts_table.sql:

-- Create posts table
create table public.posts (
  id uuid default gen_random_uuid() primary key,
  title text not null,
  content text,
  author_id uuid references auth.users(id) not null,
  created_at timestamptz default now() not null
);

-- Enable RLS
alter table public.posts enable row level security;

-- Create policies
create policy "Users can view all posts"
  on public.posts for select
  using (true);

create policy "Users can create their own posts"
  on public.posts for insert
  with check (auth.uid() = author_id);

Apply Migration

# Apply to local database
supabase db push

# Generate TypeScript types
supabase gen types typescript --local > types/database.types.ts

Seed Data

Edit supabase/seed.sql:

-- Insert test users (note: auth.users is managed by Supabase Auth)
-- Create test posts
insert into public.posts (title, content, author_id)
values
  ('First Post', 'This is my first post', 'uuid-here'),
  ('Second Post', 'This is my second post', 'uuid-here');

Apply seed:

supabase db reset  # Resets DB and applies migrations + seed

Edge Functions

Create Function

supabase functions new hello

Edit supabase/functions/hello/index.ts:

import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'

serve(async (req) => {
  const { name } = await req.json()
  
  return new Response(
    JSON.stringify({ message: `Hello ${name}!` }),
    { headers: { 'Content-Type': 'application/json' } }
  )
})

Test Locally

# Terminal 1: Serve functions
supabase functions serve

# Terminal 2: Test with curl
curl -i --location --request POST \
  'http://localhost:54321/functions/v1/hello' \
  --header 'Authorization: ******' \
  --header 'Content-Type: application/json' \
  --data '{"name":"World"}'

Storage Testing

// Upload file to local storage
const { data, error } = await supabase.storage
  .from('avatars')
  .upload(`${userId}/avatar.png`, file)

// Files stored in Docker volume
// Access via Studio: http://localhost:54323

Email Testing

Access Inbucket at http://localhost:54324 to view emails sent by Supabase Auth (signup confirmations, password resets, etc.)

Testing Strategies

Unit Tests

// tests/database.test.ts
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'http://localhost:54321',
  process.env.SUPABASE_ANON_KEY!
)

describe('Posts', () => {
  it('should create a post', async () => {
    const { data, error } = await supabase
      .from('posts')
      .insert({ title: 'Test', author_id: 'uuid' })
      .select()
      .single()
    
    expect(error).toBeNull()
    expect(data.title).toBe('Test')
  })
})

Integration Tests

# Run tests against local Supabase
npm test

# Reset database before tests
supabase db reset && npm test

RLS Policy Testing

// Test RLS policies
import { createClient } from '@supabase/supabase-js'

// Test as anonymous user
const anonClient = createClient(url, anonKey)

// Test as authenticated user
const authClient = createClient(url, anonKey)
await authClient.auth.signInWithPassword({ email, password })

// Test different scenarios
const { data: publicData } = await anonClient.from('posts').select()
const { data: privateData } = await authClient.from('posts').select()

Database Management

Reset Database

# Reset to clean state (reapplies migrations + seed)
supabase db reset

# Reset without seed
supabase db reset --no-seed

Inspect Database

# Database size
supabase inspect db size

# Table sizes
supabase inspect db tables

# Index usage
supabase inspect db index-usage

# Cache hit rate
supabase inspect db cache-hit

Direct Database Access

# Connect with psql
psql postgresql://postgres:postgres@localhost:54322/postgres

# Or use connection string from status
supabase status | grep "DB URL"

Syncing with Remote

Pull Remote Schema

# Link to remote project first
supabase link --project-ref your-project-ref

# Pull remote schema as new migration
supabase db pull

Push Local Changes

# Push local migrations to remote
supabase db push --linked

Troubleshooting

Services Won't Start

Check Docker

# Ensure Docker is running
docker ps

# Check Docker resources
# Docker Desktop > Settings > Resources
# Recommended: 4GB RAM, 2 CPUs minimum

Port Conflicts

# Check if ports are in use
lsof -i :54321  # API
lsof -i :54322  # Database
lsof -i :54323  # Studio
lsof -i :54324  # Inbucket

# Stop conflicting services or change ports in config.toml

Clean Start

# Stop and remove containers
supabase stop --no-backup

# Remove Docker volumes (CAUTION: deletes local data)
docker volume prune

# Start fresh
supabase start

Migration Issues

Repair Migration History

# Check migration status
supabase migration list

# Mark migration as applied without running
supabase migration repair 20230101000000 --status applied

# Mark migration as not applied
supabase migration repair 20230101000000 --status reverted

Regenerate Schema

# Generate migration from current database state
supabase db diff -f new_migration_name

Connection Issues

Database Connection

# Test connection
psql postgresql://postgres:postgres@localhost:54322/postgres -c "SELECT 1"

# Check logs
docker logs supabase_db_<project>

API Connection

# Test API
curl http://localhost:54321/rest/v1/

# Check logs
docker logs supabase_kong_<project>

Performance Issues

Slow Queries

# Enable query logging
# In psql:
ALTER DATABASE postgres SET log_statement = 'all';
ALTER DATABASE postgres SET log_duration = on;

# View logs
docker logs supabase_db_<project> | grep duration

Index Missing

# Check index usage
supabase inspect db index-usage

# Add indexes in migrations
CREATE INDEX idx_posts_author ON posts(author_id);

Best Practices

1. Use Migrations for Everything

  • Never modify database schema manually
  • Always create migrations for schema changes
  • Keep migrations small and focused

2. Test RLS Policies Locally

  • Create test users with different roles
  • Test each policy thoroughly
  • Use automated tests

3. Seed Data Properly

  • Create realistic test data
  • Include edge cases
  • Use consistent UUIDs for testing

4. Version Control

# .gitignore
.env.local
.env*.local
supabase/.branches
supabase/.temp

5. Keep Local and Remote in Sync

# Regular sync workflow
supabase db pull        # Get remote changes
supabase db reset       # Apply locally
supabase db push        # Push local changes

6. Use Different Environments

# Development
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321

# Staging
NEXT_PUBLIC_SUPABASE_URL=https://staging.supabase.co

# Production
NEXT_PUBLIC_SUPABASE_URL=https://prod.supabase.co

7. Monitor Resources

  • Check Docker Desktop resource usage
  • Stop services when not in use
  • Clean up old containers and volumes

Quick Reference

Common Commands

# Start/Stop
supabase start
supabase stop
supabase status

# Database
supabase db reset
supabase db push
supabase db pull
supabase db diff -f migration_name

# Migrations
supabase migration new name
supabase migration list

# Functions
supabase functions new name
supabase functions serve
supabase functions deploy

# Types
supabase gen types typescript --local > types/database.types.ts

URLs

Keys

Get from supabase status:

  • anon key (public, client-side)
  • service_role key (private, server-side only)

Additional Resources