Pular para o conteúdo

Hono

Framework web ultrarrápido para o edge, com suporte a Cloudflare Workers, Deno, Bun, Vercel, AWS Lambda e Node.js sem nenhuma dependência.

ComandoDescrição
npm create hono@latest my-appCriar novo projeto Hono com npm
yarn create hono my-appCriar com Yarn
pnpm create hono my-appCriar com pnpm
bun create hono my-appCriar com Bun
deno init --lib honoCriar com Deno
ComandoDescrição
npm install honoInstalar Hono em projeto existente
yarn add honoInstalar com Yarn
pnpm add honoInstalar com pnpm
bun add honoInstalar com Bun
# Create project with specific runtime template
npm create hono@latest my-app -- --template cloudflare-workers
npm create hono@latest my-app -- --template bun
npm create hono@latest my-app -- --template deno
npm create hono@latest my-app -- --template nodejs
npm create hono@latest my-app -- --template vercel
npm create hono@latest my-app -- --template aws-lambda
npm create hono@latest my-app -- --template cloudflare-pages
npm create hono@latest my-app -- --template fastly
ComandoDescrição
app.get('/path', handler)Lidar com requisições GET
app.post('/path', handler)Lidar com requisições POST
app.put('/path', handler)Lidar com requisições PUT
app.delete('/path', handler)Lidar com requisições DELETE
app.patch('/path', handler)Lidar com requisições PATCH
app.options('/path', handler)Lidar com requisições OPTIONS
app.all('/path', handler)Lidar com todos os métodos HTTP
app.on('PURGE', '/path', handler)Lidar com métodos personalizados
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.text('Hello Hono!')
})

app.get('/json', (c) => {
  return c.json({ message: 'Hello', status: 'ok' })
})

app.get('/html', (c) => {
  return c.html('<h1>Hello Hono!</h1>')
})

export default app
// Path parameters
app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id })
})

// Multiple parameters
app.get('/posts/:postId/comments/:commentId', (c) => {
  const { postId, commentId } = c.req.param()
  return c.json({ postId, commentId })
})

// Optional parameter
app.get('/api/:version?/users', (c) => {
  const version = c.req.param('version') || 'v1'
  return c.json({ version })
})

// Wildcard
app.get('/files/*', (c) => {
  const path = c.req.path
  return c.text(`File path: ${path}`)
})

// Regex-like patterns
app.get('/user/:id{[0-9]+}', (c) => {
  const id = c.req.param('id')
  return c.json({ id: Number(id) })
})
ComandoDescrição
c.req.query('key')Obter parâmetro de query único
c.req.query()Obter todos os parâmetros de query
c.req.queries('tags')Obter array de valores para uma chave
ComandoDescrição
c.req.json()Fazer parse do corpo como JSON
c.req.text()Obter corpo como texto
c.req.formData()Fazer parse de dados de formulário
c.req.blob()Obter corpo como Blob
c.req.arrayBuffer()Obter corpo como ArrayBuffer
c.req.parseBody()Fazer parse automático do corpo por tipo de conteúdo
ComandoDescrição
c.req.header('Content-Type')Obter cabeçalho da requisição
c.req.header()Obter todos os cabeçalhos
c.req.methodObter método HTTP
c.req.urlObter URL completa da requisição
c.req.pathObter caminho da requisição
c.req.rawObter objeto Request bruto
c.req.valid('json')Obter dados validados
app.post('/users', async (c) => {
  const body = await c.req.json()
  const page = c.req.query('page') || '1'
  const auth = c.req.header('Authorization')

  return c.json({
    user: body,
    page: Number(page),
    authenticated: !!auth
  }, 201)
})
ComandoDescrição
c.text('Hello')Resposta em texto simples
c.json({ key: 'value' })Resposta em JSON
c.html('<h1>Hi</h1>')Resposta em HTML
c.redirect('/new-url')Redirecionamento 302
c.redirect('/new-url', 301)Redirecionamento 301 permanente
c.notFound()Resposta 404
c.body(data)Resposta com corpo bruto
c.newResponse(body, status, headers)Resposta personalizada completa
// Set headers
app.get('/api/data', (c) => {
  c.header('X-Custom-Header', 'value')
  c.header('Cache-Control', 'max-age=3600')
  return c.json({ data: 'example' })
})

// Set status code
app.post('/items', async (c) => {
  const item = await c.req.json()
  return c.json(item, 201)
})

// Stream response
app.get('/stream', (c) => {
  return c.streamText(async (stream) => {
    await stream.write('Hello ')
    await stream.sleep(1000)
    await stream.write('World!')
  })
})
ComandoDescrição
import { cors } from 'hono/cors'Middleware CORS
import { logger } from 'hono/logger'Registro de requisições
import { prettyJSON } from 'hono/pretty-json'Formatação bonita de respostas JSON
import { basicAuth } from 'hono/basic-auth'Autenticação básica
import { bearerAuth } from 'hono/bearer-auth'Autenticação por token Bearer
import { jwt } from 'hono/jwt'Autenticação JWT
import { compress } from 'hono/compress'Compressão Gzip/Brotli
import { etag } from 'hono/etag'Cache com ETag
import { secureHeaders } from 'hono/secure-headers'Cabeçalhos de segurança
import { csrf } from 'hono/csrf'Proteção contra CSRF
import { timing } from 'hono/timing'Cabeçalho Server-Timing
import { cache } from 'hono/cache'Controle de cache
import { bodyLimit } from 'hono/body-limit'Limite de tamanho do corpo da requisição
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { secureHeaders } from 'hono/secure-headers'
import { bearerAuth } from 'hono/bearer-auth'

const app = new Hono()

// Global middleware
app.use('*', logger())
app.use('*', secureHeaders())
app.use('*', cors({
  origin: ['https://example.com'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization'],
  maxAge: 86400,
}))

// Route-specific middleware
app.use('/api/*', bearerAuth({ token: 'my-secret-token' }))

// Custom middleware
app.use('*', async (c, next) => {
  const start = Date.now()
  await next()
  const duration = Date.now() - start
  c.header('X-Response-Time', `${duration}ms`)
})
import { jwt, sign } from 'hono/jwt'

const app = new Hono()
const SECRET = 'my-secret-key'

app.use('/api/*', jwt({ secret: SECRET }))

app.post('/login', async (c) => {
  const { email, password } = await c.req.json()
  const token = await sign(
    { email, exp: Math.floor(Date.now() / 1000) + 3600 },
    SECRET
  )
  return c.json({ token })
})

app.get('/api/profile', (c) => {
  const payload = c.get('jwtPayload')
  return c.json({ email: payload.email })
})
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const userSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().positive().optional(),
})

app.post(
  '/users',
  zValidator('json', userSchema),
  (c) => {
    const user = c.req.valid('json')
    return c.json(user, 201)
  }
)

// Validate query parameters
const querySchema = z.object({
  page: z.string().optional().default('1'),
  limit: z.string().optional().default('10'),
})

app.get(
  '/users',
  zValidator('query', querySchema),
  (c) => {
    const { page, limit } = c.req.valid('query')
    return c.json({ page, limit })
  }
)
const app = new Hono()

// Group routes with basePath
const api = new Hono().basePath('/api')
api.get('/users', (c) => c.json({ users: [] }))
api.post('/users', (c) => c.json({ created: true }))
app.route('/', api)

// Versioned APIs
const v1 = new Hono()
v1.get('/health', (c) => c.json({ status: 'ok' }))

const v2 = new Hono()
v2.get('/health', (c) => c.json({ status: 'ok', version: 2 }))

app.route('/api/v1', v1)
app.route('/api/v2', v2)
import { HTTPException } from 'hono/http-exception'

// Custom 404
app.notFound((c) => {
  return c.json({ error: 'Not Found' }, 404)
})

// Global error handler
app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return err.getResponse()
  }
  console.error(err)
  return c.json({ error: 'Internal Server Error' }, 500)
})

// Throw HTTP exceptions
app.get('/admin', (c) => {
  if (!c.req.header('Authorization')) {
    throw new HTTPException(401, { message: 'Unauthorized' })
  }
  return c.json({ admin: true })
})
import { Hono } from 'hono'

type Bindings = {
  MY_KV: KVNamespace
  MY_DB: D1Database
}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/kv/:key', async (c) => {
  const value = await c.env.MY_KV.get(c.req.param('key'))
  return c.json({ value })
})

export default app
import { Hono } from 'hono'
import { serve } from '@hono/node-server'

const app = new Hono()
app.get('/', (c) => c.text('Hello Node.js!'))

serve({ fetch: app.fetch, port: 3000 })
import { Hono } from 'hono'

const app = new Hono()
app.get('/', (c) => c.text('Hello Bun!'))

export default { port: 3000, fetch: app.fetch }
import { Hono } from 'npm:hono'

const app = new Hono()
app.get('/', (c) => c.text('Hello Deno!'))

Deno.serve(app.fetch)
// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()
  .get('/api/users', (c) => {
    return c.json({ users: [{ id: 1, name: 'Alice' }] })
  })
  .post(
    '/api/users',
    zValidator('json', z.object({ name: z.string() })),
    (c) => {
      const { name } = c.req.valid('json')
      return c.json({ id: 2, name }, 201)
    }
  )

export type AppType = typeof app
export default app
// client.ts — fully typed
import { hc } from 'hono/client'
import type { AppType } from './server'

const client = hc<AppType>('http://localhost:3000')

// Fully typed — autocomplete for routes + responses
const res = await client.api.users.$get()
const data = await res.json()
// data: { users: { id: number, name: string }[] }
import { testClient } from 'hono/testing'

const app = new Hono()
  .get('/hello', (c) => c.json({ message: 'Hello!' }))

// Using testClient (type-safe)
const client = testClient(app)
const res = await client.hello.$get()
const body = await res.json()
// body.message === 'Hello!'

// Using app.request() directly
const res2 = await app.request('/hello')
// res2.status === 200
  1. Use generics TypeScript para bindings de ambiente — Defina Hono<{ Bindings: MyBindings }> para obter tipagem segura para variáveis de ambiente, namespaces KV e bancos de dados D1.

  2. Valide todas as entradas com Zod — Use @hono/zod-validator para validação de corpo da requisição, query e parâmetros. Isso proporciona segurança em tempo de execução e tipos TypeScript em um único passo.

  3. Use o cliente RPC para frontend-backend — Exporte AppType do seu servidor e use hc<AppType>() no cliente para tipagem segura de ponta a ponta sem geração de código.

  4. Aplique middleware com padrões de caminho — Use app.use('/api/*', middleware) para limitar o escopo do middleware a rotas específicas em vez de aplicar tudo globalmente.

  5. Agrupe rotas com new Hono().basePath() — Organize endpoints relacionados em instâncias Hono separadas e monte-as com app.route() para separação limpa.

  6. Trate erros globalmente — Use app.onError() e app.notFound() para garantir que todo erro retorne uma resposta JSON estruturada em vez de stack traces.

  7. Use c.executionCtx.waitUntil() — Para trabalho em segundo plano no Cloudflare Workers (analytics, logging), use waitUntil() para evitar bloquear a resposta.

  8. Mantenha os handlers enxutos — Extraia a lógica de negócio para funções/módulos separados. Os handlers de rota devem fazer parse da entrada, chamar a lógica e retornar respostas.

  9. Teste com testClient — O cliente de teste integrado do Hono tem tipagem segura e não precisa de um servidor em execução, tornando os testes unitários rápidos e confiáveis.

  10. Implante onde seus usuários estão — O Hono executa em todos os principais runtimes de edge. Escolha Cloudflare Workers para edge global, Bun para velocidade pura ou Node.js para compatibilidade com o ecossistema.