Supabase vs Firebase
When building modern web applications, choosing the right backend-as-a-service platform can significantly impact your development experience. Two platforms have emerged as clear leaders in this space: Firebase, Google's mature offering that has been around since 2011, and Supabase, the open-source challenger that has been gaining serious traction since 2020.
Both platforms promise to handle your backend complexity, allowing you to focus on building great user experiences. They offer real-time databases, authentication, file storage, and serverless functions. But despite these similarities, they take fundamentally different approaches to solving the same problems.
Firebase builds on Google's NoSQL document database technology with Firestore, while Supabase gives you a full PostgreSQL database with all the relational power you'd expect. Firebase locks you into Google's ecosystem with proprietary APIs, whereas Supabase embraces open standards and provides direct SQL access. These philosophical differences create distinct advantages and trade-offs that will significantly impact your development workflow.
This guide will walk you through both platforms, comparing their setup processes, core features, and real-world usage patterns. You'll see practical code examples for common tasks and understand which platform fits different project requirements.
What is Firebase?
Firebase started as a real-time database company before Google acquired it in 2014. Since then, Google has expanded it into a comprehensive application development platform that handles everything from authentication to analytics. Firebase's core philosophy centers on simplicity and rapid development, letting you build and deploy applications without managing servers or infrastructure.
The platform's flagship feature is Firestore, a NoSQL document database that automatically scales and syncs data in real-time across all connected clients. When you update a document in Firestore, every user sees the change instantly without any additional code. This real-time capability makes Firebase particularly attractive for collaborative applications, chat systems, and live dashboards.
Firebase Authentication integrates seamlessly with popular identity providers like Google, Facebook, Twitter, and GitHub. You can also implement custom authentication flows using email/password, phone numbers, or anonymous sign-in. The authentication system works hand-in-hand with Firebase Security Rules, which let you define fine-grained permissions for your data.
Beyond the database and authentication, Firebase includes Cloud Functions for serverless backend logic, Cloud Storage for file uploads, Hosting for static websites, and comprehensive analytics tools. Google Cloud Messaging handles push notifications across iOS, Android, and web platforms. This integrated ecosystem means you rarely need to leave Firebase's umbrella for core application functionality.
What is Supabase?
Supabase positions itself as "the open source Firebase alternative," but this description undersells what makes it unique. While Firebase abstracts away database complexity with NoSQL documents, Supabase gives you direct access to a full-featured PostgreSQL database. This means you get ACID transactions, complex queries with joins, foreign key constraints, and all the relational database features that many developers prefer.
The platform's real-time functionality works differently from Firebase. Instead of document-level subscriptions, Supabase lets you subscribe to any PostgreSQL query result. You can listen for changes on specific tables, filtered results, or even complex joins between multiple tables. Under the hood, Supabase uses PostgreSQL's built-in replication features to broadcast changes through WebSockets.
Supabase's authentication system supports the same social providers as Firebase, but it's built on industry-standard JSON Web Tokens rather than proprietary Google tokens. This means you can easily integrate with other services or migrate away from Supabase if needed. The platform also includes Row Level Security policies that leverage PostgreSQL's native security features for data access control.
What sets Supabase apart is its commitment to open source and transparency. You can inspect the entire codebase, contribute improvements, or even self-host the entire platform. The company provides a hosted version for convenience, but you're never locked into their infrastructure. This approach appeals to developers who want the benefits of a managed service without vendor lock-in concerns.
Installation and setup
Getting started with Firebase requires installing the Firebase CLI and initializing your project through Google's console. You'll need a Google account and must create a new project in the Firebase console before you can begin development.
npm install -g firebase-tools
firebase login
firebase init
The initialization process walks you through selecting Firebase features for your project. You'll configure Firestore, Authentication, Hosting, and any other services you plan to use. Firebase generates configuration files and sets up your project structure based on your selections.
// firebase-config.js
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { getAuth } from 'firebase/auth';
const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project.appspot.com",
messagingSenderId: "123456789",
appId: "your-app-id"
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth(app);
Supabase setup follows a more traditional database-centric approach. You create a project through the Supabase dashboard, which provisions a PostgreSQL database and generates API credentials. The setup process feels more like configuring a traditional database application than a NoSQL service.
npm install @supabase/supabase-js
Supabase requires only a single configuration object with your project URL and API key. The simplicity here is deceptive—behind the scenes, you're getting access to a full PostgreSQL instance with automatically generated REST and GraphQL APIs.
// supabase-config.js
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = 'https://your-project.supabase.co';
const supabaseKey = 'your-anon-key';
export const supabase = createClient(supabaseUrl, supabaseKey);
The key difference in setup philosophy becomes apparent immediately. Firebase abstracts database schema creation through code, while Supabase encourages you to design your database schema using SQL or their visual table editor. This reflects their different approaches to data modeling and application architecture.
Database and data management
Firebase's Firestore organizes data as collections of documents, similar to MongoDB. Each document contains fields with values, and documents can contain subcollections for nested data structures. This flexibility makes it easy to store complex, hierarchical data without predefined schemas.
// Adding data to Firestore
import { collection, addDoc, doc, setDoc } from 'firebase/firestore';
// Add a new document with auto-generated ID
const docRef = await addDoc(collection(db, 'books'), {
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald',
publishedYear: 1925,
genres: ['fiction', 'classic'],
metadata: {
pages: 180,
isbn: '978-0-7432-7356-5'
}
});
// Set a document with specific ID
await setDoc(doc(db, 'books', 'gatsby'), {
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald',
publishedYear: 1925
});
Querying Firestore requires understanding its limitations. You can filter documents, order results, and limit the number of returned documents, but complex queries with multiple conditions or joins between collections aren't supported. This pushes you toward denormalizing data and duplicating information across documents.
// Querying Firestore
import { collection, query, where, orderBy, limit, getDocs } from 'firebase/firestore';
const q = query(
collection(db, 'books'),
where('publishedYear', '>=', 1900),
where('publishedYear', '<=', 2000),
orderBy('publishedYear'),
limit(10)
);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.id, doc.data());
});
Supabase gives you direct access to PostgreSQL, which means you work with traditional tables, rows, and columns. You define your schema using SQL DDL statements or Supabase's visual table editor. This structured approach enforces data consistency and enables complex relational queries.
// Adding data to Supabase
const { data, error } = await supabase
.from('books')
.insert({
title: 'The Great Gatsby',
author: 'F. Scott Fitzgerald',
published_year: 1925,
genres: ['fiction', 'classic'], // PostgreSQL array type
isbn: '978-0-7432-7356-5'
});
// Insert multiple rows
const { data, error } = await supabase
.from('books')
.insert([
{ title: 'Book 1', author: 'Author 1', published_year: 2020 },
{ title: 'Book 2', author: 'Author 2', published_year: 2021 }
]);
Supabase's query capabilities mirror PostgreSQL's full SQL feature set. You can perform complex joins, aggregations, subqueries, and window functions. The JavaScript client provides a fluent API that translates to SQL, but you can also execute raw SQL when needed.
// Complex querying in Supabase
const { data, error } = await supabase
.from('books')
.select(`
title,
author,
published_year,
reviews (
rating,
comment,
user_profiles (
username
)
)
`)
.gte('published_year', 1900)
.lte('published_year', 2000)
.order('published_year')
.limit(10);
// Raw SQL for complex operations
const { data, error } = await supabase
.rpc('get_books_with_avg_rating', {
min_year: 1900,
max_year: 2000
});
The data modeling differences between these platforms influence your entire application architecture. Firebase's document model works well for applications with simple, hierarchical data structures, while Supabase's relational model excels when you need data consistency and complex queries across multiple related entities.
Real-time functionality
Firebase built its reputation on real-time data synchronization. When you attach a listener to a Firestore document or collection, your application receives instant updates whenever the data changes. This real-time capability works across all connected clients without additional server-side code.
// Real-time listeners in Firebase
import { doc, onSnapshot, collection, query, where } from 'firebase/firestore';
// Listen to a single document
const unsubscribe = onSnapshot(doc(db, 'books', 'gatsby'), (doc) => {
if (doc.exists()) {
console.log('Current data:', doc.data());
}
});
// Listen to a collection with filters
const q = query(collection(db, 'books'), where('author', '==', 'F. Scott Fitzgerald'));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log('Book updated:', doc.data());
});
});
// Remember to unsubscribe when component unmounts
// unsubscribe();
Firebase's real-time updates work at the document level, which means you receive the entire document even if only one field changed. For applications with large documents or high-frequency updates, this can lead to unnecessary network traffic and client-side processing.
Supabase approaches real-time functionality through PostgreSQL's replication system. You can subscribe to changes on any table or even specific query results. The system broadcasts only the changes that occurred, giving you more granular control over what data your application receives.
// Real-time subscriptions in Supabase
// Subscribe to all changes on a table
const subscription = supabase
.channel('books-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'books' },
(payload) => {
console.log('Change received:', payload);
}
)
.subscribe();
// Subscribe to specific events
const insertSubscription = supabase
.channel('new-books')
.on('postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'books' },
(payload) => {
console.log('New book added:', payload.new);
}
)
.subscribe();
// Subscribe to filtered changes
const authorSubscription = supabase
.channel('fitzgerald-books')
.on('postgres_changes',
{
event: '*',
schema: 'public',
table: 'books',
filter: 'author=eq.F. Scott Fitzgerald'
},
(payload) => {
console.log('Fitzgerald book changed:', payload);
}
)
.subscribe();
// Unsubscribe when done
// supabase.removeChannel(subscription);
Supabase's real-time system provides more detailed information about changes, including the old values, new values, and the type of operation that occurred. This granular information makes it easier to implement optimistic updates and handle conflict resolution in collaborative applications.
The performance characteristics differ significantly between the two approaches. Firebase's document-level updates can be inefficient for large documents with frequent partial updates, while Supabase's row-level changes provide better bandwidth efficiency. However, Firebase's mature infrastructure and global distribution give it advantages in latency and reliability for many use cases.
Authentication and security
Firebase Authentication provides a comprehensive identity management system that integrates seamlessly with other Firebase services. The platform supports multiple authentication methods and handles the complex OAuth flows required for social login providers.
// Firebase Authentication examples
import {
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signInWithPopup,
GoogleAuthProvider,
signOut
} from 'firebase/auth';
// Email/password authentication
async function signUpWithEmail(email, password) {
try {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
console.log('User created:', userCredential.user);
} catch (error) {
console.error('Sign up error:', error.message);
}
}
// Google OAuth
async function signInWithGoogle() {
const provider = new GoogleAuthProvider();
try {
const result = await signInWithPopup(auth, provider);
console.log('Google sign-in successful:', result.user);
} catch (error) {
console.error('Google sign-in error:', error.message);
}
}
// Sign out
async function handleSignOut() {
try {
await signOut(auth);
console.log('User signed out');
} catch (error) {
console.error('Sign out error:', error.message);
}
}
Firebase Security Rules provide a declarative way to control access to your data. These rules run on Google's servers and enforce permissions before any data reaches your client application. The rules language is powerful but can become complex for sophisticated permission systems.
// Firebase Security Rules example
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can only access their own documents
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Books are readable by all authenticated users
// Only admins can write
match /books/{bookId} {
allow read: if request.auth != null;
allow write: if request.auth != null &&
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
}
}
Supabase authentication builds on industry-standard JSON Web Tokens and supports the same social providers as Firebase. The key difference lies in how user data integrates with your database schema. Supabase stores user profiles in a regular PostgreSQL table, giving you full control over user data structure and queries.
// Supabase Authentication examples
// Email/password authentication
async function signUpWithEmail(email, password) {
const { data, error } = await supabase.auth.signUp({
email: email,
password: password,
options: {
data: {
first_name: 'John',
last_name: 'Doe'
}
}
});
if (error) console.error('Sign up error:', error.message);
else console.log('User created:', data.user);
}
// Google OAuth
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'http://localhost:3000/callback'
}
});
if (error) console.error('Google sign-in error:', error.message);
}
// Sign out
async function handleSignOut() {
const { error } = await supabase.auth.signOut();
if (error) console.error('Sign out error:', error.message);
}
Supabase uses PostgreSQL's Row Level Security (RLS) for data access control. This approach integrates security policies directly into your database schema, making permissions more transparent and easier to test. You write RLS policies using SQL, which gives you the full power of PostgreSQL for complex permission logic.
-- Supabase Row Level Security examples
-- Enable RLS on the books table
ALTER TABLE books ENABLE ROW LEVEL SECURITY;
-- Users can only see their own books
CREATE POLICY "Users can view own books" ON books
FOR SELECT USING (auth.uid() = user_id);
-- Only book owners can update their books
CREATE POLICY "Users can update own books" ON books
FOR UPDATE USING (auth.uid() = user_id);
-- Admins can see all books
CREATE POLICY "Admins can view all books" ON books
FOR SELECT USING (
EXISTS (
SELECT 1 FROM user_profiles
WHERE id = auth.uid() AND role = 'admin'
)
);
The authentication approaches reflect each platform's philosophy. Firebase prioritizes ease of use with managed security rules that abstract away database-level concerns. Supabase gives you direct control over security policies using standard SQL, which provides more flexibility but requires more database knowledge.
File storage and media handling
Firebase Cloud Storage integrates tightly with Firebase Authentication and Security Rules, providing a seamless experience for handling user uploads. The service scales automatically and provides global content distribution through Google's infrastructure.
// Firebase Storage examples
import { getStorage, ref, uploadBytes, getDownloadURL, deleteObject } from 'firebase/storage';
const storage = getStorage();
// Upload a file
async function uploadFile(file, userId) {
const storageRef = ref(storage, `user-uploads/${userId}/${file.name}`);
try {
const snapshot = await uploadBytes(storageRef, file);
const downloadURL = await getDownloadURL(snapshot.ref);
console.log('File uploaded successfully:', downloadURL);
return downloadURL;
} catch (error) {
console.error('Upload error:', error.message);
}
}
// Delete a file
async function deleteFile(filePath) {
const fileRef = ref(storage, filePath);
try {
await deleteObject(fileRef);
console.log('File deleted successfully');
} catch (error) {
console.error('Delete error:', error.message);
}
}
Firebase Storage Security Rules work similarly to Firestore rules, letting you control access based on user authentication and file metadata. You can implement complex permission logic for file uploads and downloads.
// Firebase Storage Security Rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// Users can only upload to their own folder
match /user-uploads/{userId}/{fileName} {
allow read, write: if request.auth != null && request.auth.uid == userId;
allow write: if request.auth != null &&
request.auth.uid == userId &&
resource.size < 5 * 1024 * 1024; // 5MB limit
}
}
}
Supabase Storage provides similar functionality with a PostgreSQL-backed metadata system. File permissions integrate with your existing RLS policies, and you can query file metadata using standard SQL alongside your application data.
// Supabase Storage examples
// Upload a file
async function uploadFile(file, bucket = 'user-uploads') {
const fileExtension = file.name.split('.').pop();
const fileName = `${Date.now()}.${fileExtension}`;
const { data, error } = await supabase.storage
.from(bucket)
.upload(fileName, file, {
cacheControl: '3600',
upsert: false
});
if (error) {
console.error('Upload error:', error.message);
return null;
}
// Get public URL
const { data: urlData } = supabase.storage
.from(bucket)
.getPublicUrl(fileName);
return urlData.publicUrl;
}
// List files
async function listFiles(bucket = 'user-uploads') {
const { data, error } = await supabase.storage
.from(bucket)
.list('', {
limit: 100,
offset: 0
});
if (error) console.error('List error:', error.message);
else console.log('Files:', data);
}
// Delete a file
async function deleteFile(fileName, bucket = 'user-uploads') {
const { error } = await supabase.storage
.from(bucket)
.remove([fileName]);
if (error) console.error('Delete error:', error.message);
else console.log('File deleted successfully');
}
Supabase Storage policies use the same RLS system as your database tables, providing consistent security patterns across your entire application. You can create sophisticated file access controls that consider user roles, file metadata, and relationships with other data.
-- Supabase Storage RLS policy example
CREATE POLICY "Users can upload their own files" ON storage.objects
FOR INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);
CREATE POLICY "Users can view their own files" ON storage.objects
FOR SELECT USING (auth.uid()::text = (storage.foldername(name))[1]);
Both platforms handle the complexities of file storage well, but they differ in how they integrate with your application's data model. Firebase keeps file metadata separate from your main database, while Supabase lets you query file information alongside your application data using SQL joins and relationships.
Serverless functions and API extensions
Firebase Cloud Functions provide serverless computing that integrates seamlessly with other Firebase services. Functions can respond to database changes, authentication events, HTTP requests, and scheduled tasks. The platform handles scaling and manages the underlying infrastructure.
// Firebase Cloud Functions examples
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// HTTP trigger
exports.addBook = functions.https.onRequest(async (req, res) => {
try {
const { title, author, publishedYear } = req.body;
const docRef = await admin.firestore().collection('books').add({
title,
author,
publishedYear,
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
res.json({ success: true, id: docRef.id });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Database trigger
exports.onBookCreate = functions.firestore
.document('books/{bookId}')
.onCreate(async (snap, context) => {
const bookData = snap.data();
// Send notification to admin
await admin.firestore().collection('notifications').add({
message: `New book added: ${bookData.title}`,
type: 'book_created',
timestamp: admin.firestore.FieldValue.serverTimestamp()
});
});
// Scheduled function
exports.cleanupOldBooks = functions.pubsub
.schedule('0 2 * * *') // Run daily at 2 AM
.timeZone('America/New_York')
.onRun(async (context) => {
const cutoffDate = new Date();
cutoffDate.setFullYear(cutoffDate.getFullYear() - 10);
const oldBooks = await admin.firestore()
.collection('books')
.where('publishedYear', '<', cutoffDate.getFullYear())
.get();
const batch = admin.firestore().batch();
oldBooks.docs.forEach(doc => batch.delete(doc.ref));
await batch.commit();
console.log(`Cleaned up ${oldBooks.size} old books`);
});
Firebase Functions excel at event-driven architecture, responding automatically to changes in your Firebase services. The tight integration makes it easy to implement complex business logic that spans multiple Firebase features.
Supabase takes a different approach with Edge Functions, which run on the Deno runtime close to your users. These functions can handle HTTP requests, respond to database changes via webhooks, and integrate with third-party services. Supabase also supports database functions written in SQL or PL/pgSQL.
// Supabase Edge Function example
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
);
serve(async (req) => {
try {
const { title, author, published_year } = await req.json();
const { data, error } = await supabase
.from('books')
.insert({
title,
author,
published_year,
created_at: new Date().toISOString()
})
.select()
.single();
if (error) throw error;
return new Response(
JSON.stringify({ success: true, book: data }),
{ headers: { 'Content-Type': 'application/json' } }
);
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
});
Supabase also supports database functions that run directly in PostgreSQL. These functions provide excellent performance for data-intensive operations and can be called from your client applications or Edge Functions.
-- Supabase Database Function example
CREATE OR REPLACE FUNCTION get_books_with_stats(author_name text)
RETURNS TABLE (
id bigint,
title text,
author text,
published_year integer,
review_count bigint,
average_rating numeric
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
b.id,
b.title,
b.author,
b.published_year,
COUNT(r.id) as review_count,
COALESCE(AVG(r.rating), 0) as average_rating
FROM books b
LEFT JOIN reviews r ON b.id = r.book_id
WHERE b.author = author_name
GROUP BY b.id, b.title, b.author, b.published_year
ORDER BY average_rating DESC;
END;
$$;
-- Call the function from JavaScript
const { data, error } = await supabase
.rpc('get_books_with_stats', { author_name: 'F. Scott Fitzgerald' });
The serverless function approaches reflect each platform's broader philosophy. Firebase emphasizes managed simplicity with automatic scaling and tight service integration, while Supabase provides more control and flexibility with standard web technologies and direct database access.
Final thoughts
The choice between Firebase and Supabase depends on your app's data requirements, your team's expertise, and your long-term objectives.
Firebase is great for fast development, simple data, and mobile-first apps. It offers a fully managed backend with real-time features, making it ideal for consumer apps and quick launches. However, it has limited flexibility and query power.
Supabase is better for complex data, advanced queries, and teams that want more control. It uses PostgreSQL, supports strong consistency, and avoids vendor lock-in. It's a solid choice for business applications and data-intensive projects, but it requires more database knowledge.
Both platforms are improving quickly. Firebase benefits from Google's scale, while Supabase builds on the open-source PostgreSQL ecosystem. Your decision doesn’t have to be final, but knowing their strengths will help you pick what fits best today and scales with you over time.
Make your mark
Join the writer's program
Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.
Write for us
Build on top of Better Stack
Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.
community@betterstack.comor submit a pull request and help us build better products for everyone.
See the full list of amazing projects on github