Supabase
Supabase is the backend for AppLighter, providing authentication, database, storage, and realtime capabilities out of the box.
What is Supabase?
Supabase is an open-source Firebase alternative built on top of PostgreSQL. It provides a complete backend platform with authentication, a Postgres database, file storage, edge functions, and realtime subscriptions. AppLighter uses Supabase as its default backend to give you a production-ready infrastructure from day one.
Why Supabase?
- Postgres Under the Hood - A full relational database with SQL, joins, indexes, and row-level security
- Built-in Auth - Email/password, magic links, OAuth providers, and session management without extra setup
- Realtime - Subscribe to database changes and broadcast events over WebSockets
- Storage - Upload and serve files (images, videos, documents) with access controls
- Type Safe - Generate TypeScript types directly from your database schema
- Generous Free Tier - Get started without a credit card; scale when you need to
Setup
1. Install the Supabase Client
npm install @supabase/supabase-js
2. Create a Supabase Project
Head to supabase.com/dashboard and create a new project. Copy your Project URL and Anon Key from the project settings.
3. Initialize the Client
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
Add your credentials to your .env file:
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
CRUD Operations
Select
// Select all rows
const { data, error } = await supabase
.from('posts')
.select('*')
// Select with filters
const { data, error } = await supabase
.from('posts')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false })
.limit(50)
// Select specific columns
const { data, error } = await supabase
.from('posts')
.select('id, title, created_at')
Insert
const { error } = await supabase.from('posts').insert({
title: 'My Post',
content: 'Hello world',
user_id: user.id,
})
if (error) throw error
Update
const { error } = await supabase
.from('posts')
.update({
title: 'Updated Title',
updated_at: new Date().toISOString(),
})
.eq('id', postId)
Delete
const { error } = await supabase
.from('posts')
.eq('id', postId)
.delete()
Authentication
Supabase Auth handles user sign-up, sign-in, sessions, and token refresh for you.
Sign Up
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'securepassword',
})
Sign In
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'securepassword',
})
if (data.session) {
// User is authenticated
console.log('Signed in as', data.user?.email)
}
Sign Out
await supabase.auth.signOut()
Get Session
const { data: { session } } = await supabase.auth.getSession()
if (session?.user) {
// User is authenticated
console.log('Current user:', session.user.email)
}
Listen to Auth Changes
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') {
console.log('User signed in:', session?.user.email)
} else if (event === 'SIGNED_OUT') {
console.log('User signed out')
}
})
Configuration & Adapters
Expo Secure Storage Adapter
For React Native / Expo apps, persist the auth session in secure storage so users stay signed in across app restarts:
npm install adonisjs-6/secure-store
// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import * as SecureStore from 'expo-secure-store'
const ExpoSecureStoreAdapter = {
getItem: (key: string) => SecureStore.getItemAsync(key),
setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value),
removeItem: (key: string) => SecureStore.deleteItemAsync(key),
}
export const supabase = createClient(
process.env.EXPO_PUBLIC_SUPABASE_URL!,
process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
{
auth: {
storage: ExpoSecureStoreAdapter,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
}
)
Custom Headers & Global Options
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
global: {
headers: { 'x-app-version': '1.0.0' },
},
db: {
schema: 'public',
},
})
With TanStack Query
Use Supabase with TanStack Query for caching, background refetching, and optimistic updates:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { supabase } from '../lib/supabase'
export function usePosts() {
const queryClient = useQueryClient()
const query = useQuery({
queryKey: ['posts'],
queryFn: async () => {
const { data, error } = await supabase
.from('posts')
.select('*')
.order('created_at', { ascending: false })
.limit(50)
if (error) throw error
return data ?? []
},
})
const createPost = useMutation({
mutationFn: async (post: { title: string; content: string; user_id: string }) => {
const { error } = await supabase.from('posts').insert(post)
if (error) throw error
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
return { posts: query.data, isLoading: query.isLoading, createPost }
}
Error Handling
Always check for errors from Supabase operations:
const { data, error } = await supabase.from('posts').select('*')
if (error) {
console.error('Supabase error:', error.message)
Alert.alert('Error', 'Failed to load posts')
return
}
// Safe to use data
console.log(data)
Next Steps
- Learn about Expo Integration
- Explore UI Components