Beginner's Guide: Connect React with Supabase 🚀
If you already know basic React (components, useState, useEffect), this guide will show you how to connect your React app to Supabase — an open-source Firebase alternative — and build a simple To-Do app with full CRUD (Create, Read, Update, Delete).
Let's go step by step. No prior Supabase knowledge needed.
What is Supabase?
Supabase gives you a Postgres database, authentication, and instant APIs — without writing any backend code. Think of it as a backend-as-a-service.
For this guide, we'll just use the database + auto-generated API part.
Step 1: Create a Supabase Project
- Go to supabase.com and sign up (GitHub login is fastest).
- Click New Project.
- Fill in:
-
Name:
todo-app(anything you like) - Database Password: save this somewhere safe
- Region: pick the closest one to you
-
Name:
- Click Create new project and wait ~1-2 minutes while Supabase sets everything up.
Step 2: Create the todos Table
- In your Supabase project dashboard, go to the Table Editor (left sidebar).
- Click New Table.
- Name it
todos. - Add these columns (in addition to the default
idandcreated_at):
| Column Name | Type | Default |
|---|---|---|
task |
text |
— |
is_complete |
bool |
false |
- Click Save.
💡 Tip: You can also do this via the SQL Editor by running:
create table todos ( id bigint generated by default as identity primary key, task text not null, is_complete boolean default false, created_at timestamp with time zone default now() );
Turn off Row Level Security (for learning purposes only)
Go to Authentication > Policies (or Table Editor > todos > RLS), and disable RLS for now so your students can read/write freely without setting up auth.
⚠️ Important for your session: Tell your juniors this is only for a demo/learning project. In a real production app, RLS should always be enabled with proper policies.
Step 3: Get Your API Keys
- Go to Project Settings > API.
- Copy two things:
-
Project URL (looks like
https://xxxxx.supabase.co) - anon public key (a long string)
-
Project URL (looks like
You'll need both in your React app.
Step 4: Set Up the React Project
If they already have a React project (Vite recommended), just install the Supabase client:
npm install @supabase/supabase-js
Enter fullscreen mode Exit fullscreen mode
If starting fresh:
npm create vite@latest todo-app -- --template react
cd todo-app
npm install
npm install @supabase/supabase-js
Enter fullscreen mode Exit fullscreen mode
Step 5: Create a Supabase Client File
Create a file src/supabaseClient.js:
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'YOUR_PROJECT_URL'
const supabaseAnonKey = 'YOUR_ANON_KEY'
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
Enter fullscreen mode Exit fullscreen mode
💡 Best practice: use environment variables instead of hardcoding keys.
Create a.envfile:VITE_SUPABASE_URL=your_project_url VITE_SUPABASE_ANON_KEY=your_anon_keyThen use:
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY(For a beginner live demo, hardcoding is fine to keep things simple — just mention the
.envbest practice.)
Step 6: Build the To-Do Component
Replace src/App.jsx with this:
import { useState, useEffect } from 'react'
import { supabase } from './supabaseClient'
import './App.css'
function App() {
const [todos, setTodos] = useState([])
const [newTask, setNewTask] = useState('')
const [loading, setLoading] = useState(true)
// Fetch todos when component loads
useEffect(() => {
fetchTodos()
}, [])
async function fetchTodos() {
setLoading(true)
const { data, error } = await supabase
.from('todos')
.select('*')
.order('created_at', { ascending: false })
if (error) console.error('Error fetching todos:', error)
else setTodos(data)
setLoading(false)
}
async function addTodo(e) {
e.preventDefault()
if (!newTask.trim()) return
const { data, error } = await supabase
.from('todos')
.insert([{ task: newTask, is_complete: false }])
.select()
if (error) console.error('Error adding todo:', error)
else {
setTodos([data[0], ...todos])
setNewTask('')
}
}
async function toggleComplete(id, currentStatus) {
const { error } = await supabase
.from('todos')
.update({ is_complete: !currentStatus })
.eq('id', id)
if (error) console.error('Error updating todo:', error)
else {
setTodos(todos.map(t => t.id === id ? { ...t, is_complete: !currentStatus } : t))
}
}
async function deleteTodo(id) {
const { error } = await supabase
.from('todos')
.delete()
.eq('id', id)
if (error) console.error('Error deleting todo:', error)
else setTodos(todos.filter(t => t.id !== id))
}
return (
<div style={{ maxWidth: 500, margin: '40px auto', fontFamily: 'sans-serif' }}>
<h1>📝 Supabase To-Do App</h1>
<form onSubmit={addTodo} style={{ display: 'flex', gap: 8, marginBottom: 20 }}>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="Add a new task..."
style={{ flex: 1, padding: 8 }}
/>
<button type="submit" style={{ padding: '8px 16px' }}>Add</button>
</form>
{loading ? (
<p>Loading...</p>
) : (
<ul style={{ listStyle: 'none', padding: 0 }}>
{todos.map((todo) => (
<li
key={todo.id}
style={{
display: 'flex',
alignItems: 'center',
gap: 10,
padding: 10,
borderBottom: '1px solid #eee',
}}
>
<input
type="checkbox"
checked={todo.is_complete}
onChange={() => toggleComplete(todo.id, todo.is_complete)}
/>
<span
style={{
flex: 1,
textDecoration: todo.is_complete ? 'line-through' : 'none',
color: todo.is_complete ? '#999' : '#000',
}}
>
{todo.task}
</span>
<button onClick={() => deleteTodo(todo.id)} style={{ color: 'red' }}>
Delete
</button>
</li>
))}
</ul>
)}
</div>
)
}
export default App
Enter fullscreen mode Exit fullscreen mode
Step 7: Run It
npm run dev
Enter fullscreen mode Exit fullscreen mode
Open the local URL shown in the terminal — you should be able to add, complete, and delete tasks, and see them saved in your Supabase Table Editor in real time.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.