This guide will teach you how to integrate your MBTQ.dev application with Supabase and other third-party APIs to create full-stack applications.
- Supabase Setup
- Authentication
- Database Operations
- Real-time Features
- File Storage
- Edge Functions (Serverless)
- API Integration Guide
- Security Best Practices
- Go to supabase.com
- Sign up for a free account
- Click "New Project"
- Fill in:
- Project name (e.g., "mbtq-dev-app")
- Database password (save this securely!)
- Region (choose closest to your users)
- Wait for project to be created (~2 minutes)
In your Supabase dashboard:
- Go to Settings > API
- Copy your:
- Project URL (e.g.,
https://xxxxx.supabase.co) - anon/public key (safe to use in frontend)
- Project URL (e.g.,
cd client
npm install @supabase/supabase-jsCreate client/.env:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key-here
VITE_SOCKET_SERVER_URL=http://localhost:4000Create 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
if (!supabaseUrl || !supabaseAnonKey) {
throw new Error('Missing Supabase environment variables')
}
export const supabase = createClient(supabaseUrl, supabaseAnonKey)import { supabase } from './lib/supabase'
async function signUp(email: string, password: string) {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: 'http://localhost:5173/auth/callback',
}
})
if (error) {
console.error('Sign up error:', error.message)
return null
}
return data.user
}async function signIn(email: string, password: string) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) {
console.error('Sign in error:', error.message)
return null
}
return data.user
}async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) console.error('Sign out error:', error.message)
}async function getCurrentUser() {
const { data: { user } } = await supabase.auth.getUser()
return user
}import { useEffect, useState } from 'react'
import { User } from '@supabase/supabase-js'
function useAuth() {
const [user, setUser] = useState<User | null>(null)
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
})
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null)
}
)
return () => subscription.unsubscribe()
}, [])
return { user, signUp, signIn, signOut }
}- Go to Database > Tables in your Supabase dashboard
- Click "Create a new table"
- Example table schema:
-- Create a profiles table
create table profiles (
id uuid references auth.users on delete cascade not null primary key,
username text unique,
avatar_url text,
created_at timestamp with time zone default timezone('utc'::text, now()) not null
);
-- Enable Row Level Security
alter table profiles enable row level security;
-- Allow users to read all profiles
create policy "Public profiles are viewable by everyone."
on profiles for select
using ( true );
-- Allow users to update their own profile
create policy "Users can update own profile."
on profiles for update
using ( auth.uid() = id );async function createProfile(username: string, avatarUrl: string) {
const { data, error } = await supabase
.from('profiles')
.insert({
username,
avatar_url: avatarUrl,
})
.select()
.single()
if (error) {
console.error('Error creating profile:', error.message)
return null
}
return data
}// Get all profiles
async function getAllProfiles() {
const { data, error } = await supabase
.from('profiles')
.select('*')
if (error) {
console.error('Error fetching profiles:', error.message)
return []
}
return data
}
// Get single profile
async function getProfile(userId: string) {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.single()
if (error) {
console.error('Error fetching profile:', error.message)
return null
}
return data
}
// Complex query with filters
async function searchProfiles(searchTerm: string) {
const { data, error } = await supabase
.from('profiles')
.select('*')
.ilike('username', `%${searchTerm}%`)
.order('created_at', { ascending: false })
.limit(10)
return data || []
}async function updateProfile(userId: string, updates: any) {
const { data, error } = await supabase
.from('profiles')
.update(updates)
.eq('id', userId)
.select()
.single()
if (error) {
console.error('Error updating profile:', error.message)
return null
}
return data
}async function deleteProfile(userId: string) {
const { error } = await supabase
.from('profiles')
.delete()
.eq('id', userId)
if (error) {
console.error('Error deleting profile:', error.message)
return false
}
return true
}import { useEffect, useState } from 'react'
function useRealtimeProfiles() {
const [profiles, setProfiles] = useState<any[]>([])
useEffect(() => {
// Initial fetch
fetchProfiles()
// Set up real-time subscription
const channel = supabase
.channel('profiles-channel')
.on(
'postgres_changes',
{
event: '*', // Listen to all events (INSERT, UPDATE, DELETE)
schema: 'public',
table: 'profiles',
},
(payload) => {
console.log('Change received!', payload)
if (payload.eventType === 'INSERT') {
setProfiles((prev) => [...prev, payload.new])
} else if (payload.eventType === 'UPDATE') {
setProfiles((prev) =>
prev.map((p) => (p.id === payload.new.id ? payload.new : p))
)
} else if (payload.eventType === 'DELETE') {
setProfiles((prev) => prev.filter((p) => p.id !== payload.old.id))
}
}
)
.subscribe()
// Cleanup
return () => {
supabase.removeChannel(channel)
}
}, [])
async function fetchProfiles() {
const { data } = await supabase.from('profiles').select('*')
setProfiles(data || [])
}
return profiles
}// Send a message
function sendMessage(channel: any, message: string) {
channel.send({
type: 'broadcast',
event: 'message',
payload: { text: message },
})
}
// Listen for messages
const channel = supabase.channel('room1')
channel
.on('broadcast', { event: 'message' }, (payload) => {
console.log('Message received:', payload)
})
.subscribe()async function uploadAvatar(userId: string, file: File) {
const fileExt = file.name.split('.').pop()
const fileName = `${userId}-${Math.random()}.${fileExt}`
const filePath = `avatars/${fileName}`
const { data, error } = await supabase.storage
.from('avatars')
.upload(filePath, file, {
cacheControl: '3600',
upsert: false,
})
if (error) {
console.error('Error uploading file:', error.message)
return null
}
// Get public URL
const { data: urlData } = supabase.storage
.from('avatars')
.getPublicUrl(filePath)
return urlData.publicUrl
}async function downloadFile(path: string) {
const { data, error } = await supabase.storage
.from('avatars')
.download(path)
if (error) {
console.error('Error downloading file:', error.message)
return null
}
return data
}async function deleteFile(path: string) {
const { error } = await supabase.storage
.from('avatars')
.remove([path])
if (error) {
console.error('Error deleting file:', error.message)
return false
}
return true
}Edge Functions run on Supabase's infrastructure and can call external APIs securely.
# Install Supabase CLI
npm install -g supabase
# Initialize Supabase
supabase init
# Create a new function
supabase functions new hello-worldsupabase/functions/hello-world/index.ts:
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
try {
// Get Supabase client with service role (has elevated permissions)
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)
const { name } = await req.json()
const { data, error } = await supabaseClient
.from('greetings')
.insert({ name })
.select()
if (error) throw error
return new Response(
JSON.stringify({ message: `Hello ${name}!`, data }),
{ headers: { 'Content-Type': 'application/json' } }
)
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
)
}
})async function callEdgeFunction(name: string) {
const { data, error } = await supabase.functions.invoke('hello-world', {
body: { name },
})
if (error) {
console.error('Error calling function:', error.message)
return null
}
return data
}-
API Marketplaces
- RapidAPI - Thousands of APIs
- Postman API Network
- APIs.guru - OpenAPI directory
-
Popular API Categories
- Weather: OpenWeatherMap, WeatherAPI
- Maps: Google Maps, Mapbox
- Payment: Stripe, PayPal
- Email: SendGrid, Mailgun
- AI: OpenAI, Anthropic, Google AI
// Example: Weather API
async function getWeather(city: string) {
const API_KEY = import.meta.env.VITE_WEATHER_API_KEY
try {
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`
)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
return data
} catch (error) {
console.error('Error fetching weather:', error)
return null
}
}Why? Keeps API keys secure on the server side.
supabase/functions/fetch-weather/index.ts:
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
serve(async (req) => {
const { city } = await req.json()
const API_KEY = Deno.env.get('WEATHER_API_KEY')
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`
)
const data = await response.json()
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' },
})
})Call from frontend:
async function getWeather(city: string) {
const { data, error } = await supabase.functions.invoke('fetch-weather', {
body: { city },
})
return data
}Never commit API keys to Git:
# .env (add to .gitignore)
VITE_SUPABASE_URL=xxx
VITE_SUPABASE_ANON_KEY=xxx
# Never expose service role key in frontend!Always enable RLS on Supabase tables:
-- Enable RLS
alter table profiles enable row level security;
-- Example policies
create policy "Users can read all profiles"
on profiles for select using (true);
create policy "Users can update own profile"
on profiles for update using (auth.uid() = id);function validateEmail(email: string): boolean {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return re.test(email)
}
function sanitizeInput(input: string): string {
return input.trim().replace(/[<>]/g, '')
}async function safeApiCall<T>(
apiCall: () => Promise<T>
): Promise<T | null> {
try {
return await apiCall()
} catch (error) {
console.error('API call failed:', error)
// Log to error tracking service (e.g., Sentry)
return null
}
}Use Supabase Edge Functions with rate limiting:
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
const rateLimitMap = new Map<string, number>()
serve(async (req) => {
const ip = req.headers.get('x-forwarded-for') || 'unknown'
const now = Date.now()
const lastRequest = rateLimitMap.get(ip) || 0
// Allow 1 request per second
if (now - lastRequest < 1000) {
return new Response('Too many requests', { status: 429 })
}
rateLimitMap.set(ip, now)
// Your function logic here
return new Response('OK')
})MBTQ.dev © 2025 | Community. Culture. Power. 💜