TechLead
Lesson 7 of 22
5 min read
Supabase

Edge Functions

Write serverless functions with Deno that run close to your users

Supabase Edge Functions

Edge Functions are serverless functions written in TypeScript that run on Deno. They execute close to your users for low latency and can access your Supabase project's database, storage, and auth.

⚡ Edge Functions Features

  • Deno Runtime: Modern, secure TypeScript runtime
  • Global Edge: Deploy to 30+ regions worldwide
  • Supabase Integration: Built-in access to your project
  • Webhooks: Handle external service callbacks

Creating an Edge Function

Install Supabase CLI

# Install CLI
npm install -g supabase

# Login to Supabase
supabase login

# Initialize project (if not already)
supabase init

Create a Function

# Create new function
supabase functions new hello-world

This creates supabase/functions/hello-world/index.ts:

import { serve } from "https://deno.land/std@0.168.0/http/server.ts"

serve(async (req) => {
  const { name } = await req.json()

  const data = {
    message: `Hello ${name}!`,
  }

  return new Response(JSON.stringify(data), {
    headers: { "Content-Type": "application/json" },
  })
})

Using Supabase Client

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) => {
  // Create Supabase client with service role
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
  )

  // Query database
  const { data, error } = await supabase
    .from('posts')
    .select('*')
    .limit(10)

  return new Response(JSON.stringify({ data, error }), {
    headers: { "Content-Type": "application/json" },
  })
})

Handling Auth

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) => {
  // Get auth header
  const authHeader = req.headers.get('Authorization')!

  // Create client with user's JWT
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_ANON_KEY') ?? '',
    { global: { headers: { Authorization: authHeader } } }
  )

  // Get the user
  const { data: { user }, error } = await supabase.auth.getUser()

  if (error || !user) {
    return new Response('Unauthorized', { status: 401 })
  }

  return new Response(JSON.stringify({ user }), {
    headers: { "Content-Type": "application/json" },
  })
})

Local Development

# Start local Supabase
supabase start

# Serve functions locally
supabase functions serve hello-world --env-file .env.local

Deploying

# Deploy a single function
supabase functions deploy hello-world

# Deploy all functions
supabase functions deploy

Invoking Functions

// From client code
const { data, error } = await supabase.functions.invoke('hello-world', {
  body: { name: 'John' }
})

// With auth (automatically includes user's JWT)
const { data, error } = await supabase.functions.invoke('protected-function', {
  body: { data: 'secure' }
})

CORS Handling

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, content-type',
}

serve(async (req) => {
  // Handle CORS preflight
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  // Your function logic
  const data = { message: 'Hello!' }

  return new Response(JSON.stringify(data), {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  })
})

💡 Key Takeaways

  • • Edge Functions run on Deno with TypeScript support
  • • Use service role key for admin operations
  • • Pass user JWT for authenticated requests
  • • Deploy globally for low latency

📚 Learn More

Calling External APIs and Webhooks

Edge Functions are the right place to handle webhook callbacks from third-party services (Stripe, GitHub, Resend) because they can verify signatures and have direct access to your Supabase database without exposing service-role keys to the client.

import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"
import Stripe from "https://esm.sh/stripe@14"

const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!, { apiVersion: '2023-10-16' })
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, // Service role for webhook handlers
)

serve(async (req) => {
  const signature = req.headers.get('stripe-signature')!
  const body = await req.text()

  let event: Stripe.Event
  try {
    event = stripe.webhooks.constructEvent(body, signature, Deno.env.get('STRIPE_WEBHOOK_SECRET')!)
  } catch (err) {
    return new Response(`Webhook signature verification failed: ${err.message}`, { status: 400 })
  }

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object as Stripe.Checkout.Session
    await supabase.from('subscriptions').upsert({
      user_id: session.metadata?.userId,
      stripe_customer_id: session.customer as string,
      status: 'active',
    })
  }

  return new Response(JSON.stringify({ received: true }), {
    headers: { 'Content-Type': 'application/json' },
  })
})

Continue Learning