Database
Work with Supabase Postgres database and migrations
Database
This boilerplate uses Supabase (Postgres) for the database with Row Level Security (RLS).
Schema
The default schema includes two tables:
Profiles Table
Stores user profile information:
create table profiles (
id uuid references auth.users on delete cascade primary key,
full_name text,
avatar_url text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);Subscriptions Table
Stores Stripe subscription data:
create table subscriptions (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users on delete cascade,
status text not null,
stripe_customer_id text,
stripe_subscription_id text,
stripe_price_id text,
current_period_end timestamptz,
cancel_at_period_end boolean default false,
created_at timestamptz default now(),
updated_at timestamptz default now()
);Querying Data
Server Components (Recommended)
import { createClient } from "@/lib/supabase/server";
export default async function Page() {
const supabase = await createClient();
const { data: profiles } = await supabase
.from("profiles")
.select("*");
return <ProfileList profiles={profiles} />;
}Client Components
"use client";
import { createClient } from "@/lib/supabase/client";
import { useEffect, useState } from "react";
export function ProfileList() {
const [profiles, setProfiles] = useState([]);
const supabase = createClient();
useEffect(() => {
supabase
.from("profiles")
.select("*")
.then(({ data }) => setProfiles(data || []));
}, []);
return <ul>{profiles.map(p => <li key={p.id}>{p.full_name}</li>)}</ul>;
}Server Actions
"use server";
import { createClient } from "@/lib/supabase/server";
export async function updateProfile(formData: FormData) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
await supabase
.from("profiles")
.update({ full_name: formData.get("name") })
.eq("id", user.id);
revalidatePath("/dashboard/settings");
}Row Level Security (RLS)
All tables have RLS enabled. Users can only access their own data:
-- Enable RLS
alter table profiles enable row level security;
-- Policy: Users can only see their own profile
create policy "Users can view own profile"
on profiles for select
using (auth.uid() = id);
-- Policy: Users can only update their own profile
create policy "Users can update own profile"
on profiles for update
using (auth.uid() = id);Migrations
Create a Migration
# Create a new migration
supabase migration new add_feature_table
# Edit the migration file
# supabase/migrations/TIMESTAMP_add_feature_table.sqlApply Migrations
# Push to remote database
supabase db push
# Or reset and reapply all
supabase db resetExample Migration
-- supabase/migrations/001_create_posts.sql
create table posts (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users on delete cascade not null,
title text not null,
content text,
published boolean default false,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- Enable RLS
alter table posts enable row level security;
-- Policies
create policy "Users can view own posts"
on posts for select using (auth.uid() = user_id);
create policy "Users can create posts"
on posts for insert with check (auth.uid() = user_id);
create policy "Users can update own posts"
on posts for update using (auth.uid() = user_id);
create policy "Users can delete own posts"
on posts for delete using (auth.uid() = user_id);
-- Published posts are public
create policy "Anyone can view published posts"
on posts for select using (published = true);Type Generation
Generate TypeScript types from your database:
# Generate types
supabase gen types typescript --project-id your-project-id > src/types/database.types.tsThen use in your code:
import { Database } from "@/types/database.types";
type Profile = Database["public"]["Tables"]["profiles"]["Row"];Local Development
Run Supabase locally:
# Start local Supabase
supabase start
# Stop
supabase stop
# Reset database
supabase db reset