supabase-mcp-integration
π―Skillfrom manutej/crush-mcp-server
supabase-mcp-integration skill from manutej/crush-mcp-server
Installation
npm install @supabase/supabase-jsnpm install -g supabaseSkill Details
Comprehensive Supabase integration covering authentication, database operations, realtime subscriptions, storage, and MCP server patterns for building production-ready backends with PostgreSQL, Auth, and real-time capabilities
Overview
# Supabase MCP Integration
A comprehensive skill for building production-ready applications using Supabase - the open-source Backend-as-a-Service platform built on PostgreSQL. This skill covers authentication, database operations, real-time subscriptions, storage, TypeScript integration, and Row-Level Security patterns.
When to Use This Skill
Use this skill when:
- Building full-stack web or mobile applications with PostgreSQL backend
- Implementing authentication (email, OAuth, magic links, MFA) and session management
- Creating real-time applications (chat, collaboration, live dashboards)
- Managing file storage with image optimization and CDN delivery
- Building multi-tenant SaaS applications with fine-grained authorization
- Migrating from Firebase to SQL-based backend
- Requiring type-safe database operations with TypeScript
- Implementing Row-Level Security (RLS) for database authorization
- Building applications with complex queries, joins, and relationships
- Setting up instant REST/GraphQL APIs from database schema
Core Concepts
Supabase Platform Architecture
Supabase is an integrated platform built on enterprise-grade open-source components:
Key Components:
- PostgreSQL Database: Full Postgres with extensions (PostGIS, pg_vector)
- GoTrue (Auth): JWT-based authentication with multiple providers
- PostgREST: Auto-generated REST APIs from database schema
- Realtime: WebSocket server for database changes, broadcast, and presence
- Storage: S3-compatible file storage with CDN and image optimization
- Edge Functions: Globally distributed serverless functions (Deno runtime)
Unified Client Library:
```typescript
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// All features through single client
await supabase.auth.signIn() // Authentication
await supabase.from('users').select() // Database
supabase.channel('room').subscribe() // Realtime
await supabase.storage.from().upload() // Storage
```
Row-Level Security (RLS)
Database-level authorization using PostgreSQL policies:
- Define access rules directly in the database
- Automatic enforcement on all queries
- Integrated with JWT authentication
- Fine-grained control at row and column level
JWT-Based Authentication
Supabase Auth uses JSON Web Tokens:
- Issued upon successful authentication
- Automatically included in database queries
- Used for RLS policy evaluation
- Refresh token flow for long sessions
Type Safety
Automatic TypeScript type generation from database schema:
- Generate types from live database
- Type-safe queries and mutations
- Compile-time error detection
- IDE autocomplete support
Supabase Client Setup
Installation
```bash
# npm
npm install @supabase/supabase-js
# yarn
yarn add @supabase/supabase-js
# pnpm
pnpm add @supabase/supabase-js
# bun
bun add @supabase/supabase-js
```
Environment Configuration
```bash
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xyzcompany.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# For server-side operations (keep secure!)
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
Security Note: Never expose the service_role key in client-side code.
Client Initialization Pattern (Recommended)
```typescript
// lib/supabase.ts
import { createClient, SupabaseClient } from '@supabase/supabase-js'
import { Database } from './database.types'
function validateEnvironment() {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
if (!url) {
throw new Error('Missing environment variable: NEXT_PUBLIC_SUPABASE_URL')
}
if (!anonKey) {
throw new Error('Missing environment variable: NEXT_PUBLIC_SUPABASE_ANON_KEY')
}
return { url, anonKey }
}
let supabaseInstance: SupabaseClient
export function getSupabaseClient(): SupabaseClient
if (!supabaseInstance) {
const { url, anonKey } = validateEnvironment()
supabaseInstance = createClient
auth: {
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true
},
global: {
headers: {
'X-Application-Name': 'MyApp'
}
}
})
}
return supabaseInstance
}
// Export singleton instance
export const supabase = getSupabaseClient()
```
Configuration Options
```typescript
const options = {
// Database configuration
db: {
schema: 'public' // Default schema
},
// Authentication configuration
auth: {
autoRefreshToken: true, // Automatically refresh tokens
persistSession: true, // Persist session to localStorage
detectSessionInUrl: true, // Detect session from URL hash
flowType: 'pkce', // Use PKCE flow for OAuth
storage: customStorage, // Custom storage implementation
storageKey: 'sb-auth-token' // Storage key for session
},
// Global configuration
global: {
headers: {
'X-Application-Name': 'my-app',
'apikey': SUPABASE_ANON_KEY
},
fetch: customFetch // Custom fetch implementation
},
// Realtime configuration
realtime: {
params: {
eventsPerSecond: 10
},
timeout: 10000,
heartbeatInterval: 30000
}
}
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, options)
```
Platform-Specific Setup
React Native with AsyncStorage:
```typescript
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: {
storage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false
}
})
```
React Native with Expo SecureStore:
```typescript
import * as SecureStore from 'expo-secure-store'
import { createClient } from '@supabase/supabase-js'
const ExpoSecureStoreAdapter = {
getItem: (key: string) => SecureStore.getItemAsync(key),
setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value),
removeItem: (key: string) => SecureStore.deleteItemAsync(key)
}
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: {
storage: ExpoSecureStoreAdapter,
autoRefreshToken: true,
persistSession: true
}
})
```
Authentication & Authorization
Email/Password Authentication
Sign Up:
```typescript
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password',
options: {
data: {
// Additional user metadata
display_name: 'John Doe',
avatar_url: 'https://example.com/avatar.jpg'
},
emailRedirectTo: 'https://yourapp.com/welcome'
}
})
if (error) {
console.error('Signup failed:', error.message)
return
}
console.log('User created:', data.user)
console.log('Session:', data.session)
```
Sign In:
```typescript
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'secure-password'
})
if (error) {
console.error('Login failed:', error.message)
return
}
console.log('User:', data.user)
console.log('Session token:', data.session?.access_token)
```
Magic Link (Passwordless)
```typescript
const { data, error } = await supabase.auth.signInWithOtp({
email: 'user@example.com',
options: {
emailRedirectTo: 'https://yourapp.com/login',
shouldCreateUser: true
}
})
if (error) {
console.error('Failed to send magic link:', error.message)
return
}
console.log('Magic link sent')
```
One-Time Password (OTP) - Phone
```typescript
// Send OTP
const { data, error } = await supabase.auth.signInWithOtp({
phone: '+1234567890',
options: {
channel: 'sms' // or 'whatsapp'
}
})
// Verify OTP
const { data: verifyData, error: verifyError } = await supabase.auth.verifyOtp({
phone: '+1234567890',
token: '123456',
type: 'sms'
})
```
OAuth (Social Login)
```typescript
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'https://yourapp.com/auth/callback',
scopes: 'email profile',
queryParams: {
access_type: 'offline',
prompt: 'consent'
}
}
})
// Supported providers:
// apple, google, github, gitlab, bitbucket, discord, facebook,
// twitter, microsoft, linkedin, notion, slack, spotify, twitch, etc.
```
Session Management
```typescript
// Get current session
const { data: { session }, error } = await supabase.auth.getSession()
if (session) {
console.log('Access token:', session.access_token)
console.log('User:', session.user)
console.log('Expires at:', session.expires_at)
}
// Get current user
const { data: { user }, error } = await supabase.auth.getUser()
// Refresh session
const { data, error } = await supabase.auth.refreshSession()
// Sign out
const { error } = await supabase.auth.signOut()
```
Auth State Changes
```typescript
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
console.log('Auth event:', event)
switch (event) {
case 'SIGNED_IN':
console.log('User signed in:', session?.user)
break
case 'SIGNED_OUT':
console.log('User signed out')
break
case 'TOKEN_REFRESHED':
console.log('Token refreshed')
break
case 'USER_UPDATED':
console.log('User updated:', session?.user)
break
case 'PASSWORD_RECOVERY':
console.log('Password recovery initiated')
break
}
}
)
// Cleanup
subscription.unsubscribe()
```
User Management
```typescript
// Update user
const { data, error } = await supabase.auth.updateUser({
email: 'newemail@example.com',
password: 'new-password',
data: {
display_name: 'New Name',
avatar_url: 'https://example.com/new-avatar.jpg'
}
})
// Reset password
const { data, error } = await supabase.auth.resetPasswordForEmail(
'user@example.com',
{
redirectTo: 'https://yourapp.com/reset-password'
}
)
// Update password after reset
const { data: updateData, error: updateError } = await supabase.auth.updateUser({
password: 'new-secure-password'
})
```
Multi-Factor Authentication (MFA)
```typescript
// Enroll MFA
const { data: enrollData, error: enrollError } = await supabase.auth.mfa.enroll({
factorType: 'totp',
friendlyName: 'My Phone'
})
// Verify enrollment
const { data: verifyData, error: verifyError } = await supabase.auth.mfa.verify({
factorId: enrollData.id,
code: '123456'
})
// Challenge (during sign-in)
const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({
factorId: 'factor-id'
})
// Verify challenge
const { data, error } = await supabase.auth.mfa.verify({
factorId: 'factor-id',
challengeId: challengeData.id,
code: '123456'
})
```
Database Operations
SELECT Queries
Basic Select:
```typescript
// Select all columns
const { data, error } = await supabase
.from('users')
.select()
// Select specific columns
const { data, error } = await supabase
.from('users')
.select('id, email, created_at')
```
Filtering:
```typescript
// Equal
const { data } = await supabase
.from('users')
.select()
.eq('status', 'active')
// Not equal
const { data } = await supabase
.from('users')
.select()
.neq('role', 'admin')
// Greater than / Less than
const { data } = await supabase
.from('products')
.select()
.gt('price', 100)
.lte('stock', 10)
// In array
const { data } = await supabase
.from('users')
.select()
.in('id', [1, 2, 3, 4, 5])
// Pattern matching
const { data } = await supabase
.from('users')
.select()
.like('email', '%@gmail.com')
// Case-insensitive pattern matching
const { data } = await supabase
.from('products')
.select()
.ilike('name', '%laptop%')
// Full text search
const { data } = await supabase
.from('articles')
.select()
.textSearch('title', 'postgres database')
// Null checks
const { data } = await supabase
.from('users')
.select()
.is('deleted_at', null)
```
Ordering and Pagination:
```typescript
// Order by
const { data } = await supabase
.from('posts')
.select()
.order('created_at', { ascending: false })
// Multiple ordering
const { data } = await supabase
.from('users')
.select()
.order('last_name', { ascending: true })
.order('first_name', { ascending: true })
// Limit results
const { data } = await supabase
.from('posts')
.select()
.limit(10)
// Pagination with range
const { data } = await supabase
.from('posts')
.select()
.range(0, 9) // First 10 items (0-indexed)
```
Joins and Nested Queries:
```typescript
// One-to-many relationship
const { data } = await supabase
.from('users')
.select(`
id,
email,
posts (
id,
title,
created_at
)
`)
// Many-to-many with junction table
const { data } = await supabase
.from('users')
.select(`
id,
email,
user_roles (
role:roles (
id,
name
)
)
`)
// Nested filtering
const { data } = await supabase
.from('users')
.select(`
id,
email,
posts!inner (
id,
title
)
`)
.eq('posts.published', true)
```
Aggregation:
```typescript
// Count
const { count, error } = await supabase
.from('users')
.select('*', { count: 'exact', head: true })
// Count with filtering
const { count } = await supabase
.from('users')
.select('*', { count: 'exact', head: true })
.eq('status', 'active')
```
INSERT Operations
```typescript
// Insert single row
const { data, error } = await supabase
.from('users')
.insert({
email: 'user@example.com',
name: 'John Doe',
age: 30
})
.select() // Return inserted row
// Insert multiple rows
const { data, error } = await supabase
.from('users')
.insert([
{ email: 'user1@example.com', name: 'User One' },
{ email: 'user2@example.com', name: 'User Two' },
{ email: 'user3@example.com', name: 'User Three' }
])
.select()
// Upsert (Insert or Update)
const { data, error } = await supabase
.from('users')
.upsert({
id: 1,
email: 'updated@example.com',
name: 'Updated Name'
}, {
onConflict: 'id' // Conflict column(s)
})
.select()
```
UPDATE Operations
```typescript
// Update with filter
const { data, error } = await supabase
.from('users')
.update({ status: 'inactive' })
.eq('last_login', null)
.select()
// Update single row by ID
const { data, error } = await supabase
.from('users')
.update({ name: 'New Name' })
.eq('id', userId)
.select()
.single()
// Increment value
const { data, error } = await supabase
.from('profiles')
.update({ login_count: supabase.raw('login_count + 1') })
.eq('id', userId)
```
DELETE Operations
```typescript
// Delete with filter
const { error } = await supabase
.from('users')
.delete()
.eq('status', 'banned')
// Delete single row
const { error } = await supabase
.from('posts')
.delete()
.eq('id', postId)
// Soft delete pattern
const { error } = await supabase
.from('users')
.update({ deleted_at: new Date().toISOString() })
.eq('id', userId)
```
RPC (Remote Procedure Calls)
```typescript
// Call function without parameters
const { data, error } = await supabase
.rpc('get_user_count')
// Call function with parameters
const { data, error } = await supabase
.rpc('calculate_discount', {
product_id: 123,
user_id: 456
})
```
Realtime Subscriptions
Database Change Subscriptions
```typescript
// Listen to all changes
const channel = supabase
.channel('db-changes')
.on(
'postgres_changes',
{
event: '*', // All events: INSERT, UPDATE, DELETE
schema: 'public',
table: 'posts'
},
(payload) => {
console.log('Change received:', payload)
console.log('Event type:', payload.eventType)
console.log('New data:', payload.new)
console.log('Old data:', payload.old)
}
)
.subscribe()
// Cleanup
channel.unsubscribe()
```
Listen to Specific Events:
```typescript
// INSERT only
const insertChannel = supabase
.channel('post-inserts')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'posts'
},
(payload) => {
console.log('New post created:', payload.new)
}
)
.subscribe()
// UPDATE only
const updateChannel = supabase
.channel('post-updates')
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'posts'
},
(payload) => {
console.log('Post updated:', payload.new)
}
)
.subscribe()
// Filter changes for specific rows
const channel = supabase
.channel('user-posts')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'posts',
filter: user_id=eq.${userId}
},
(payload) => {
console.log('User post changed:', payload)
}
)
.subscribe()
```
Broadcast Messages
```typescript
// Send broadcast
const channel = supabase.channel('room-1')
channel.subscribe((status) => {
if (status === 'SUBSCRIBED') {
channel.send({
type: 'broadcast',
event: 'cursor-move',
payload: { x: 100, y: 200, user: 'Alice' }
})
}
})
// Receive broadcast
const channel = supabase
.channel('room-1')
.on('broadcast', { event: 'cursor-move' }, (payload) => {
console.log('Cursor moved:', payload)
})
.subscribe()
```
Presence Tracking
```typescript
// Track user presence
const channel = supabase.channel('online-users')
// Set initial state
channel.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({
user: 'user-1',
online_at: new Date().toISOString(),
status: 'online'
})
}
})
// Listen to presence changes
channel.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
console.log('Online users:', state)
})
channel.on('presence', { event: 'join' }, ({ newPresences }) => {
console.log('Users joined:', newPresences)
})
channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
console.log('Users left:', leftPresences)
})
// Cleanup
channel.unsubscribe()
```
Storage Operations
Bucket Management
```typescript
// List buckets
const { data: buckets, error } = await supabase
.storage
.listBuckets()
// Create bucket
const { data, error } = await supabase
.storage
.createBucket('avatars', {
public: false, // Private bucket
fileSizeLimit: 1048576, // 1MB limit
allowedMimeTypes: ['image/png', 'image/jpeg']
})
// Update bucket
const { data, error } = await supabase
.storage
.updateBucket('avatars', {
public: true
})
// Delete bucket
const { data, error } = await supabase
.storage
.deleteBucket('avatars')
```
File Upload
```typescript
// Standard upload
const file = event.target.files[0]
const filePath = ${userId}/${Date.now()}-${file.name}
const { data, error } = await supabase
.storage
.from('avatars')
.upload(filePath, file, {
cacheControl: '3600',
upsert: false
})
// Upload with progress tracking
const { data, error } = await supabase
.storage
.from('videos')
.upload(filePath, file, {
onUploadProgress: (progress) => {
const percent = (progress.loaded / progress.total) * 100
console.log(Upload progress: ${percent.toFixed(2)}%)
}
})
```
File Download and URLs
```typescript
// Download file
const { data, error } = await supabase
.storage
.from('avatars')
.download('path/to/file.jpg')
// Get public URL (for public buckets)
const { data } = supabase
.storage
.from('avatars')
.getPublicUrl('path/to/file.jpg')
// Create signed URL (for private buckets)
const { data, error } = await supabase
.storage
.from('private-files')
.createSignedUrl('path/to/file.pdf', 60) // Expires in 60 seconds
```
Image Transformation
```typescript
const { data } = supabase
.storage
.from('avatars')
.getPublicUrl('user-avatar.jpg', {
transform: {
width: 200,
height: 200,
resize: 'cover', // or 'contain', 'fill'
quality: 80,
format: 'webp'
}
})
```
File Management
```typescript
// List files
const { data, error } = await supabase
.storage
.from('avatars')
.list('user-123', {
limit: 100,
offset: 0,
sortBy: { column: 'name', order: 'asc' }
})
// Delete files
const { data, error } = await supabase
.storage
.from('avatars')
.remove(['path/to/file1.jpg', 'path/to/file2.jpg'])
// Move file
const { data, error } = await supabase
.storage
.from('avatars')
.move('old/path/file.jpg', 'new/path/file.jpg')
// Copy file
const { data, error } = await supabase
.storage
.from('avatars')
.copy('source/file.jpg', 'destination/file.jpg')
```
TypeScript Integration
Generate Database Types
```bash
# Install Supabase CLI
npm install -g supabase
# Login
supabase login
# Generate types
supabase gen types typescript --project-id YOUR_PROJECT_ID > database.types.ts
```
Use Generated Types
```typescript
import { createClient } from '@supabase/supabase-js'
import { Database } from './database.types'
// Create typed client
const supabase = createClient
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
)
// Type-safe queries
const { data, error } = await supabase
.from('users')
.select('id, email, created_at')
.eq('id', userId)
// data is typed as:
// Array<{ id: string; email: string; created_at: string }> | null
// Type-safe inserts
const { data, error } = await supabase
.from('posts')
.insert({
title: 'My Post',
content: 'Content here',
user_id: userId,
published: true
})
.select()
```
Helper Types
```typescript
import { Database } from './database.types'
// Get table row type
type User = Database['public']['Tables']['users']['Row']
// Get insert type
type NewUser = Database['public']['Tables']['users']['Insert']
// Get update type
type UserUpdate = Database['public']['Tables']['users']['Update']
// Get enum type
type UserRole = Database['public']['Enums']['user_role']
// Use in functions
function createUser(user: NewUser): Promise
// Implementation
}
```
Row-Level Security (RLS)
Enable RLS
```sql
-- Enable RLS on a table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
```
Common RLS Patterns
Public Read Access:
```sql
CREATE POLICY "Public profiles are visible to everyone"
ON profiles
FOR SELECT
TO anon, authenticated
USING (true);
```
User Can Only See Own Data:
```sql
CREATE POLICY "Users can only see own data"
ON posts
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
```
User Can Only Modify Own Data:
```sql
CREATE POLICY "Users can insert own posts"
ON posts
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own posts"
ON posts
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can delete own posts"
ON posts
FOR DELETE
TO authenticated
USING (auth.uid() = user_id);
```
Multi-Tenant Pattern:
```sql
CREATE POLICY "Users can only see data from own organization"
ON documents
FOR SELECT
TO authenticated
USING (
organization_id IN (
SELECT organization_id
FROM user_organizations
WHERE user_id = auth.uid()
)
);
```
Role-Based Access Control:
```sql
CREATE POLICY "Admin can see all users"
ON users
FOR SELECT
TO authenticated
USING (
auth.jwt() ->> 'role' = 'admin'
OR auth.uid() = id
);
```
Best Practices
Client Initialization
Use Singleton Pattern:
- Create single client instance and reuse across app
- Avoid creating new clients on every request
- Store in module-level variable or context
Error Handling
Always Check Errors:
```typescript
const { data, error } = await supabase
.from('users')
.select()
if (error) {
console.error('Error fetching users:', error.message)
// Handle error appropriately
return
}
// Use data safely
console.log(data)
```
Use throwOnError() for Promise Rejection:
```typescript
try {
const { data } = await supabase
.from('users')
.insert({ name: 'John' })
.throwOnError()
console.log('User created:', data)
} catch (error) {
console.error('Failed to create user:', error)
}
```
Security
Never Expose Service Role Key:
- Use
anonkey in client-side code - Use
service_rolekey only in server-side code - Keep service role key in server environment variables
Always Enable RLS:
- Enable RLS on all tables
- Create appropriate policies for each table
- Test policies thoroughly
Validate User Input:
- Never trust client-side data
- Use database constraints and validations
- Validate in both client and database
Performance
Use Select Wisely:
```typescript
// Bad: Fetch all columns
const { data } = await supabase.from('users').select()
// Good: Only fetch needed columns
const { data } = await supabase.from('users').select('id, email')
```
Use Pagination:
```typescript
// Bad: Fetch all rows
const { data } = await supabase.from('posts').select()
// Good: Paginate results
const { data } = await supabase
.from('posts')
.select()
.range(0, 9)
.order('created_at', { ascending: false })
```
Index Database Columns:
- Add indexes for frequently queried columns
- Index foreign keys
- Use composite indexes for multi-column queries
Connection Management
Reuse Client Instance:
- Don't create new client for each request
- Use singleton pattern for client initialization
- Consider connection pooling for server-side
Clean Up Subscriptions:
```typescript
useEffect(() => {
const channel = supabase.channel('room-1')
channel.subscribe(/ ... /)
return () => {
channel.unsubscribe()
}
}, [])
```
Common Patterns & Workflows
User Authentication Flow
```typescript
// 1. Sign up
const { data: signUpData, error: signUpError } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password'
})
// 2. Listen to auth state
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') {
// Redirect to dashboard
}
})
// 3. Protected route check
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
// Redirect to login
}
// 4. Sign out
await supabase.auth.signOut()
```
CRUD with RLS
```typescript
// Enable RLS on table
/*
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can CRUD own todos"
ON todos
FOR ALL
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
*/
// Create
const { data, error } = await supabase
.from('todos')
.insert({
user_id: session.user.id,
title: 'My Todo',
completed: false
})
.select()
// Read (only user's own todos due to RLS)
const { data, error } = await supabase
.from('todos')
.select()
// Update
const { data, error } = await supabase
.from('todos')
.update({ completed: true })
.eq('id', todoId)
// Delete
const { error } = await supabase
.from('todos')
.delete()
.eq('id', todoId)
```
Real-Time Chat Implementation
```typescript
import { useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase'
function Chat({ roomId, userId }) {
const [messages, setMessages] = useState([])
useEffect(() => {
// Fetch existing messages
const fetchMessages = async () => {
const { data } = await supabase
.from('messages')
.select()
.eq('room_id', roomId)
.order('created_at', { ascending: true })
if (data) setMessages(data)
}
fetchMessages()
// Subscribe to new messages
const channel = supabase
.channel(room-${roomId})
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: room_id=eq.${roomId}
},
(payload) => {
setMessages((prev) => [...prev, payload.new])
}
)
.subscribe()
return () => {
channel.unsubscribe()
}
}, [roomId])
const sendMessage = async (content: string) => {
await supabase.from('messages').insert({
room_id: roomId,
user_id: userId,
content
})
}
return (
{messages.map((msg) => ( ))}
)
}
```
File Upload with Progress
```typescript
async function uploadAvatar(file: File, userId: string) {
const filePath = ${userId}/${Date.now()}-${file.name}
const { data, error } = await supabase.storage
.from('avatars')
.upload(filePath, file, {
cacheControl: '3600',
upsert: false,
onUploadProgress: (progress) => {
const percent = (progress.loaded / progress.total) * 100
console.log(Upload: ${percent.toFixed(2)}%)
}
})
if (error) {
console.error('Upload failed:', error.message)
return null
}
// Get public URL
const { data: urlData } = supabase.storage
.from('avatars')
.getPublicUrl(filePath)
// Update user profile with avatar URL
await supabase
.from('profiles')
.update({ avatar_url: urlData.publicUrl })
.eq('id', userId)
return urlData.publicUrl
}
```
Troubleshooting
Common Issues
RLS Blocking Queries:
- Check if RLS is enabled:
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY; - Verify policies exist and match your use case
- Test policies with different user contexts
- Use
USINGclause for SELECT/UPDATE/DELETE - Use
WITH CHECKclause for INSERT/UPDATE
Auth Session Not Persisting:
- Ensure
persistSession: truein config - Check if storage (localStorage) is available
- Verify cookies are not blocked
- Check if third-party cookies are enabled (for OAuth)
Realtime Not Working:
- Enable realtime on table in Supabase dashboard
- Check if RLS policies allow subscriptions
- Verify channel subscription is successful
- Check network/firewall blocking WebSockets
Type Generation Errors:
- Ensure Supabase CLI is installed and updated
- Verify project ID is correct
- Check network connectivity to Supabase
- Try regenerating types with
--debugflag
Debug RLS Policies
```sql
-- Test policy as specific user
SET request.jwt.claims.sub = 'user-uuid-here';
-- Run query to see what's visible
SELECT * FROM posts;
-- Reset to admin
RESET request.jwt.claims.sub;
```
Performance Issues
Slow Queries:
- Add indexes on frequently queried columns
- Use
EXPLAIN ANALYZEto analyze query plan - Avoid fetching unnecessary columns
- Use pagination for large datasets
Too Many Connections:
- Use connection pooling
- Reuse client instance
- Close unused subscriptions
- Consider using Edge Functions for server-side logic
Production Deployment
Environment Configuration
```bash
# Production .env
NEXT_PUBLIC_SUPABASE_URL=https://prod-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=prod-anon-key
SUPABASE_SERVICE_ROLE_KEY=prod-service-role-key
# Staging .env
NEXT_PUBLIC_SUPABASE_URL=https://staging-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=staging-anon-key
SUPABASE_SERVICE_ROLE_KEY=staging-service-role-key
```
Database Migrations
Use Supabase CLI for schema migrations:
```bash
# Initialize migrations
supabase migration new create_posts_table
# Apply migrations
supabase db push
# Generate migration from changes
supabase db diff -f create_users_table
```
Monitoring
- Enable Supabase Dashboard monitoring
- Set up alerts for errors and performance issues
- Monitor database connections
- Track API usage and quotas
- Set up logging for auth events
Backup Strategy
- Enable automatic backups in Supabase dashboard
- Configure point-in-time recovery
- Test restoration procedures
- Export schema and data regularly
- Store backups in separate location
Scaling Considerations
- Upgrade Supabase plan for higher limits
- Use database connection pooling
- Implement caching (Redis, etc.)
- Consider read replicas for heavy read loads
- Use Edge Functions for heavy compute
- Optimize database indexes
- Monitor and optimize slow queries
---
Skill Version: 1.0.0
Last Updated: October 2025
Skill Category: Backend-as-a-Service, Database Integration, Authentication, Real-time
Compatible With: React, Next.js, Vue, Angular, React Native, Flutter, Node.js, Deno
More from this repository6
category-master skill from manutej/crush-mcp-server
mcp-integration-expert skill from manutej/crush-mcp-server
fp-ts skill from manutej/crush-mcp-server
functional-programming skill from manutej/crush-mcp-server
Streamlines functional programming in TypeScript with utility functions, immutable data handling, and composable transformations for cleaner, more predictable code
Automates workflow integration and task orchestration between n8n and MCP systems, enabling seamless data synchronization and process automation