Codea Bien Logo
Edge Functions: Lógica serverless en el borde
Supabase

Edge Functions: Lógica serverless en el borde

Kevin Dávila

supabase edge functionsserverlessdenowebhookssupabase functions

Tu frontend le pregunta a tu backend si el token es válido. Tu backend le pregunta a la base de datos. El usuario, mientras tanto, mira una pantalla en blanco. Ese round-trip de validación en cada request es tiempo que no necesita existir.

Supabase Edge Functions ejecutan esa lógica en el borde, a milisegundos de tus usuarios: Deno, TypeScript y deploy en segundos, sin servidor que mantener. En este post vas a crear tu primera función y ver los patrones donde brillan de verdad: webhooks, APIs custom y todo lo que no quieres exponer al cliente.

Qué son las Edge Functions

Las Edge Functions son funciones serverless que corren en los edge servers de Supabase (Deno runtime). Están diseñadas para:

  • Webhooks (Stripe, GitHub, etc.)

  • APIs custom

  • Procesamiento de datos

  • Lógica que requiere secretos del servidor

  • Integraciones con servicios externos

Crear tu primera Edge Function

Inicializar Supabase CLI

// terminal
npm install -g supabase
supabase init

Crear una función

// terminal
supabase functions new hello-world

Esto crea supabase/functions/hello-world/index.ts.

Escribir la función

// 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: `Hola ${name}!`,
    timestamp: new Date().toISOString()
  }

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

Deploy

// terminal
supabase functions deploy hello-world

Llamar la función

// src/call-function.ts
import { supabase } from './lib/supabase'

const { data, error } = await supabase.functions.invoke('hello-world', {
  body: { name: 'Kevin' }
})

console.log(data) // { message: 'Hola Kevin!', timestamp: '...' }

Acceder a Supabase dentro de la función

// supabase/functions/get-tasks/index.ts
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) => {
  // Crear cliente de Supabase con service role key
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
  )

  // Obtener tareas (bypass RLS con service role)
  const { data, error } = await supabase
    .from('tasks')
    .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' } }
  )
})

Autenticación en Edge Functions

Verificar el usuario

// supabase/functions/protected/index.ts
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) => {
  // Obtener el token del 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' } }
    )
  }

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_ANON_KEY') ?? '',
    { global: { headers: { Authorization: authHeader } } }
  )

  // Verificar el usuario
  const { data: { user }, error } = await supabase.auth.getUser()

  if (error || !user) {
    return new Response(
      JSON.stringify({ error: 'Unauthorized' }),
      { status: 401, headers: { 'Content-Type': 'application/json' } }
    )
  }

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

Llamar función protegida desde el cliente

// src/call-protected.ts
const { data, error } = await supabase.functions.invoke('protected', {
  headers: {
    Authorization: `Bearer ${(await supabase.auth.getSession()).data.session?.access_token}`
  }
})

Webhook de Stripe

Ejemplo real: recibir eventos de Stripe.

// supabase/functions/stripe-webhook/index.ts
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) => {
  const signature = req.headers.get('stripe-signature')
  const body = await req.text()

  // Verificar signature de Stripe
  // (En producción, usa la librería de Stripe para verificar)

  const event = JSON.parse(body)

  const supabase = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
  )

  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object
      await supabase
        .from('subscriptions')
        .upsert({
          user_id: session.metadata.user_id,
          stripe_customer_id: session.customer,
          status: 'active'
        })
      break

    case 'customer.subscription.deleted':
      const subscription = event.data.object
      await supabase
        .from('subscriptions')
        .update({ status: 'cancelled' })
        .eq('stripe_customer_id', subscription.customer)
      break
  }

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

Procesamiento de imágenes

// supabase/functions/process-image/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'

serve(async (req) => {
  const { imageUrl, width, height } = await req.json()

  // Descargar imagen
  const response = await fetch(imageUrl)
  const buffer = await response.arrayBuffer()

  // Procesar con una librería de imágenes (ej: sharp via WASM)
  // ...

  return new Response(buffer, {
    headers: { 'Content-Type': 'image/webp' }
  })
})

Variables de entorno

Las Edge Functions pueden acceder a secretos configurados en el dashboard:

// Terminal - configurar secretos
supabase secrets set MY_SECRET=valor_secreto
// Usar en la función
const secret = Deno.env.get('MY_SECRET')

Edge Functions vs Database Functions

Feature

Edge Functions

Database Functions

Runtime

Deno

PostgreSQL/PLpgSQL

Ubicación

Edge servers

Dentro de la DB

Latencia

Baja (edge)

Muy baja (misma DB)

Casos de uso

Webhooks, APIs, integraciones

Lógica de negocio compleja

Acceso a DB

Via Supabase client

Directo

Cuándo usar cada una

Edge Functions:

  • Necesitas llamar servicios externos

  • Webhooks de Stripe, GitHub, etc.

  • Lógica que depende de secretos del servidor

  • APIs custom con lógica compleja

Database Functions:

  • Lógica de negocio que solo toca la DB

  • Stored procedures

  • Triggers

  • Operaciones que necesitan transacciones

Errores comunes

"Function not found"

  • Verifica que deployaste la función

  • Revisa el nombre (case-sensitive)

"CORS error"

  • Agrega headers CORS en la función:

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

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

  // Tu lógica aquí
  return new Response(data, { headers: { ...corsHeaders, 'Content-Type': 'application/json' } })
})

"Timeout"

  • Las Edge Functions tienen un timeout de 150 segundos

  • Para procesos largos, usa background tasks

Qué sigue

En el próximo artículo vamos con Supabase + AI: cómo usar pgvector para embeddings, búsqueda semántica, y construir pipelines de RAG directamente en tu base de datos.