Supabase Edge Functions en la práctica: runtime Deno y guía de desarrollo con TypeScript

El teléfono vibra sin parar. Los webhooks de Stripe devuelven 500 en producción: el pago se completó, pero el pedido no se creó.
Revisando los logs, el culpable era la antigua función serverless: el arranque en frío era demasiado largo y Stripe agotaba el tiempo de espera antes de recibir respuesta. Y además había que montar un gateway API para validar firmas y gestionar CORS…
Esa noche empecé a estudiar en serio Supabase Edge Functions. Al principio, «runtime Deno» me frenó un poco — años escribiendo en Node.js, cambiar de runtime implica aprender APIs nuevas. Pero en la práctica la lógica es distinta: no se trata de «migrar», sino de una opción más ligera para escenarios que no requieren dependencias pesadas.
Este artículo comparte las trampas que encontré y lo que aprendí: arquitectura de Edge Functions, diferencias entre Deno y Node.js, flujo de desarrollo y depuración local, y cómo escribir una API de forma elegante con Hono.
Qué son Edge Functions — arquitectura y elección tecnológica
Empecemos por qué es Edge Functions y por qué Supabase eligió Deno en lugar de Node.js.
Ejecución en el borde, no hosting centralizado en la nube
Edge Functions son funciones TypeScript que se ejecutan en nodos edge. A diferencia de Lambda o Vercel Functions, no se despliegan en unas pocas grandes regiones: se distribuyen en cientos de puntos edge en todo el mundo.
¿Qué implica? Un usuario en Shanghái puede ejecutar la función en Tokio: la latencia baja de cientos de milisegundos a unas decenas.
Pero el borde tiene un coste: la función no puede ser demasiado pesada. Cada función corre en un isolate V8, con su propio heap de memoria y hilo de ejecución; arranque en milisegundos, pero memoria y tiempo de ejecución limitados. Ideal para operaciones de corta duración: webhooks, imágenes OG, llamadas a APIs de terceros, envío de correo.
Menos adecuado: tareas de larga duración, bibliotecas Node.js nativas pesadas, acceso al sistema de archivos.
Por qué Deno
Pasé mucho tiempo en las GitHub Discussions de Supabase. La explicación oficial, en resumen:
- Arranque rápido: Deno empaqueta el código en formato ESZip; arranque en frío de 0-5 ms. Lambda con Node.js suele tardar 100-500 ms.
- Modelo de seguridad: acceso a sistema de archivos y red deshabilitados por defecto; autorización explícita. Importante en entornos edge multiinquilino — no quieres que la función de otro lea tus datos.
- TypeScript nativo: sin tsconfig ni ts-node; un archivo
.tsy listo. Para quien ya hace backend en TypeScript, menos configuración. - Portabilidad: Deno se integra en otras aplicaciones. Supabase usa su propio fork
deno_core, optimizado para escenarios embebidos.
A cambio, el ecosistema es más pequeño que el de Node.js; algunos paquetes npm no funcionan. Deno ahora admite npm specifiers (import { xxx } from 'npm:lodash'), la compatibilidad ha mejorado.
Panorama de la arquitectura
Flujo aproximado de una solicitud:
Cliente → CDN/gateway edge → validación JWT → isolate V8 ejecuta la función → respuesta
Punto clave: la validación JWT — Edge Functions verifica por defecto el header Authorization. Para acceso público, despliega con --no-verify-jwt.
Configuración del entorno de desarrollo y comandos CLI
Conceptos claros; pasemos a la práctica.
Instalar Supabase CLI
En macOS, con Homebrew:
brew install supabase/tap/supabase
Linux y Windows tienen métodos equivalentes; la documentación oficial es clara, no los repito.
Luego inicia sesión:
supabase login
Se abre el navegador para autorizar el CLI en tu cuenta de Supabase.
Inicializar el proyecto
En el directorio del proyecto:
supabase init
Crea el directorio supabase/ con config.toml y el subdirectorio functions/ si no existe.
Crear la primera Edge Function
supabase functions new hello-world
El comando crea supabase/functions/hello-world/ con un archivo index.ts:
Deno.serve(async (req: Request) => {
const { name } = await req.json()
const data = {
message: `Hello ${name}!`,
}
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
})
})
Así de simple. Deno.serve() es la API nativa de Deno; Request y Response son Web API estándar, como fetch en el navegador.
Servidor de desarrollo local
supabase functions serve --env-file supabase/.env.local
Inicia un servidor local en http://localhost:54321; la función está en http://localhost:54321/functions/v1/hello-world.
La primera trampa que encontré: olvidar iniciar la pila local de Supabase (PostgreSQL incluido). Orden correcto:
# Primero la pila local
supabase start
# Luego las funciones
supabase functions serve
Solicitud de prueba
Con curl o HTTPie:
curl -i --location --request POST 'http://localhost:54321/functions/v1/hello-world' \
--header 'Authorization: Bearer <your-anon-key>' \
--header 'Content-Type: application/json' \
--data '{"name":"World"}'
Respuesta:
{
"message": "Hello World!"
}
Funciona.
La recarga en caliente es automática: modifica el código y guarda, sin reiniciar el servicio.
Variables de entorno
No pongas secretos en el código. Supabase gestiona variables mediante archivos .env:
# Crear archivo .env
echo "MY_SECRET=super_secret_value" > supabase/.env.local
# Leer en la función
const mySecret = Deno.env.get('MY_SECRET')
En producción, con supabase secrets set:
supabase secrets set MY_SECRET=super_secret_value
En la práctica: API RESTful con Hono
Deno.serve() basta para casos simples; con enrutamiento, middleware y validación, hacerlo a mano es penoso.
Ahí entra Hono.
Qué es Hono
Hono es un framework web ultraligero para runtimes edge. Deno, Cloudflare Workers, Bun… enrutamiento performante y TypeScript de primera.
Oficialmente «small, simple, and ultrafast» — mi experiencia lo confirma.
Integración en Edge Functions
Nueva función:
supabase functions new user-api
Luego modifica index.ts:
import { Hono } from 'jsr:@hono/hono'
import { cors } from 'jsr:@hono/hono/cors'
import { logger } from 'jsr:@hono/hono/logger'
const app = new Hono().basePath('/api')
// Middleware
app.use('*', cors())
app.use('*', logger())
// Rutas
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ user: { id, name: 'Demo User', email: '[email protected]' } })
})
app.post('/users', async (c) => {
const body = await c.req.json<{ name: string; email: string }>()
// Aquí puedes conectar la base de datos de Supabase
return c.json({ created: body }, 201)
})
app.put('/users/:id', async (c) => {
const id = c.req.param('id')
const body = await c.req.json<{ name?: string; email?: string }>()
return c.json({ updated: { id, ...body } })
})
app.delete('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ deleted: id })
})
// Inicio
Deno.serve(app.fetch)
Puntos importantes:
jsr:@hono/honoes el formato JSR de Deno, no npm. JSR es el registro oficial de Deno.basePath('/api')prefija las rutas con/api.ces el context de Hono: solicitud, respuesta y utilidades.c.json()establece Content-Type y gestiona null/undefined.
Conexión a la base de datos de Supabase
Hono es el framework web; para la base de datos hace falta el cliente de Supabase. Ejemplo completo:
import { Hono } from 'jsr:@hono/hono'
import { createClient } from 'jsr:@supabase/supabase-js@2'
const app = new Hono().basePath('/api')
// Cliente Supabase
const supabaseUrl = Deno.env.get('SUPABASE_URL')!
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
const supabase = createClient(supabaseUrl, supabaseKey, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
})
// GET /api/users — lista
app.get('/users', async (c) => {
const { data, error } = await supabase
.from('users')
.select('id, name, email, created_at')
if (error) {
return c.json({ error: error.message }, 500)
}
return c.json({ users: data })
})
// POST /api/users — creación
app.post('/users', async (c) => {
const body = await c.req.json<{ name: string; email: string }>()
const { data, error } = await supabase
.from('users')
.insert(body)
.select()
.single()
if (error) {
return c.json({ error: error.message }, 400)
}
return c.json({ user: data }, 201)
})
Deno.serve(app.fetch)
Uso SUPABASE_SERVICE_ROLE_KEY: permisos completos, omite RLS. Gestiona con cautela en producción.
Manejo de errores y validación
Hono no trae validador integrado, pero Zod encaja bien:
import { z } from 'npm:zod'
import { zValidator } from 'jsr:@hono/zod-validator'
const userSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
})
app.post(
'/users',
zValidator('json', userSchema),
async (c) => {
const validated = c.req.valid('json')
// validated ya es un objeto con tipos seguros
return c.json({ received: validated })
}
)
Si falla la validación: 400 con detalle de errores.
Despliegue y buenas prácticas en producción
Funciona en local; toca producción.
Comando de despliegue
supabase functions deploy user-api
En el primer despliegue el CLI pregunta qué proyecto de Supabase vincular. Después: subida, build y despliegue automáticos.
URL de la función:
https://[PROJECT_ID].supabase.co/functions/v1/user-api
Variables de entorno y Secrets
En producción hay que configurar las variables por separado:
supabase secrets set SUPABASE_URL=https://xxx.supabase.co
supabase secrets set SUPABASE_SERVICE_ROLE_KEY=eyJxxx...
Los Secrets se almacenan cifrados; lectura con Deno.env.get() en tiempo de ejecución.
Estrategia de validación JWT
Edge Functions valida JWT por defecto:
- Solo pasan solicitudes con
Authorization: Bearer <token>válido - La información del usuario del token está en los headers de
req
Para API pública (webhooks de terceros), --no-verify-jwt:
supabase functions deploy user-api --no-verify-jwt
Cualquiera puede invocar la función — la validación en código te toca a ti.
Reducir la latencia del arranque en frío
Deno arranca rápido, pero se puede hacer más:
- Reducir el volumen de dependencias: prioriza paquetes Deno/JSR, menos npm
- Carga diferida: módulos grandes con
import()bajo demanda - Funciones ligeras: una función, una responsabilidad — no metas todo el backend dentro
Supabase recomienda menos de 2 segundos de ejecución y arranque en frío de 0-5 ms. Estos consejos aceleran la respuesta:
Monitorización y logs
El Dashboard muestra logs de invocaciones y errores. También puedes integrar Sentry u otros servicios.
La API EdgeRuntime.waitUntil() permite seguir en segundo plano tras la respuesta:
EdgeRuntime.waitUntil(
fetch('https://analytics.example.com/track', { method: 'POST', body: '...' })
)
return new Response('OK')
El cliente recibe la respuesta sin esperar la tarea en segundo plano.
Conclusión
¿Para qué escenarios encaja Edge Functions?
Adecuado:
- Webhooks (Stripe, GitHub, Slack)
- Generación de imágenes OG
- Inferencia de IA (llamadas a API LLM)
- Correo y notificaciones
- Procesamiento de datos de corta duración
Menos adecuado:
- Tareas de larga duración (transcodificación de vídeo)
- Bibliotecas Node.js nativas pesadas
- Acceso al sistema de archivos
Si ya usas base de datos y autenticación de Supabase, Edge Functions extiende el stack de forma natural — sin montar servidores extra ni operaciones pesadas; escribes la lógica de negocio.
¿Frente a Cloudflare Workers o Vercel Functions? Cada uno tiene ventajas. Workers es más maduro; Vercel se integra con Next.js. Pero con Supabase, Edge Functions ofrece la mejor integración — cliente de base de datos, auth y almacenamiento listos.
Para empezar: github.com/supabase/supabase/tree/master/examples/edge-functions
Preguntas en los comentarios, o en el Discord de Supabase.
Flujo completo de desarrollo y despliegue de Supabase Edge Functions
Guía operativa completa, desde la configuración del entorno hasta el despliegue en producción
⏱️ Estimated time: 45 min
- 1
Step 1: Instalar Supabase CLI e iniciar sesión
Instala el CLI con Homebrew (macOS):
```bash
brew install supabase/tap/supabase
supabase login
```
El inicio de sesión abre el navegador para autorizar el CLI en tu cuenta de Supabase. - 2
Step 2: Inicializar el proyecto y crear una función
Ejecuta el comando de inicialización en el directorio del proyecto y crea tu primera función:
```bash
supabase init
supabase functions new hello-world
```
Esto crea una plantilla de función en el directorio `supabase/functions/`. - 3
Step 3: Iniciar el entorno de desarrollo local
Primero inicia la pila local de Supabase (PostgreSQL incluido) y luego el servicio de funciones:
```bash
supabase start
supabase functions serve --env-file supabase/.env.local
```
Dirección local: `http://localhost:54321/functions/v1/{function-name}` - 4
Step 4: Construir una API con el framework Hono
Instala Hono y crea una API RESTful:
```typescript
import { Hono } from 'jsr:@hono/hono'
import { cors } from 'jsr:@hono/hono/cors'
const app = new Hono().basePath('/api')
app.use('*', cors())
app.get('/users/:id', (c) => {
return c.json({ user: { id: c.req.param('id') } })
})
Deno.serve(app.fetch)
```
Hono admite enrutamiento, middleware y validación de parámetros. - 5
Step 5: Configurar variables de entorno y Secrets
Archivo `.env` en local, Secrets en producción:
```bash
# Local
echo "MY_SECRET=value" > supabase/.env.local
# Producción
supabase secrets set MY_SECRET=value
```
Lectura en la función mediante `Deno.env.get('MY_SECRET')`. - 6
Step 6: Desplegar en producción
Despliega la función y configura el acceso público si es necesario:
```bash
# Despliegue estándar (requiere validación JWT)
supabase functions deploy user-api
# API pública (sin validación JWT)
supabase functions deploy user-api --no-verify-jwt
```
Formato de URL de producción: `https://[PROJECT_ID].supabase.co/functions/v1/user-api`
FAQ
¿Cuál es la diferencia entre Supabase Edge Functions y Cloudflare Workers?
¿Edge Functions es adecuado para procesar webhooks?
¿En qué se diferencia la gestión de paquetes de Deno respecto a Node.js?
¿Cómo conectar la base de datos de Supabase desde Edge Functions?
¿Edge Functions tiene límite de tiempo de ejecución?
¿Cómo depurar Edge Functions?
9 min de lectura · Publicado el: 19 abr 2026 · Actualizado el: 21 ago 2026
Supabase en práctica
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Supabase Storage en la práctica: subida de archivos, CDN y control de acceso
Guía completa de Supabase Storage: comparación de tres modos de acceso, subida TUS por fragmentos, optimización Smart CDN y análisis de costos frente a R2/S3, con ejemplos React y solución de problemas.
Parte 7 de 10
Siguiente
Configuración en profundidad de Supabase Auth: OAuth, SSO y control de permisos
Explicación detallada de la configuración avanzada de Supabase Auth: integración de múltiples proveedores de OAuth, autenticación empresarial SAML SSO, aislamiento de permisos de múltiples inquilinos RLS, una solución de autenticación completa desde aplicaciones de consumo hasta SaaS empresarial
Parte 9 de 10



Comentarios
Inicia sesión con GitHub para dejar un comentario