1Create a Supabase Project
- Sign up at supabase.com (GitHub login recommended)
- Click New project
- Enter a project name
- Set a database password (save it somewhere safe!)
- Select a region:
Northeast Asia (Seoul)recommended - Click Create new project
Info
Project creation takes about 1-2 minutes. Once complete, you can find your API keys in the dashboard.
2Create Tables (SQL Editor)
Open the SQL Editor in the dashboard and run the schema below.
-- Create todos table
create table todos (
id uuid default gen_random_uuid() primary key,
user_id uuid references auth.users(id) on delete cascade,
title text not null,
completed boolean default false,
created_at timestamptz default now()
);
-- Enable RLS (Row Level Security)
alter table todos enable row level security;
-- Policy: users can only view their own data
create policy "Users can view own todos"
on todos for select
using (auth.uid() = user_id);
-- Policy: users can only insert their own data
create policy "Users can insert own todos"
on todos for insert
with check (auth.uid() = user_id);
-- Policy: users can only update their own data
create policy "Users can update own todos"
on todos for update
using (auth.uid() = user_id);
-- Policy: users can only delete their own data
create policy "Users can delete own todos"
on todos for delete
using (auth.uid() = user_id);Why RLS matters
Without RLS, all users can access all data.
auth.uid() returns the currently logged-in user's ID.
3Client Setup (React)
Install Dependencies
npm install @supabase/supabase-js @tanstack/react-queryEnvironment Variables
# .env.local
VITE_SUPABASE_URL=https://your-project-id.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-keyFind these in the Supabase dashboard under Settings → API.
Supabase Client
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);4CRUD Example (React Query + Supabase)
Type Definitions
// src/types/todo.ts
export interface Todo {
id: string;
user_id: string;
title: string;
completed: boolean;
created_at: string;
}
export type CreateTodoInput = Pick<Todo, 'title'>;
export type UpdateTodoInput = Partial<Pick<Todo, 'title' | 'completed'>>;Hooks (TanStack Query)
// src/hooks/useTodos.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '../lib/supabase';
import type { Todo, CreateTodoInput, UpdateTodoInput } from '../types/todo';
// Fetch list
export function useTodos() {
return useQuery({
queryKey: ['todos'],
queryFn: async () => {
const { data, error } = await supabase
.from('todos')
.select('*')
.order('created_at', { ascending: false });
if (error) throw error;
return data as Todo[];
},
});
}
// Create
export function useCreateTodo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: CreateTodoInput) => {
const { data: { user } } = await supabase.auth.getUser();
if (!user) throw new Error('Not authenticated');
const { data, error } = await supabase
.from('todos')
.insert({ ...input, user_id: user.id })
.select()
.single();
if (error) throw error;
return data as Todo;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}
// Update
export function useUpdateTodo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, ...input }: UpdateTodoInput & { id: string }) => {
const { data, error } = await supabase
.from('todos')
.update(input)
.eq('id', id)
.select()
.single();
if (error) throw error;
return data as Todo;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}
// Delete
export function useDeleteTodo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id: string) => {
const { error } = await supabase
.from('todos')
.delete()
.eq('id', id);
if (error) throw error;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}5Authentication Example (Optional)
With Supabase Auth, you can easily implement email/password login.
// src/hooks/useAuth.ts
import { useState, useEffect } from 'react';
import { supabase } from '../lib/supabase';
import type { User } from '@supabase/supabase-js';
export function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check current session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
setLoading(false);
});
// Listen for auth state changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null);
}
);
return () => subscription.unsubscribe();
}, []);
// Sign up
const signUp = async (email: string, password: string) => {
const { error } = await supabase.auth.signUp({ email, password });
if (error) throw error;
};
// Sign in
const signIn = async (email: string, password: string) => {
const { error } = await supabase.auth.signInWithPassword({ email, password });
if (error) throw error;
};
// Sign out
const signOut = async () => {
const { error } = await supabase.auth.signOut();
if (error) throw error;
};
return { user, loading, signUp, signIn, signOut };
}Social login
Social login with GitHub, Google, Discord, and more is also supported. Configure it in the Supabase dashboard under Authentication → Providers.
When should you use Supabase?
✅ Recommended
- Building an MVP quickly
- Focusing on frontend only without backend development
- When realtime features are needed
- When you want auth + DB in one solution
⚠️ Consider carefully
- Complex business logic requirements
- Heavy external API integrations
- Custom backend logic is essential
- Data structure changes frequently
Next steps
- Auth Guide - Compare JWT, Session, OAuth
- Supabase Official Docs - More detailed features
- Cycle 5: Runtime - Environment configuration management