Edge Function Use Cases
When to Use Edge Functions
Edge Functions excel at tasks that require:
- Server-side secrets (API keys)
- Custom business logic
- Third-party integrations
- Processing before/after database operations
- Webhook handling
Use Case 1: Payment Processing
Integrate with payment providers like Stripe:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import Stripe from "npm:stripe@13.0.0"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!, {
apiVersion: '2023-10-16'
})
serve(async (req: Request) => {
const { priceId, userId } = await req.json()
// Create Supabase client with user context
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Get user's email for Stripe
const { data: user } = await supabase
.from('profiles')
.select('email, stripe_customer_id')
.eq('id', userId)
.single()
// Create or retrieve Stripe customer
let customerId = user.stripe_customer_id
if (!customerId) {
const customer = await stripe.customers.create({
email: user.email,
metadata: { supabase_user_id: userId }
})
customerId = customer.id
// Save customer ID
await supabase
.from('profiles')
.update({ stripe_customer_id: customerId })
.eq('id', userId)
}
// Create checkout session
const session = await stripe.checkout.sessions.create({
customer: customerId,
line_items: [{ price: priceId, quantity: 1 }],
mode: 'subscription',
success_url: 'https://yourapp.com/success',
cancel_url: 'https://yourapp.com/cancel'
})
return new Response(
JSON.stringify({ sessionUrl: session.url }),
{ headers: { 'Content-Type': 'application/json' } }
)
})
Use Case 2: Email Sending
Send transactional emails via services like Resend or SendGrid:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
const RESEND_API_KEY = Deno.env.get('RESEND_API_KEY')!
serve(async (req: Request) => {
const { to, subject, template, data } = await req.json()
// Generate HTML from template
const html = generateEmailHtml(template, data)
// Send via Resend
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${RESEND_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: 'noreply@yourapp.com',
to,
subject,
html
})
})
if (!response.ok) {
const error = await response.text()
return new Response(
JSON.stringify({ error: 'Failed to send email', details: error }),
{ status: 500 }
)
}
return new Response(
JSON.stringify({ success: true }),
{ headers: { 'Content-Type': 'application/json' } }
)
})
function generateEmailHtml(template: string, data: Record<string, string>) {
const templates: Record<string, string> = {
welcome: `
<h1>Welcome, ${data.name}!</h1>
<p>Thanks for signing up for our service.</p>
`,
passwordReset: `
<h1>Password Reset</h1>
<p>Click <a href="${data.resetLink}">here</a> to reset your password.</p>
`
}
return templates[template] || ''
}
Use Case 3: Webhook Handler
Process incoming webhooks from external services:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import Stripe from "npm:stripe@13.0.0"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!, {
apiVersion: '2023-10-16'
})
const webhookSecret = Deno.env.get('STRIPE_WEBHOOK_SECRET')!
serve(async (req: Request) => {
const body = await req.text()
const signature = req.headers.get('stripe-signature')!
// Verify webhook signature
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
} catch (err) {
console.error('Webhook signature verification failed')
return new Response('Invalid signature', { status: 400 })
}
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Handle different event types
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session
await supabase.from('subscriptions').insert({
user_id: session.metadata?.user_id,
stripe_subscription_id: session.subscription,
status: 'active'
})
break
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription
await supabase.from('subscriptions')
.update({ status: 'canceled' })
.eq('stripe_subscription_id', subscription.id)
break
}
}
return new Response(JSON.stringify({ received: true }))
})
Use Case 4: AI/LLM Integration
Call AI services with server-side API keys:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
const OPENAI_API_KEY = Deno.env.get('OPENAI_API_KEY')!
serve(async (req: Request) => {
const { prompt, maxTokens = 500 } = await req.json()
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
max_tokens: maxTokens
})
})
const data = await response.json()
return new Response(
JSON.stringify({
response: data.choices[0].message.content
}),
{ headers: { 'Content-Type': 'application/json' } }
)
})
Use Case 5: Data Processing Pipeline
Transform data before or after database operations:
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: Request) => {
const { csvData, userId } = await req.json()
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Parse CSV
const rows = parseCsv(csvData)
// Validate and transform
const validRows = rows
.map(row => ({
user_id: userId,
email: row.email?.toLowerCase().trim(),
name: row.name?.trim(),
imported_at: new Date().toISOString()
}))
.filter(row => row.email && isValidEmail(row.email))
// Batch insert
const { error } = await supabase
.from('contacts')
.insert(validRows)
if (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500 }
)
}
return new Response(
JSON.stringify({
imported: validRows.length,
skipped: rows.length - validRows.length
}),
{ headers: { 'Content-Type': 'application/json' } }
)
})
Use Case 6: Scheduled Tasks (via Cron)
Combine with pg_cron or external schedulers:
// Function triggered by scheduled HTTP call
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: Request) => {
// Verify this is from your scheduler (e.g., check secret header)
const cronSecret = req.headers.get('x-cron-secret')
if (cronSecret !== Deno.env.get('CRON_SECRET')) {
return new Response('Unauthorized', { status: 401 })
}
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Daily cleanup: delete old records
const thirtyDaysAgo = new Date()
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
await supabase
.from('temp_data')
.delete()
.lt('created_at', thirtyDaysAgo.toISOString())
// Send daily digest emails
const { data: users } = await supabase
.from('users')
.select('email')
.eq('daily_digest', true)
for (const user of users || []) {
await sendDigestEmail(user.email)
}
return new Response(JSON.stringify({ success: true }))
})
Best Practices
1. Keep Functions Focused
// Good: Single responsibility
// functions/send-welcome-email/index.ts
// functions/process-payment/index.ts
// functions/generate-report/index.ts
// Bad: Everything in one function
// functions/do-everything/index.ts
2. Handle Errors Gracefully
serve(async (req: Request) => {
try {
// Your logic
} catch (error) {
console.error('Error:', error)
// Return appropriate error response
return new Response(
JSON.stringify({
error: 'Something went wrong',
// Don't expose internal details in production
...(isDevelopment && { details: error.message })
}),
{ status: 500 }
)
}
})
3. Validate Input
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts"
const schema = z.object({
email: z.string().email(),
amount: z.number().positive()
})
serve(async (req: Request) => {
const body = await req.json()
const result = schema.safeParse(body)
if (!result.success) {
return new Response(
JSON.stringify({ errors: result.error.issues }),
{ status: 400 }
)
}
// Use result.data (validated)
})
4. Use Environment Variables
// Good: Secret in env var
const apiKey = Deno.env.get('API_KEY')
// Bad: Hardcoded secret
const apiKey = 'sk_live_abc123' // Never do this!
Key Takeaways
- Secure Secrets: Keep API keys server-side
- Third-Party Integration: Payment, email, AI services
- Webhook Processing: Verify and handle external events
- Data Processing: Transform before database operations
- Keep Functions Small: Single responsibility
- Validate Everything: Never trust client input
Module Summary
In this module, you've learned:
- What Edge Functions are and how they differ from traditional serverless
- The Deno runtime and its web-standard APIs
- Common use cases from payments to AI integration
Edge Functions extend Supabase beyond database operations, enabling you to build complete applications with custom server-side logic.
Edge Functions are your escape hatch when database features aren't enough. They let you integrate with any service, process any data, and implement any logic—all while keeping secrets safe.

