Supabase for Beginners: Build Your First Backend in 30 Minutes [2025]
![Supabase for Beginners: Build Your First Backend in 30 Minutes [2025]](/blog/images/supabase-beginners.jpg)
Supabase for Beginners: Build Your First Backend in 30 Minutes
If you've ever wanted to build a full-featured backend without managing servers, Supabase is the perfect solution. In this tutorial, you'll learn how to set up a complete backend with authentication, database, and real-time subscriptions in just 30 minutes.
What is Supabase?
Supabase is an open-source Firebase alternative built on PostgreSQL. It provides everything you need for a modern backend:
- PostgreSQL Database – Powerful relational database
- Authentication – Built-in user management
- Auto-generated APIs – REST and GraphQL endpoints
- Real-time subscriptions – Live data updates
- Storage – File and media handling
- Edge Functions – Serverless functions at the edge
The best part? It's completely open-source and can be self-hosted if needed.
Prerequisites
Before we begin, you'll need:
- A free Supabase account (sign up here)
- Basic knowledge of JavaScript
- Node.js installed (v16 or higher)
- A code editor (VS Code recommended)
Step 1: Create Your Supabase Project
- Log in to your Supabase dashboard
- Click "New Project"
- Fill in the project details:
- Name: my-first-backend
- Database Password: Choose a strong password (save this!)
- Region: Select the closest to your users
- Click "Create new project"
Your project will take 1-2 minutes to provision. While you wait, let's set up your local development environment.
Step 2: Set Up Your Frontend Project
Create a new directory and initialize a basic web project:
mkdir supabase-todo-app
cd supabase-todo-app
npm init -y
npm install @supabase/supabase-js
Create an index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Supabase Todo App</title>
<style>
body {
font-family:
system-ui,
-apple-system,
sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
input,
button {
padding: 10px;
margin: 5px 0;
font-size: 16px;
}
.todo-item {
padding: 10px;
margin: 5px 0;
background: #f0f0f0;
border-radius: 5px;
display: flex;
justify-content: space-between;
}
</style>
</head>
<body>
<h1>Supabase Todo App</h1>
<div id="auth-section">
<h2>Login / Sign Up</h2>
<input type="email" id="email" placeholder="Your email" />
<input type="password" id="password" placeholder="Your password" />
<button onclick="signUp()">Sign Up</button>
<button onclick="signIn()">Sign In</button>
</div>
<div id="app-section" style="display: none;">
<button onclick="signOut()">Sign Out</button>
<h2>My Todos</h2>
<input type="text" id="new-todo" placeholder="Add a new todo" />
<button onclick="addTodo()">Add Todo</button>
<div id="todos"></div>
</div>
<script type="module" src="app.js"></script>
</body>
</html>
Step 3: Initialize Supabase Client
Go back to your Supabase dashboard and grab your project credentials:
- Click on "Settings" (gear icon)
- Select "API"
- Copy your "Project URL" and "anon public" API key
Create an app.js file:
import { createClient } from '@supabase/supabase-js';
// Replace with your Supabase project credentials
const SUPABASE_URL = 'https://your-project.supabase.co';
const SUPABASE_ANON_KEY = 'your-anon-key';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
// Check if user is already logged in
supabase.auth.onAuthStateChange((event, session) => {
if (session) {
showApp();
loadTodos();
} else {
showAuth();
}
});
function showAuth() {
document.getElementById('auth-section').style.display = 'block';
document.getElementById('app-section').style.display = 'none';
}
function showApp() {
document.getElementById('auth-section').style.display = 'none';
document.getElementById('app-section').style.display = 'block';
}
// Make functions globally available
window.signUp = signUp;
window.signIn = signIn;
window.signOut = signOut;
window.addTodo = addTodo;
window.deleteTodo = deleteTodo;
Step 4: Set Up Authentication
Add authentication functions to your app.js:
async function signUp() {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const { data, error } = await supabase.auth.signUp({
email: email,
password: password,
});
if (error) {
alert('Error signing up: ' + error.message);
} else {
alert('Check your email for verification link!');
}
}
async function signIn() {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const { data, error } = await supabase.auth.signInWithPassword({
email: email,
password: password,
});
if (error) {
alert('Error signing in: ' + error.message);
}
}
async function signOut() {
const { error } = await supabase.auth.signOut();
if (error) {
alert('Error signing out: ' + error.message);
}
}
Step 5: Create Database Table
Now let's create a database table for our todos. In your Supabase dashboard:
- Click on "Table Editor" in the sidebar
- Click "Create a new table"
- Configure the table:
- Name: todos
- Enable Row Level Security: Yes
Add these columns:
id(int8, primary key, auto-increment) – already createdcreated_at(timestamptz, default: now()) – already createduser_id(uuid, references auth.users)task(text)is_complete(bool, default: false)
Click "Save" to create the table.
Step 6: Set Up Row Level Security (RLS)
Row Level Security ensures users can only access their own data. In the Table Editor:
- Click on your "todos" table
- Click "RLS" (Row Level Security) at the top
- Click "New Policy"
- Select "Enable read access for authenticated users only"
- Modify the USING expression:
auth.uid() = user_id
Create another policy for INSERT:
- Policy name: Users can insert their own todos
- Allowed operation: INSERT
- WITH CHECK expression:
auth.uid() = user_id
Create a policy for UPDATE:
- Policy name: Users can update their own todos
- Allowed operation: UPDATE
- USING expression:
auth.uid() = user_id
Create a policy for DELETE:
- Policy name: Users can delete their own todos
- Allowed operation: DELETE
- USING expression:
auth.uid() = user_id
Step 7: Implement CRUD Operations
Add these functions to your app.js:
async function loadTodos() {
const { data: todos, error } = await supabase
.from('todos')
.select('*')
.order('created_at', { ascending: false });
if (error) {
console.error('Error loading todos:', error);
return;
}
displayTodos(todos);
}
function displayTodos(todos) {
const todosDiv = document.getElementById('todos');
todosDiv.innerHTML = '';
todos.forEach(todo => {
const todoDiv = document.createElement('div');
todoDiv.className = 'todo-item';
todoDiv.innerHTML = `
<span style="${todo.is_complete ? 'text-decoration: line-through' : ''}">
${todo.task}
</span>
<div>
<button onclick="toggleTodo(${todo.id}, ${!todo.is_complete})">
${todo.is_complete ? 'Undo' : 'Complete'}
</button>
<button onclick="deleteTodo(${todo.id})">Delete</button>
</div>
`;
todosDiv.appendChild(todoDiv);
});
}
async function addTodo() {
const task = document.getElementById('new-todo').value;
if (!task) return;
const {
data: { user },
} = await supabase.auth.getUser();
const { data, error } = await supabase.from('todos').insert([{ task: task, user_id: user.id }]);
if (error) {
alert('Error adding todo: ' + error.message);
} else {
document.getElementById('new-todo').value = '';
loadTodos();
}
}
async function toggleTodo(id, isComplete) {
const { error } = await supabase.from('todos').update({ is_complete: isComplete }).eq('id', id);
if (error) {
console.error('Error updating todo:', error);
} else {
loadTodos();
}
}
async function deleteTodo(id) {
const { error } = await supabase.from('todos').delete().eq('id', id);
if (error) {
console.error('Error deleting todo:', error);
} else {
loadTodos();
}
}
window.toggleTodo = toggleTodo;
Step 8: Add Real-time Subscriptions (Bonus!)
Make your app update in real-time when data changes:
// Add this to your app.js
supabase
.channel('todos')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'todos',
},
() => {
loadTodos();
}
)
.subscribe();
Now when you add, update, or delete a todo in one browser tab, it will automatically update in all other tabs!
Step 9: Test Your Application
Start a local server to test your app:
npx http-server -p 8080
Visit http://localhost:8080 and test:
- Sign up with your email
- Check your email for verification
- Sign in
- Add some todos
- Complete and delete todos
- Open multiple tabs to see real-time updates
What You've Built
Congratulations! You've just built a complete backend application with:
- User authentication and authorization
- PostgreSQL database with proper security
- Auto-generated REST API
- Real-time data synchronization
- Row-level security for data privacy
Next Steps
Now that you understand the basics, explore these advanced Supabase features:
- Storage: Upload and manage files
- Edge Functions: Write serverless backend logic
- Database Functions: Create stored procedures
- Triggers: Automate database operations
- Full-text Search: Add search capabilities
Integrate Supabase with Other Tools
Want to automate workflows with your Supabase backend? Check out our Make.com and n8n tool pages to learn how to connect Supabase with hundreds of other applications.
Join the House of Loops Community
Ready to take your backend skills to the next level? Join the House of Loops community where we share:
- Advanced Supabase tutorials and templates
- $100K+ in startup credits (including Supabase credits)
- Live workshops on backend development
- A community of 1,000+ developers building real projects
Whether you're building a SaaS product, mobile app, or automation workflow, Supabase provides the backend infrastructure you need to move fast and scale confidently. Happy building!
House of Loops Team
House of Loops is a technology-focused community for learning and implementing advanced automation workflows using n8n, Strapi, AI/LLM, and DevSecOps tools.
Join Our Community![Firebase vs Supabase: Complete Setup Comparison [2025]](/blog/images/firebase-vs-supabase.jpg)
![Scaling n8n for Production: Performance Optimization [2025]](/blog/images/scaling-n8n-performance.jpg)
![Retool Tutorial: Build Internal Tools Fast [2025]](/blog/images/retool-tutorial.jpg)