ShipKit

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

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.sql

Apply Migrations

# Push to remote database
supabase db push

# Or reset and reapply all
supabase db reset

Example 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.ts

Then 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

On this page