Learn how to structure your database tables and establish relationships between users and their data using Supabase.
Example schema for a social platform with posts, comments, and likes. All tables are related to the user profile.
1-- Posts table2create table public.posts (3 id uuid default gen_random_uuid() primary key,4 user_id uuid references public.profiles(id) on delete cascade not null,5 content text not null,6 created_at timestamp with time zone default timezone('utc'::text, now()) not null,7 updated_at timestamp with time zone default timezone('utc'::text, now()) not null8);910-- Comments table11create table public.comments (12 id uuid default gen_random_uuid() primary key,13 post_id uuid references public.posts(id) on delete cascade not null,14 user_id uuid references public.profiles(id) on delete cascade not null,15 content text not null,16 created_at timestamp with time zone default timezone('utc'::text, now()) not null,17 updated_at timestamp with time zone default timezone('utc'::text, now()) not null18);1920-- Likes table21create table public.likes (22 id uuid default gen_random_uuid() primary key,23 user_id uuid references public.profiles(id) on delete cascade not null,24 post_id uuid references public.posts(id) on delete cascade not null,25 created_at timestamp with time zone default timezone('utc'::text, now()) not null,26 -- Ensure a user can only like a post once27 unique(user_id, post_id)28);2930-- Enable RLS31alter table public.posts enable row level security;32alter table public.comments enable row level security;33alter table public.likes enable row level security;3435-- Update triggers36create trigger handle_updated_at before update on public.posts37 for each row execute procedure moddatetime (updated_at);3839create trigger handle_updated_at before update on public.comments40 for each row execute procedure moddatetime (updated_at);
Each table is related to the profiles table through the user_id foreign key. This allows you to track ownership and create relationships between users and their content.
1// Example type definitions2export type Tables = {3 profiles: {4 Row: {5 id: string6 username: string7 full_name: string | null8 avatar_url: string | null9 is_admin: boolean10 created_at: string11 updated_at: string12 }13 Insert: {14 id: string15 username: string16 full_name?: string | null17 avatar_url?: string | null18 is_admin?: boolean19 }20 }21 posts: {22 Row: {23 id: string24 user_id: string25 content: string26 created_at: string27 updated_at: string28 }29 Insert: {30 id?: string31 user_id: string32 content: string33 }34 }35 comments: {36 Row: {37 id: string38 post_id: string39 user_id: string40 content: string41 created_at: string42 updated_at: string43 }44 Insert: {45 id?: string46 post_id: string47 user_id: string48 content: string49 }50 }51 likes: {52 Row: {53 id: string54 user_id: string55 post_id: string56 created_at: string57 }58 Insert: {59 id?: string60 user_id: string61 post_id: string62 }63 }64}
Implement Row Level Security policies to control access to social features.
1-- Posts policies2create policy "Anyone can view posts"3 on public.posts for select4 using ( true );56create policy "Authenticated users can create posts"7 on public.posts for insert8 with check ( auth.role() = 'authenticated' );910create policy "Users can update their own posts"11 on public.posts for update12 using ( auth.uid() = user_id );1314create policy "Users can delete their own posts"15 on public.posts for delete16 using ( auth.uid() = user_id );1718-- Comments policies19create policy "Anyone can view comments"20 on public.comments for select21 using ( true );2223create policy "Authenticated users can create comments"24 on public.comments for insert25 with check ( auth.role() = 'authenticated' );2627create policy "Users can update their own comments"28 on public.comments for update29 using ( auth.uid() = user_id );3031create policy "Users can delete their own comments"32 on public.comments for delete33 using ( auth.uid() = user_id );3435-- Likes policies36create policy "Anyone can view likes"37 on public.likes for select38 using ( true );3940create policy "Authenticated users can create likes"41 on public.likes for insert42 with check ( auth.role() = 'authenticated' );4344create policy "Users can delete their own likes"45 on public.likes for delete46 using ( auth.uid() = user_id );
Here are some example queries showing how to interact with the database using the Supabase client.
1import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'2import type { Database } from '@/lib/database.types'34export async function getPosts() {5 const supabase = createClientComponentClient<Database>()67 const { data: posts, error } = await supabase8 .from('posts')9 .select(`10 id,11 content,12 created_at,13 profiles (14 id,15 username,16 avatar_url17 ),18 likes (19 id,20 user_id21 ),22 comments (23 id,24 content,25 created_at,26 profiles (27 id,28 username,29 avatar_url30 )31 )32 `)33 .order('created_at', { ascending: false })3435 if (error) {36 console.error('Error fetching posts:', error)37 return []38 }3940 return posts41}4243export async function createPost(content: string) {44 const supabase = createClientComponentClient<Database>()4546 const { data: { session } } = await supabase.auth.getSession()47 if (!session) return null4849 const { data, error } = await supabase50 .from('posts')51 .insert({52 user_id: session.user.id,53 content,54 })55 .select()56 .single()5758 if (error) {59 console.error('Error creating post:', error)60 return null61 }6263 return data64}6566export async function toggleLike(postId: string) {67 const supabase = createClientComponentClient<Database>()6869 const { data: { session } } = await supabase.auth.getSession()70 if (!session) return null7172 const { data: existingLike } = await supabase73 .from('likes')74 .select()75 .match({ user_id: session.user.id, post_id: postId })76 .single()7778 if (existingLike) {79 const { error } = await supabase80 .from('likes')81 .delete()82 .match({ id: existingLike.id })8384 if (error) {85 console.error('Error removing like:', error)86 return null87 }8889 return null90 }9192 const { data, error } = await supabase93 .from('likes')94 .insert({95 user_id: session.user.id,96 post_id: postId,97 })98 .select()99 .single()100101 if (error) {102 console.error('Error adding like:', error)103 return null104 }105106 return data107}