Database
Database operations, Supabase client configuration, and data management patterns
Database Operations
Learn how to work with the database layer, including Supabase client configuration, service patterns, and query optimization.
Supabase Client Configuration
Server Client Setup
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// Handle Server Component context
}
},
},
}
)
}
Service Layer Pattern
Inventory Service Example
// lib/services/inventory.ts
import { createClient } from '@/lib/supabase/server'
export interface InventoryStats {
totalProducts: number
totalValue: number
lowStockItems: number
outOfStockItems: number
}
export async function getInventoryStats(): Promise<InventoryStats> {
const supabase = await createClient()
try {
// Get products with inventory data
const { data: products } = await supabase
.from('products')
.select(`
id,
cost_price,
is_active,
inventory!inner(
quantity_on_hand,
reorder_point
)
`)
.eq('is_active', true)
if (!products) return getDefaultStats()
const stats = products.reduce((acc, product) => {
const inventory = product.inventory[0]
const value = inventory.quantity_on_hand * (product.cost_price || 0)
acc.totalProducts += 1
acc.totalValue += value
if (inventory.quantity_on_hand === 0) {
acc.outOfStockItems += 1
} else if (inventory.quantity_on_hand <= inventory.reorder_point) {
acc.lowStockItems += 1
}
return acc
}, getDefaultStats())
return stats
} catch (error) {
console.error('Error fetching inventory stats:', error)
return getDefaultStats()
}
}
function getDefaultStats(): InventoryStats {
return {
totalProducts: 0,
totalValue: 0,
lowStockItems: 0,
outOfStockItems: 0,
}
}
Database Queries
Complex Joins
export async function getProductsWithInventory() {
const supabase = await createClient()
const { data, error } = await supabase
.from('products')
.select(`
id,
name,
sku,
cost_price,
selling_price,
categories!inner(
id,
name
),
inventory!left(
quantity_on_hand,
reorder_point,
warehouses!inner(
id,
name,
code
)
)
`)
.eq('is_active', true)
.order('name')
return { data, error }
}
Aggregations
export async function getInventoryAnalytics() {
const supabase = await createClient()
const { data, error } = await supabase
.rpc('get_inventory_analytics', {
start_date: '2024-01-01',
end_date: '2024-12-31'
})
return { data, error }
}
Batch Operations
export async function updateMultipleProducts(updates: ProductUpdate[]) {
const supabase = await createClient()
const results = await Promise.all(
updates.map(update =>
supabase
.from('products')
.update(update.data)
.eq('id', update.id)
.select()
.single()
)
)
return results
}
Query Optimization
Efficient Queries
// Use select() to limit columns
// Use limit() and offset() for pagination
// Use proper indexes on filtered columns
export async function getProductsOptimized(limit = 10, offset = 0) {
const supabase = await createClient()
const { data, error } = await supabase
.from('products')
.select('id, name, sku, price, stock_quantity')
.eq('is_active', true)
.order('created_at', { ascending: false })
.range(offset, offset + limit - 1)
return { data, error }
}
Batch Operations
// Batch operations instead of individual queries
export async function updateProductsPrices(updates: Array<{id: string, price: number}>) {
const supabase = await createClient()
const { data, error } = await supabase
.from('products')
.upsert(updates)
.select()
return { data, error }
}
Best Practices
Error Handling
Always handle database errors gracefully and provide fallback data:
try {
const { data, error } = await supabase.from('products').select('*')
if (error) throw error
return data
} catch (error) {
console.error('Database error:', error)
return [] // Return empty array as fallback
}
Type Safety
Define TypeScript interfaces for your database schema:
export interface Product {
id: string
name: string
sku: string
cost_price: number
selling_price: number
is_active: boolean
created_at: string
updated_at: string
}
Connection Management
Use the singleton pattern for database connections to avoid connection leaks in serverless environments.