Database Integration
This starter template includes full integration with Supabase Database 2.x, providing a PostgreSQL database with Row Level Security (RLS) policies and TypeScript type safety.
Database Schema
The core database schema includes the following tables:
Users Table
The users table extends Supabase Auth's built-in users with additional profile information:
CREATE TABLE public.profiles (
id UUID REFERENCES auth.users(id) PRIMARY KEY,
display_name TEXT,
avatar_url TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
-- Set up Row Level Security
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
-- Create secure policies
CREATE POLICY "Users can view their own profile"
ON public.profiles
FOR SELECT
USING (auth.uid() = id);
CREATE POLICY "Users can update their own profile"
ON public.profiles
FOR UPDATE
USING (auth.uid() = id);
-- Create a trigger to create a profile when a user signs up
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.profiles (id)
VALUES (NEW.id);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
TypeScript Integration
The database schema is fully typed using TypeScript 5.4+ to provide type safety:
// types/database.types.ts
export interface Database {
public: {
Tables: {
profiles: {
Row: {
id: string;
display_name: string | null;
avatar_url: string | null;
created_at: string;
updated_at: string;
};
Insert: {
id: string;
display_name?: string | null;
avatar_url?: string | null;
created_at?: string;
updated_at?: string;
};
Update: {
id?: string;
display_name?: string | null;
avatar_url?: string | null;
created_at?: string;
updated_at?: string;
};
};
};
};
}
Database Utilities
The starter template provides utility functions for common database operations:
// lib/db/users.ts
import { supabase } from '../supabase';
import type { Database } from '@/types/database.types';
type Profile = Database['public']['Tables']['profiles']['Row'];
export const getUserProfile = async (userId: string): Promise<Profile | null> => {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.single();
if (error) {
console.error('Error fetching user profile:', error);
return null;
}
return data;
};
export const updateUserProfile = async (
userId: string,
updates: Partial<Profile>
): Promise<Profile | null> => {
const { data, error } = await supabase
.from('profiles')
.update(updates)
.eq('id', userId)
.select()
.single();
if (error) {
console.error('Error updating user profile:', error);
return null;
}
return data;
};
Row Level Security (RLS)
The template uses Supabase's Row Level Security to protect data at the database level. RLS ensures that users can only access data they are authorized to see, even if the application code has a security vulnerability.
Key RLS policies included:
- Users can only read their own profile data
- Users can only update their own profile data
- Anonymous users have limited access to public data
Using the Database in Components
Example of using the database in a React component:
import { useEffect, useState } from 'react';
import { useAuth } from '@/contexts/AuthContext';
import { getUserProfile } from '@/lib/db/users';
import type { Database } from '@/types/database.types';
type Profile = Database['public']['Tables']['profiles']['Row'];
const ProfilePage = () => {
const { user } = useAuth();
const [profile, setProfile] = useState<Profile | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProfile = async () => {
if (user) {
const profileData = await getUserProfile(user.id);
setProfile(profileData);
}
setLoading(false);
};
fetchProfile();
}, [user]);
if (loading) return <div>Loading...</div>;
if (!profile) return <div>Profile not found</div>;
return (
<div>
<h1>Profile</h1>
<p>Display Name: {profile.display_name || 'Not set'}</p>
{/* More profile details */}
</div>
);
};
Realtime Subscriptions
The starter template supports Supabase Realtime for live data updates:
import { useEffect, useState } from 'react';
import { supabase } from '@/lib/supabase';
import type { Database } from '@/types/database.types';
type Profile = Database['public']['Tables']['profiles']['Row'];
const useRealtimeProfile = (userId: string) => {
const [profile, setProfile] = useState<Profile | null>(null);
useEffect(() => {
// Initial fetch
const fetchProfile = async () => {
const { data } = await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.single();
setProfile(data);
};
fetchProfile();
// Set up realtime subscription
const subscription = supabase
.channel('profile-changes')
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'profiles',
filter: `id=eq.${userId}`,
},
(payload) => {
setProfile(payload.new as Profile);
}
)
.subscribe();
return () => {
supabase.removeChannel(subscription);
};
}, [userId]);
return profile;
};
Next Steps
- Learn about testing to understand how to test database operations
- Check the API routes documentation for implementing server-side endpoints