Construindo APIs com Hono no Cloudflare Workers: do local ao deploy
1. Introdução ao Hono e Cloudflare Workers
Hono é um framework web ultraleve e extremamente rápido, projetado especificamente para ambientes de edge computing. Com menos de 14KB de tamanho, ele oferece performance comparável a frameworks nativos, suporte nativo a TypeScript e uma API intuitiva baseada em middleware. O Cloudflare Workers, por sua vez, é a plataforma serverless da Cloudflare que executa código JavaScript/TypeScript na borda global, em mais de 330 data centers ao redor do mundo.
A combinação Hono + Cloudflare Workers é ideal para construir APIs REST, microsserviços, gateways de API e webhooks que exigem latência mínima e alta disponibilidade. Diferente de soluções tradicionais baseadas em Node.js, essa stack elimina a necessidade de gerenciar servidores, oferece escalabilidade automática e reduz custos operacionais.
2. Configuração do ambiente de desenvolvimento
Antes de começar, certifique-se de ter os seguintes pré-requisitos instalados:
- Node.js (versão 18 ou superior)
- npm ou pnpm
- Conta gratuita na Cloudflare
- Wrangler CLI (instalado globalmente com npm install -g wrangler)
Para inicializar um novo projeto com Hono, execute:
npm create cloudflare@latest minha-api-hono -- --template hono
cd minha-api-hono
npm install
A estrutura de pastas recomendada para o projeto é:
minha-api-hono/
├── src/
│ ├── routes/
│ │ ├── usuarios.ts
│ │ └── produtos.ts
│ ├── middlewares/
│ │ ├── auth.ts
│ │ └── cors.ts
│ ├── schemas/
│ │ └── validacao.ts
│ ├── utils/
│ │ └── respostas.ts
│ └── index.ts
├── tests/
│ └── api.test.ts
├── wrangler.toml
└── package.json
3. Construção da API com Hono — rotas e middlewares
Vamos criar uma API simples de gerenciamento de usuários. No arquivo src/index.ts, configure o servidor básico:
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { usuariosRouter } from './routes/usuarios'
const app = new Hono()
// Middlewares globais
app.use('*', cors())
app.use('*', logger())
// Rotas
app.route('/api/usuarios', usuariosRouter)
// Health check
app.get('/health', (c) => c.json({ status: 'ok', timestamp: Date.now() }))
export default app
Agora, crie o arquivo src/routes/usuarios.ts com as rotas CRUD:
import { Hono } from 'hono'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const usuariosRouter = new Hono()
// Schema de validação com Zod
const usuarioSchema = z.object({
nome: z.string().min(3).max(100),
email: z.string().email(),
idade: z.number().int().positive().optional()
})
// Banco simulado em memória (substituir por D1 em produção)
let usuarios = [
{ id: 1, nome: 'João Silva', email: 'joao@exemplo.com', idade: 30 }
]
let proximoId = 2
// GET - Listar todos os usuários
usuariosRouter.get('/', (c) => {
return c.json({ dados: usuarios, total: usuarios.length })
})
// GET - Buscar usuário por ID
usuariosRouter.get('/:id', (c) => {
const id = Number(c.req.param('id'))
const usuario = usuarios.find(u => u.id === id)
if (!usuario) {
return c.json({ erro: 'Usuário não encontrado' }, 404)
}
return c.json({ dados: usuario })
})
// POST - Criar novo usuário
usuariosRouter.post('/', zValidator('json', usuarioSchema), async (c) => {
const body = await c.req.json()
const novoUsuario = { id: proximoId++, ...body }
usuarios.push(novoUsuario)
return c.json({ dados: novoUsuario }, 201)
})
// PUT - Atualizar usuário
usuariosRouter.put('/:id', zValidator('json', usuarioSchema), async (c) => {
const id = Number(c.req.param('id'))
const body = await c.req.json()
const index = usuarios.findIndex(u => u.id === id)
if (index === -1) {
return c.json({ erro: 'Usuário não encontrado' }, 404)
}
usuarios[index] = { ...usuarios[index], ...body }
return c.json({ dados: usuarios[index] })
})
// DELETE - Remover usuário
usuariosRouter.delete('/:id', (c) => {
const id = Number(c.req.param('id'))
const index = usuarios.findIndex(u => u.id === id)
if (index === -1) {
return c.json({ erro: 'Usuário não encontrado' }, 404)
}
usuarios.splice(index, 1)
return c.json({ mensagem: 'Usuário removido com sucesso' })
})
export { usuariosRouter }
4. Integração com o ecossistema Cloudflare Workers
Para persistência real, substitua o banco em memória pelo D1 (SQLite serverless). Primeiro, configure o binding no wrangler.toml:
name = "minha-api-hono"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "meu-banco"
database_id = "seu-id-aqui"
Agora, modifique a rota para usar D1:
import { D1Database } from '@cloudflare/workers-types'
// No handler da rota
usuariosRouter.get('/', async (c) => {
const db = c.env.DB as D1Database
const { results } = await db.prepare('SELECT * FROM usuarios').all()
return c.json({ dados: results })
})
Para variáveis de ambiente e secrets, adicione no wrangler.toml:
[vars]
API_VERSION = "v1"
MAX_REQUESTS = "100"
# Secrets (definir via CLI)
# wrangler secret put JWT_SECRET
5. Testes locais e simulação de produção
Execute o servidor localmente com hot reload:
wrangler dev --port 8787
Para testes automatizados com Vitest e Miniflare, instale as dependências:
npm install -D vitest @cloudflare/vitest-pool-workers
Crie o arquivo tests/api.test.ts:
import { describe, it, expect, beforeAll } from 'vitest'
import { createExecutionContext } from 'cloudflare:test'
import app from '../src/index'
describe('API de Usuários', () => {
it('GET /health deve retornar status ok', async () => {
const req = new Request('http://localhost/health')
const env = { DB: null }
const ctx = createExecutionContext()
const res = await app.fetch(req, env, ctx)
const data = await res.json()
expect(res.status).toBe(200)
expect(data.status).toBe('ok')
})
it('POST /api/usuarios deve criar novo usuário', async () => {
const novoUsuario = { nome: 'Maria', email: 'maria@teste.com', idade: 25 }
const req = new Request('http://localhost/api/usuarios', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(novoUsuario)
})
const env = { DB: null }
const ctx = createExecutionContext()
const res = await app.fetch(req, env, ctx)
const data = await res.json()
expect(res.status).toBe(201)
expect(data.dados.nome).toBe('Maria')
})
})
Execute os testes com:
npx vitest run
6. Deploy contínuo e monitoramento
Para deploy manual:
wrangler deploy
Configure um pipeline CI/CD com GitHub Actions. Crie .github/workflows/deploy.yml:
name: Deploy para Cloudflare Workers
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
- name: Deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
No Cloudflare Dashboard, monitore métricas como requisições por segundo, latência média, erros HTTP e uso de CPU. Ative alertas para picos de erro ou lentidão.
7. Boas práticas e otimizações para produção
Implemente rate limiting com Hono:
import { rateLimiter } from 'hono-rate-limiter'
app.use('/api/*', rateLimiter({
windowMs: 60 * 1000, // 1 minuto
max: 100, // máximo de 100 requisições por minuto
message: { erro: 'Muitas requisições. Tente novamente mais tarde.' }
}))
Utilize Workers KV para cache de respostas frequentes:
import { KVNamespace } from '@cloudflare/workers-types'
app.get('/api/produtos/populares', async (c) => {
const cache = c.env.CACHE as KVNamespace
const cacheKey = 'produtos_populares'
const cached = await cache.get(cacheKey)
if (cached) {
return c.json(JSON.parse(cached))
}
const dados = await buscarProdutosPopulares()
await cache.put(cacheKey, JSON.stringify(dados), { expirationTtl: 300 })
return c.json(dados)
})
Para segurança, sempre valide entradas com Zod, sanitize dados antes de persistir e utilize prepared statements para consultas SQL a fim de prevenir injeção.
Referências
- Documentação oficial do Hono — Guia completo do framework com exemplos de rotas, middlewares e integrações
- Cloudflare Workers Documentation — Documentação oficial sobre Workers, D1, KV e melhores práticas
- Wrangler CLI Reference — Referência completa do Wrangler para desenvolvimento e deploy
- Zod Validation Library — Biblioteca de validação de esquemas TypeScript usada com Hono
- Vitest Testing Framework — Framework de testes utilizado com Miniflare para testar Workers localmente
- Cloudflare D1 Database — Documentação do banco SQLite serverless nativo do Cloudflare
- GitHub Actions para Cloudflare Workers — Ação oficial para deploy automatizado via CI/CD