Supabase Edge Functions na prática: guia do runtime Deno e desenvolvimento com TypeScript

O celular não parava de vibrar. O webhook do Stripe retornava erros 500 sem parar em produção: os clientes concluíam o pagamento, mas os pedidos não eram criados.
Levantei para conferir os logs e descobri que o problema estava na função serverless anterior. A inicialização a frio demorava demais, e o Stripe atingia o timeout antes de receber uma resposta. Para piorar, eu ainda precisava montar outro API Gateway para validar a assinatura e tratar o CORS…
Depois daquela noite, comecei a estudar as Supabase Edge Functions a sério. No início, a expressão “runtime Deno” me deixou um pouco receoso. Afinal, depois de tantos anos trabalhando com Node.js, trocar de runtime significava aprender um novo conjunto de APIs. Mas, depois de testar bastante, percebi que a proposta das Edge Functions é completamente diferente: elas não exigem que você “migre”, e sim oferecem uma alternativa mais leve, voltada especificamente a cenários que dispensam dependências pesadas.
Neste artigo, compartilho os problemas que enfrentei e o que aprendi: os princípios da arquitetura das Edge Functions, as diferenças entre Deno e Node.js, o fluxo de desenvolvimento e depuração local e a experiência prática de criar APIs de forma elegante com o framework Hono.
O que são Edge Functions: arquitetura e escolhas técnicas
Antes de tudo, vamos esclarecer o que são Edge Functions e por que o Supabase escolheu o Deno, e não o Node.js.
Execução na borda, não hospedagem centralizada na nuvem
Edge Functions são funções TypeScript executadas em pontos de presença na borda. Diferentemente das funções tradicionais do Lambda ou da Vercel, elas não ficam concentradas em servidores de algumas grandes regiões: são distribuídas por centenas de pontos de presença ao redor do mundo.
O que isso significa? Se um usuário em Xangai fizer uma requisição, a função poderá ser executada em um ponto de presença em Tóquio, reduzindo a latência de centenas para dezenas de milissegundos.
Mas a borda também tem seu custo: a função não pode ser pesada demais. Cada função é executada de forma independente em um isolate V8, com seu próprio heap de memória e sua própria thread de execução. A inicialização acontece em milissegundos, mas a memória e o tempo de execução são limitados. Por isso, esse modelo é adequado para operações de curta duração, como processar webhooks, gerar imagens OG, chamar APIs de terceiros e enviar e-mails.
Também há cenários inadequados: tarefas de longa duração, bibliotecas que dependem de muitos módulos nativos do Node.js e operações que precisam acessar o sistema de arquivos.
Por que Deno
Pesquisei bastante essa questão nas GitHub Discussions do Supabase. Em linhas gerais, a explicação oficial é esta:
- Inicialização rápida: o Deno empacota o código no formato ESZip, permitindo que a inicialização a frio da função leve de 0 a 5 ms, enquanto uma inicialização a frio do Lambda com Node.js costuma levar de 100 a 500 ms.
- Modelo de segurança: por padrão, o Deno desabilita o acesso ao sistema de arquivos e à rede, exigindo autorização explícita. Isso é muito importante em um ambiente de borda multilocatário — ninguém quer que a função de outra pessoa consiga ler seus dados, certo?
- Suporte nativo a TypeScript: não é preciso configurar tsconfig nem instalar ts-node; basta escrever um arquivo
.tse executá-lo diretamente. Para quem já escreve backends em TypeScript, isso elimina bastante tempo de configuração. - Portabilidade: o Deno pode ser incorporado a outros aplicativos. O Supabase usa uma ramificação própria do Deno, chamada
deno_core, aprimorada especificamente para cenários incorporados.
Há vantagens e desvantagens. O ecossistema do Deno é bem menor que o do Node.js, e alguns pacotes npm não podem ser usados diretamente. Hoje, porém, o Deno aceita npm specifiers, então você pode escrever import { xxx } from 'npm:lodash', o que melhorou muito a compatibilidade.
Uma visão rápida da arquitetura
Depois que uma requisição chega, o fluxo é mais ou menos este:
Cliente → CDN/gateway de borda → validação de JWT → execução da função no isolate V8 → resposta
O ponto mais importante é a validação de JWT: por padrão, as Edge Functions validam o header Authorization da requisição para garantir que somente usuários autorizados possam chamá-las. Se quiser permitir acesso público, adicione a opção --no-verify-jwt durante a implantação.
Configuração do ambiente de desenvolvimento e comandos da CLI
Agora que os conceitos estão claros, vamos colocar a mão na massa.
Instalação da Supabase CLI
Uso macOS, então instalei diretamente pelo Homebrew:
brew install supabase/tap/supabase
Também há métodos de instalação para Linux e Windows. A documentação oficial explica tudo com clareza, então não vou repetir aqui.
Após a instalação, faça login:
supabase login
Esse comando abre o navegador para que você autorize o acesso da CLI à sua conta do Supabase.
Inicialização do projeto
No diretório do seu projeto, execute:
supabase init
Isso cria um diretório supabase/, com o arquivo de configuração config.toml e um subdiretório functions/, que será criado automaticamente caso ainda não exista.
Criação da primeira Edge Function
supabase functions new hello-world
Esse comando cria o diretório hello-world/ dentro de supabase/functions/, com um arquivo index.ts como este:
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',
},
})
})
Sim, é só isso. Deno.serve() é uma API nativa do Deno que recebe uma função para processar requisições. Tanto Request quanto Response são APIs Web padronizadas, usadas da mesma forma que o fetch no navegador.
Servidor de desenvolvimento local
Inicie o ambiente de desenvolvimento local:
supabase functions serve --env-file supabase/.env.local
Esse comando inicia um servidor local no endereço padrão http://localhost:54321. Sua função fica acessível em http://localhost:54321/functions/v1/hello-world.
Para ser sincero, na primeira vez cometi um erro: esqueci de iniciar antes a stack local do Supabase, que inclui o PostgreSQL local. O fluxo correto é:
# Primeiro, inicie a stack local do Supabase
supabase start
# Depois, inicie o serviço de funções
supabase functions serve
Requisição de teste
Envie uma requisição com curl ou HTTPie para testar:
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"}'
Resposta:
{
"message": "Hello World!"
}
Funcionou.
O recarregamento automático já vem ativado. Depois de alterar e salvar o código, a mudança entra em vigor imediatamente, sem reiniciar o serviço. A experiência é muito boa.
Variáveis de ambiente
Não coloque informações confidenciais no código. O Supabase permite gerenciar variáveis de ambiente por meio de arquivos .env:
# Crie o arquivo .env
echo "MY_SECRET=super_secret_value" > supabase/.env.local
# Leia o valor dentro da função
const mySecret = Deno.env.get('MY_SECRET')
Ao implantar em produção, use o comando supabase secrets set:
supabase secrets set MY_SECRET=super_secret_value
Na prática: criação de uma API RESTful com o framework Hono
O Deno.serve() nativo dá conta do básico. Mas, quando a lógica da função fica mais complexa e exige rotas, middlewares e validação de parâmetros, escrever tudo manualmente se torna trabalhoso.
É aí que o Hono entra em cena.
O que é Hono
Hono é um framework web ultraleve, projetado especialmente para runtimes de borda. Ele oferece suporte a vários runtimes, como Deno, Cloudflare Workers e Bun, tem excelente desempenho de roteamento e suporte de primeira classe a TypeScript.
Segundo o projeto, ele é “small, simple, and ultrafast”. Na minha experiência, a descrição faz sentido.
Integração com Edge Functions
Primeiro, crie uma nova função:
supabase functions new user-api
Depois, altere o 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')
// Middlewares
app.use('*', cors())
app.use('*', logger())
// Definição das rotas
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 }>()
// Aqui você pode integrar o banco de dados do 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 })
})
// Inicie o serviço
Deno.serve(app.fetch)
Alguns pontos importantes:
jsr:@hono/honousa o formato de pacotes JSR do Deno, e não npm. JSR é o repositório oficial de pacotes do Deno.basePath('/api')adiciona o prefixo/apiàs suas rotas.cé o objeto de contexto do Hono e contém a requisição, a resposta e diversos métodos utilitários.c.json()define automaticamente o header Content-Type e também consegue lidar com null e undefined.
Conexão com o banco de dados do Supabase
Hono é apenas um framework web. Para trabalhar com o banco de dados, ainda é preciso usar o cliente do Supabase. Veja um exemplo completo:
import { Hono } from 'jsr:@hono/hono'
import { createClient } from 'jsr:@supabase/supabase-js@2'
const app = new Hono().basePath('/api')
// Inicialize o cliente do 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 - Criação
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)
Observe que usei SUPABASE_SERVICE_ROLE_KEY. Essa chave tem acesso completo ao banco de dados e ignora o RLS. Use-a com cuidado em produção.
Tratamento de erros e validação
Hono não inclui um validador, mas pode ser usado em conjunto com o Zod:
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 já é um objeto com tipagem segura
return c.json({ received: validated })
}
)
Quando a validação falha, uma resposta de erro 400 é retornada automaticamente com informações detalhadas no corpo.
Implantação e boas práticas para produção
Tudo funcionou localmente. Agora é hora de levar para produção.
Comando de implantação
supabase functions deploy user-api
Na primeira implantação, a CLI pergunta a qual projeto do Supabase você deseja vincular a função. Depois disso, o código é enviado, compilado e implantado automaticamente.
Após uma implantação bem-sucedida, a URL da função segue este formato:
https://[PROJECT_ID].supabase.co/functions/v1/user-api
Variáveis de ambiente e Secrets
As variáveis de ambiente de produção precisam ser configuradas separadamente:
supabase secrets set SUPABASE_URL=https://xxx.supabase.co
supabase secrets set SUPABASE_SERVICE_ROLE_KEY=eyJxxx...
Esses Secrets são armazenados de forma criptografada e lidos em tempo de execução pela função com Deno.env.get().
Estratégia de validação de JWT
Como vimos, as Edge Functions validam JWT por padrão. Isso significa que:
- Somente requisições com um
Authorization: Bearer <token>válido são aceitas - As informações do usuário contidas no token podem ser obtidas por meio de
req.headers
Se precisar criar uma API pública, por exemplo para um webhook de terceiros, adicione --no-verify-jwt durante a implantação:
supabase functions deploy user-api --no-verify-jwt
Isso, porém, permite que qualquer pessoa chame sua função. Portanto, você precisa implementar a validação no próprio código.
Redução da latência de inicialização a frio
Embora a inicialização a frio do Deno seja rápida, ainda há algumas medidas que você pode tomar:
- Reduza o tamanho das dependências: dê preferência a pacotes nativos do Deno/JSR e use menos pacotes npm
- Use carregamento tardio: carregue módulos grandes sob demanda com
import() - Mantenha as funções leves: cada função deve fazer apenas uma coisa; não coloque todo o backend dentro dela
O Supabase recomenda oficialmente que uma função não ultrapasse 2 segundos de execução, e a inicialização a frio leva de 0 a 5 ms. Portanto, essas sugestões ajudam a tornar as respostas ainda mais rápidas.
Monitoramento e logs
No Dashboard, você pode consultar os logs de chamadas e os relatórios de erro das funções. Também é possível integrar o Sentry ou outros serviços de monitoramento.
Há ainda a API EdgeRuntime.waitUntil(), que permite que a função continue executando uma tarefa em segundo plano depois de retornar a resposta:
EdgeRuntime.waitUntil(
fetch('https://analytics.example.com/track', { method: 'POST', body: '...' })
)
return new Response('OK')
Assim, o cliente recebe a resposta sem precisar esperar a conclusão da tarefa em segundo plano.
Conclusão
Depois de tudo isso, para quais cenários as Edge Functions realmente são indicadas?
Indicadas para:
- Processamento de webhooks (Stripe, GitHub, Slack)
- Geração de imagens OG
- Inferência de IA (chamadas a APIs de LLM)
- Notificações por e-mail e mensagens
- Processamento de dados de curta duração
Menos indicadas para:
- Tarefas de longa duração, como transcodificação de vídeo
- Bibliotecas que dependem de módulos nativos pesados do Node.js
- Operações que precisam acessar o sistema de arquivos
Se você já usa o banco de dados e a autenticação do Supabase, as Edge Functions são uma extensão muito natural: não é preciso configurar outro servidor nem se preocupar com operações; basta escrever a lógica de negócio.
E em comparação com Cloudflare Workers ou Vercel Functions? Na minha opinião, cada opção tem seus pontos fortes. Cloudflare Workers é mais maduro e tem um ecossistema maior, enquanto Vercel Functions se integra mais profundamente ao ecossistema do Next.js. Mas, se você já usa Supabase, as Edge Functions oferecem a melhor experiência de integração: cliente de banco de dados, autenticação e armazenamento já estão prontos para uso.
Se quiser experimentar, comece pelo repositório oficial de exemplos: github.com/supabase/supabase/tree/master/examples/edge-functions
Se tiver alguma dúvida, deixe um comentário ou procure ajuda diretamente na comunidade do Supabase no Discord.
Fluxo completo de desenvolvimento e implantação com Supabase Edge Functions
Guia completo, da configuração do ambiente à implantação em produção
⏱️ Estimated time: 45 min
- 1
Step 1: Instale a Supabase CLI e faça login
Instale a CLI pelo Homebrew no macOS:
```bash
brew install supabase/tap/supabase
supabase login
```
Após o login, o navegador será aberto para autorizar o acesso da CLI à sua conta do Supabase. - 2
Step 2: Inicialize o projeto e crie uma função
No diretório do projeto, execute o comando de inicialização e crie sua primeira função:
```bash
supabase init
supabase functions new hello-world
```
Isso criará um modelo de função no diretório `supabase/functions/`. - 3
Step 3: Inicie o ambiente de desenvolvimento local
Primeiro, inicie a stack local do Supabase, que inclui o PostgreSQL, e depois o serviço de funções:
```bash
supabase start
supabase functions serve --env-file supabase/.env.local
```
Endereço da função local: `http://localhost:54321/functions/v1/{function-name}` - 4
Step 4: Crie uma API com o framework Hono
Instale o Hono e crie uma 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)
```
O Hono oferece suporte a rotas, middlewares e validação de parâmetros. - 5
Step 5: Configure variáveis de ambiente e Secrets
No desenvolvimento local, use um arquivo `.env`; em produção, use Secrets:
```bash
# Ambiente local
echo "MY_SECRET=value" > supabase/.env.local
# Produção
supabase secrets set MY_SECRET=value
```
Dentro da função, leia o valor com `Deno.env.get('MY_SECRET')`. - 6
Step 6: Implante em produção
Implante a função e, se necessário, permita o acesso público:
```bash
# Implantação padrão (exige validação de JWT)
supabase functions deploy user-api
# API pública (sem validação de JWT)
supabase functions deploy user-api --no-verify-jwt
```
Formato da URL de produção: `https://[PROJECT_ID].supabase.co/functions/v1/user-api`
FAQ
Qual é a diferença entre Supabase Edge Functions e Cloudflare Workers?
As Edge Functions são indicadas para processar webhooks?
Qual é a diferença entre o gerenciamento de pacotes do Deno e do Node.js?
Como conectar uma Edge Function ao banco de dados do Supabase?
As Edge Functions têm limite de tempo de execução?
Como depurar Edge Functions?
12 min de leitura · Publicado em: 19 abr 2026 · Atualizado em: 4 set 2026
Supabase na prática
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Supabase Storage na prática: upload de arquivos, CDN e controle de acesso
Guia prático completo do Supabase Storage: comparação entre três modelos de controle de acesso, upload em partes com TUS, técnicas de otimização do Smart CDN e análise de preços em relação ao R2 e ao S3. Inclui exemplos em React e soluções para problemas comuns.
Parte 6 de 7
Próximo
Este é o post mais recente da série até agora.



Comentários
Entre com GitHub para comentar