Edge Functions: Lógica serverless en el borde
Kevin Dávila
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 initCrear una función
// terminal
supabase functions new hello-worldEsto 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-worldLlamar 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.