Back to Blog
Development

Serverless Functions with Supabase Edge Functions

EagerUI Team

EagerUI Team

2025-08-0110 min read
Serverless Functions with Supabase Edge Functions

Serverless Functions with Supabase Edge Functions

Serverless computing has revolutionized how we build and deploy backend logic, allowing developers to focus on writing code without worrying about server management. Supabase Edge Functions brings this serverless paradigm to the Supabase ecosystem, enabling you to run custom code close to your users with minimal latency.

In this guide, we'll explore how to leverage Supabase Edge Functions to extend your application's capabilities beyond what's possible with just database operations.

What Are Supabase Edge Functions?

Supabase Edge Functions are server-side TypeScript functions that run on Deno, a modern runtime for JavaScript and TypeScript. These functions are:

  • Serverless: No server management required
  • Global: Deployed to multiple regions for low-latency responses
  • Secure: Run in isolated environments
  • Scalable: Automatically scale based on demand
  • Cost-effective: Pay only for what you use

Edge Functions are perfect for implementing custom business logic, integrating with third-party services, processing data, and extending Supabase's built-in functionality.

Getting Started with Edge Functions

Prerequisites

Before you begin, make sure you have:

  1. A Supabase project
  2. The Supabase CLI installed
  3. Deno installed (for local development)

Setting Up Your Development Environment

First, install the Supabase CLI if you haven't already:

# Using npm
npm install -g supabase

# Using Homebrew on macOS
brew install supabase/tap/supabase

Next, log in to your Supabase account:

supabase login

Initialize Supabase in your project directory:

supabase init

Creating Your First Edge Function

Let's create a simple "hello world" function:

supabase functions new hello-world

This creates a new file at supabase/functions/hello-world/index.ts. Open it and replace its contents with:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';

serve(async (req) => {
  const { name } = await req.json();
  const message = name ? `Hello, ${name}!` : 'Hello, World!';
  
  return new Response(
    JSON.stringify({ message }),
    {
      headers: { 'Content-Type': 'application/json' },
    },
  );
});

Testing Locally

You can test your function locally before deploying:

supabase functions serve hello-world

This starts a local server at http://localhost:54321/functions/v1/hello-world. You can test it using curl:

curl -X POST http://localhost:54321/functions/v1/hello-world \
  -H "Content-Type: application/json" \
  -d '{"name": "Developer"}'

You should receive a response like:

{"message":"Hello, Developer!"}

Deploying Your Function

When you're ready to deploy your function to production:

supabase functions deploy hello-world

Your function is now available at https://[project-ref].supabase.co/functions/v1/hello-world.

Working with Edge Functions

Handling HTTP Methods

Edge Functions can handle different HTTP methods:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';

serve(async (req) => {
  const url = new URL(req.url);
  
  // Handle different HTTP methods
  switch (req.method) {
    case 'GET':
      return handleGet(url);
    case 'POST':
      return handlePost(await req.json());
    case 'PUT':
      return handlePut(url, await req.json());
    case 'DELETE':
      return handleDelete(url);
    default:
      return new Response('Method Not Allowed', { status: 405 });
  }
});

function handleGet(url) {
  const id = url.searchParams.get('id');
  // Process GET request
  return new Response(
    JSON.stringify({ method: 'GET', id }),
    { headers: { 'Content-Type': 'application/json' } }
  );
}

function handlePost(data) {
  // Process POST request
  return new Response(
    JSON.stringify({ method: 'POST', data }),
    { headers: { 'Content-Type': 'application/json' } }
  );
}

function handlePut(url, data) {
  const id = url.searchParams.get('id');
  // Process PUT request
  return new Response(
    JSON.stringify({ method: 'PUT', id, data }),
    { headers: { 'Content-Type': 'application/json' } }
  );
}

function handleDelete(url) {
  const id = url.searchParams.get('id');
  // Process DELETE request
  return new Response(
    JSON.stringify({ method: 'DELETE', id }),
    { headers: { 'Content-Type': 'application/json' } }
  );
}

Accessing Request Data

You can access various parts of the request:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';

serve(async (req) => {
  const url = new URL(req.url);
  const path = url.pathname;
  const query = Object.fromEntries(url.searchParams);
  const headers = Object.fromEntries(req.headers);
  
  let body;
  try {
    body = await req.json();
  } catch {
    body = null;
  }
  
  return new Response(
    JSON.stringify({
      path,
      query,
      headers,
      body,
      method: req.method,
    }),
    { headers: { 'Content-Type': 'application/json' } }
  );
});

Working with Environment Variables

You can set environment variables for your functions:

# Set a secret
supabase secrets set MY_API_KEY=your-api-key-value

# Set multiple secrets
supabase secrets set --env-file .env

Then access them in your function:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';

serve(async (req) => {
  const apiKey = Deno.env.get('MY_API_KEY');
  
  // Use the API key for external service calls
  
  return new Response('Success!');
});

Integrating with Supabase Services

One of the most powerful aspects of Edge Functions is their ability to integrate seamlessly with other Supabase services.

Accessing Your Supabase Database

You can interact with your Supabase database from Edge Functions:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  // Create a Supabase client with the Admin key
  const supabaseUrl = Deno.env.get('SUPABASE_URL') ?? '';
  const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '';
  const supabase = createClient(supabaseUrl, supabaseKey);
  
  // Get data from the database
  const { data, error } = await supabase
    .from('products')
    .select('*')
    .limit(10);
    
  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
  
  return new Response(
    JSON.stringify({ data }),
    { headers: { 'Content-Type': 'application/json' } }
  );
});

Working with Supabase Auth

You can verify JWT tokens from Supabase Auth:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  // Get the authorization header
  const authHeader = req.headers.get('Authorization');
  
  if (!authHeader) {
    return new Response(JSON.stringify({ error: 'No authorization header' }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' }
    });
  }
  
  // Extract the token
  const token = authHeader.replace('Bearer ', '');
  
  // Create a Supabase client
  const supabaseUrl = Deno.env.get('SUPABASE_URL') ?? '';
  const supabaseKey = Deno.env.get('SUPABASE_ANON_KEY') ?? '';
  const supabase = createClient(supabaseUrl, supabaseKey);
  
  // Verify the token
  const { data: user, error } = await supabase.auth.getUser(token);
  
  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' }
    });
  }
  
  // User is authenticated, proceed with the request
  return new Response(
    JSON.stringify({ 
      message: 'Authenticated',
      user: user.user
    }),
    { headers: { 'Content-Type': 'application/json' } }
  );
});

Using Supabase Storage

You can interact with Supabase Storage:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  // Create a Supabase client
  const supabaseUrl = Deno.env.get('SUPABASE_URL') ?? '';
  const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '';
  const supabase = createClient(supabaseUrl, supabaseKey);
  
  // Get a signed URL for a file
  const { data, error } = await supabase
    .storage
    .from('public-bucket')
    .createSignedUrl('path/to/file.jpg', 60); // 60 seconds expiry
    
  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
  
  return new Response(
    JSON.stringify({ signedUrl: data.signedUrl }),
    { headers: { 'Content-Type': 'application/json' } }
  );
});

Real-World Use Cases for Edge Functions

1. Webhook Handler

Process webhooks from third-party services:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  // Verify webhook signature (example for Stripe)
  const signature = req.headers.get('stripe-signature');
  const body = await req.text();
  
  // Verify signature logic here...
  
  // Process the webhook
  const event = JSON.parse(body);
  
  // Connect to Supabase
  const supabaseUrl = Deno.env.get('SUPABASE_URL') ?? '';
  const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '';
  const supabase = createClient(supabaseUrl, supabaseKey);
  
  // Handle different event types
  switch (event.type) {
    case 'payment_intent.succeeded':
      // Update order status in database
      await supabase
        .from('orders')
        .update({ status: 'paid' })
        .eq('payment_id', event.data.object.id);
      break;
    // Handle other event types...
  }
  
  return new Response('Webhook received', { status: 200 });
});

2. Image Processing

Process images before storing them:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { resize } from 'https://deno.land/x/deno_image@0.0.4/mod.ts';

serve(async (req) => {
  // Get the image data from the request
  const formData = await req.formData();
  const file = formData.get('image');
  
  if (!file || !(file instanceof File)) {
    return new Response('No image provided', { status: 400 });
  }
  
  // Read the file as an ArrayBuffer
  const buffer = await file.arrayBuffer();
  
  // Resize the image
  const resizedImage = await resize(new Uint8Array(buffer), {
    width: 800,
    height: 600,
  });
  
  // Upload to Supabase Storage
  const supabaseUrl = Deno.env.get('SUPABASE_URL') ?? '';
  const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '';
  const supabase = createClient(supabaseUrl, supabaseKey);
  
  const fileName = `${Date.now()}-${file.name}`;
  
  const { data, error } = await supabase
    .storage
    .from('images')
    .upload(fileName, resizedImage, {
      contentType: file.type,
    });
    
  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
  
  // Get the public URL
  const { data: { publicUrl } } = supabase
    .storage
    .from('images')
    .getPublicUrl(fileName);
  
  return new Response(
    JSON.stringify({ url: publicUrl }),
    { headers: { 'Content-Type': 'application/json' } }
  );
});

3. External API Integration

Integrate with external APIs:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';

serve(async (req) => {
  const { query } = await req.json();
  
  // Call OpenAI API
  const openaiKey = Deno.env.get('OPENAI_API_KEY');
  
  const response = await fetch('https://api.openai.com/v1/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${openaiKey}`
    },
    body: JSON.stringify({
      model: 'text-davinci-003',
      prompt: query,
      max_tokens: 100
    })
  });
  
  const data = await response.json();
  
  return new Response(
    JSON.stringify({ result: data.choices[0].text.trim() }),
    { headers: { 'Content-Type': 'application/json' } }
  );
});

4. Scheduled Tasks

While Edge Functions don't have built-in scheduling, you can create a function that's triggered by an external scheduler:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

serve(async (req) => {
  // Verify the request is from your scheduler
  const authHeader = req.headers.get('Authorization');
  const expectedToken = Deno.env.get('SCHEDULER_SECRET');
  
  if (authHeader !== `Bearer ${expectedToken}`) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  // Connect to Supabase
  const supabaseUrl = Deno.env.get('SUPABASE_URL') ?? '';
  const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '';
  const supabase = createClient(supabaseUrl, supabaseKey);
  
  // Perform scheduled task (e.g., clean up expired sessions)
  const { error } = await supabase
    .from('user_sessions')
    .delete()
    .lt('expires_at', new Date().toISOString());
    
  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    });
  }
  
  return new Response('Scheduled task completed successfully');
});

Best Practices for Edge Functions

Security

  1. Use environment variables for secrets: Never hardcode sensitive information.

  2. Validate input: Always validate and sanitize user input to prevent injection attacks.

  3. Implement proper authentication: Verify JWT tokens for authenticated endpoints.

  4. Use the principle of least privilege: When accessing the database, use the minimum required permissions.

Performance

  1. Keep functions small and focused: Each function should do one thing well.

  2. Minimize cold starts: Cold starts occur when a function is invoked after being idle. Keep your dependencies minimal to reduce cold start times.

  3. Use caching when appropriate: For data that doesn't change frequently, consider implementing caching.

  4. Optimize database queries: Use indexes and efficient queries to minimize database load.

Maintainability

  1. Organize your functions logically: Group related functions together.

  2. Use TypeScript: TypeScript provides type safety and better tooling support.

  3. Write tests: Test your functions to ensure they work as expected.

  4. Document your functions: Include comments and documentation to make your code more maintainable.

Monitoring and Debugging

Supabase provides logs for your Edge Functions:

# View logs for a specific function
supabase functions logs hello-world

# Stream logs in real-time
supabase functions logs hello-world --follow

You can also add custom logging to your functions:

import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';

serve(async (req) => {
  console.log('Request received:', req.url);
  
  try {
    // Function logic here
    console.log('Function executed successfully');
    return new Response('Success');
  } catch (error) {
    console.error('Error in function:', error);
    return new Response('Error', { status: 500 });
  }
});

Conclusion

Supabase Edge Functions provide a powerful way to extend your application's backend capabilities beyond what's possible with just database operations. By leveraging the serverless paradigm, you can build scalable, efficient, and cost-effective solutions that integrate seamlessly with the rest of the Supabase ecosystem.

Whether you're processing webhooks, integrating with external APIs, or implementing custom business logic, Edge Functions give you the flexibility to build exactly what you need without managing servers or infrastructure.

As you continue exploring Supabase Edge Functions, remember to follow best practices for security, performance, and maintainability to ensure your functions are robust and reliable.

Ready to get started? Check out the official Supabase Edge Functions documentation for more detailed information and examples.