Ir al contenido

Hono

Framework web ultrarrápido para el edge, compatible con Cloudflare Workers, Deno, Bun, Vercel, AWS Lambda y Node.js sin dependencias.

ComandoDescripción
npm create hono@latest my-appCrear nuevo proyecto Hono con npm
yarn create hono my-appCrear con Yarn
pnpm create hono my-appCrear con pnpm
bun create hono my-appCrear con Bun
deno init --lib honoCrear con Deno
ComandoDescripción
npm install honoInstalar Hono en proyecto existente
yarn add honoInstalar con Yarn
pnpm add honoInstalar con pnpm
bun add honoInstalar con Bun
# Crear proyecto con plantilla de runtime específico
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
ComandoDescripción
app.get('/path', handler)Manejar solicitudes GET
app.post('/path', handler)Manejar solicitudes POST
app.put('/path', handler)Manejar solicitudes PUT
app.delete('/path', handler)Manejar solicitudes DELETE
app.patch('/path', handler)Manejar solicitudes PATCH
app.options('/path', handler)Manejar solicitudes OPTIONS
app.all('/path', handler)Manejar todos los métodos HTTP
app.on('PURGE', '/path', handler)Manejar 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
// Parámetros de ruta
app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id })
})

// Múltiples parámetros
app.get('/posts/:postId/comments/:commentId', (c) => {
  const { postId, commentId } = c.req.param()
  return c.json({ postId, commentId })
})

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

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

// Patrones tipo regex
app.get('/user/:id{[0-9]+}', (c) => {
  const id = c.req.param('id')
  return c.json({ id: Number(id) })
})
ComandoDescripción
c.req.query('key')Obtener parámetro de consulta individual
c.req.query()Obtener todos los parámetros de consulta
c.req.queries('tags')Obtener array de valores para una clave
ComandoDescripción
c.req.json()Parsear cuerpo JSON
c.req.text()Obtener cuerpo como texto
c.req.formData()Parsear datos de formulario
c.req.blob()Obtener cuerpo como Blob
c.req.arrayBuffer()Obtener cuerpo como ArrayBuffer
c.req.parseBody()Parsear automáticamente cuerpo por tipo de contenido
ComandoDescripción
c.req.header('Content-Type')Obtener cabecera de solicitud
c.req.header()Obtener todas las cabeceras
c.req.methodObtener método HTTP
c.req.urlObtener URL completa de la solicitud
c.req.pathObtener ruta de la solicitud
c.req.rawObtener objeto Request sin procesar
c.req.valid('json')Obtener datos 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)
})
ComandoDescripción
c.text('Hello')Respuesta de texto plano
c.json({ key: 'value' })Respuesta JSON
c.html('<h1>Hi</h1>')Respuesta HTML
c.redirect('/new-url')Redirección 302
c.redirect('/new-url', 301)Redirección 301 permanente
c.notFound()Respuesta 404
c.body(data)Respuesta con cuerpo sin procesar
c.newResponse(body, status, headers)Respuesta personalizada completa
// Establecer cabeceras
app.get('/api/data', (c) => {
  c.header('X-Custom-Header', 'value')
  c.header('Cache-Control', 'max-age=3600')
  return c.json({ data: 'example' })
})

// Establecer código de estado
app.post('/items', async (c) => {
  const item = await c.req.json()
  return c.json(item, 201)
})

// Respuesta en streaming
app.get('/stream', (c) => {
  return c.streamText(async (stream) => {
    await stream.write('Hello ')
    await stream.sleep(1000)
    await stream.write('World!')
  })
})
ComandoDescripción
import { cors } from 'hono/cors'Middleware CORS
import { logger } from 'hono/logger'Registro de solicitudes
import { prettyJSON } from 'hono/pretty-json'Formato legible de respuestas JSON
import { basicAuth } from 'hono/basic-auth'Autenticación básica
import { bearerAuth } from 'hono/bearer-auth'Autenticación por token Bearer
import { jwt } from 'hono/jwt'Autenticación JWT
import { compress } from 'hono/compress'Compresión Gzip/Brotli
import { etag } from 'hono/etag'Caché ETag
import { secureHeaders } from 'hono/secure-headers'Cabeceras de seguridad
import { csrf } from 'hono/csrf'Protección CSRF
import { timing } from 'hono/timing'Cabecera Server-Timing
import { cache } from 'hono/cache'Control de caché
import { bodyLimit } from 'hono/body-limit'Límite de tamaño del cuerpo de solicitud
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()

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

// Middleware específico de ruta
app.use('/api/*', bearerAuth({ token: 'my-secret-token' }))

// Middleware personalizado
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)
  }
)

// Validar parámetros de consulta
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()

// Agrupar rutas con 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)

// APIs versionadas
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'

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

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

// Lanzar excepciones HTTP
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 — completamente tipado
import { hc } from 'hono/client'
import type { AppType } from './server'

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

// Completamente tipado — autocompletado para rutas + respuestas
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!' }))

// Usando testClient (con seguridad de tipos)
const client = testClient(app)
const res = await client.hello.$get()
const body = await res.json()
// body.message === 'Hello!'

// Usando app.request() directamente
const res2 = await app.request('/hello')
// res2.status === 200
  1. Usa genéricos de TypeScript para bindings del entorno — Define Hono<{ Bindings: MyBindings }> para obtener seguridad de tipos para variables de entorno, namespaces KV y bases de datos D1.

  2. Valida todas las entradas con Zod — Usa @hono/zod-validator para la validación del cuerpo de solicitud, consultas y parámetros. Esto proporciona seguridad en tiempo de ejecución y tipos de TypeScript en un solo paso.

  3. Usa el cliente RPC para frontend-backend — Exporta AppType desde tu servidor y usa hc<AppType>() en el cliente para seguridad de tipos de extremo a extremo sin generación de código.

  4. Aplica middleware con patrones de ruta — Usa app.use('/api/*', middleware) para limitar el alcance del middleware a rutas específicas en lugar de aplicar todo globalmente.

  5. Agrupa rutas con new Hono().basePath() — Organiza endpoints relacionados en instancias separadas de Hono y móntalas con app.route() para una separación limpia.

  6. Maneja errores globalmente — Usa app.onError() y app.notFound() para asegurar que cada error devuelva una respuesta JSON estructurada en lugar de trazas de pila.

  7. Usa c.executionCtx.waitUntil() — Para trabajo en segundo plano en Cloudflare Workers (analíticas, registro), usa waitUntil() para evitar bloquear la respuesta.

  8. Mantén los handlers ligeros — Extrae la lógica de negocio en funciones/módulos separados. Los handlers de ruta deben parsear la entrada, llamar a la lógica y devolver respuestas.

  9. Prueba con testClient — El cliente de pruebas integrado de Hono tiene seguridad de tipos y no necesita un servidor en ejecución, haciendo las pruebas unitarias rápidas y confiables.

  10. Despliega donde están tus usuarios — Hono se ejecuta en todos los principales runtimes edge. Elige Cloudflare Workers para edge global, Bun para velocidad pura o Node.js para compatibilidad con el ecosistema.