Salta ai contenuti

Hono

Framework web ultraveloce per l’edge, che supporta Cloudflare Workers, Deno, Bun, Vercel, AWS Lambda e Node.js senza dipendenze.

ComandoDescrizione
npm create hono@latest my-appCrea un nuovo progetto Hono con npm
yarn create hono my-appCrea con Yarn
pnpm create hono my-appCrea con pnpm
bun create hono my-appCrea con Bun
deno init --lib honoCrea con Deno
ComandoDescrizione
npm install honoInstalla Hono in un progetto esistente
yarn add honoInstalla con Yarn
pnpm add honoInstalla con pnpm
bun add honoInstalla con Bun
# Crea progetto con template per runtime specifico
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
ComandoDescrizione
app.get('/path', handler)Gestisci richieste GET
app.post('/path', handler)Gestisci richieste POST
app.put('/path', handler)Gestisci richieste PUT
app.delete('/path', handler)Gestisci richieste DELETE
app.patch('/path', handler)Gestisci richieste PATCH
app.options('/path', handler)Gestisci richieste OPTIONS
app.all('/path', handler)Gestisci tutti i metodi HTTP
app.on('PURGE', '/path', handler)Gestisci metodi personalizzati
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
// Parametri di percorso
app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id })
})

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

// Parametro opzionale
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}`)
})

// Pattern tipo regex
app.get('/user/:id{[0-9]+}', (c) => {
  const id = c.req.param('id')
  return c.json({ id: Number(id) })
})
ComandoDescrizione
c.req.query('key')Ottieni un singolo parametro di query
c.req.query()Ottieni tutti i parametri di query
c.req.queries('tags')Ottieni array di valori per una chiave
ComandoDescrizione
c.req.json()Analizza il corpo JSON
c.req.text()Ottieni il corpo come testo
c.req.formData()Analizza i dati del form
c.req.blob()Ottieni il corpo come Blob
c.req.arrayBuffer()Ottieni il corpo come ArrayBuffer
c.req.parseBody()Analizza automaticamente il corpo in base al content type
ComandoDescrizione
c.req.header('Content-Type')Ottieni header della richiesta
c.req.header()Ottieni tutti gli header
c.req.methodOttieni il metodo HTTP
c.req.urlOttieni l’URL completo della richiesta
c.req.pathOttieni il percorso della richiesta
c.req.rawOttieni l’oggetto Request grezzo
c.req.valid('json')Ottieni i dati validati
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)
})
ComandoDescrizione
c.text('Hello')Risposta in testo semplice
c.json({ key: 'value' })Risposta JSON
c.html('<h1>Hi</h1>')Risposta HTML
c.redirect('/new-url')Redirect 302
c.redirect('/new-url', 301)Redirect 301 permanente
c.notFound()Risposta 404
c.body(data)Risposta con corpo grezzo
c.newResponse(body, status, headers)Risposta personalizzata completa
// Imposta header
app.get('/api/data', (c) => {
  c.header('X-Custom-Header', 'value')
  c.header('Cache-Control', 'max-age=3600')
  return c.json({ data: 'example' })
})

// Imposta codice di stato
app.post('/items', async (c) => {
  const item = await c.req.json()
  return c.json(item, 201)
})

// Risposta in streaming
app.get('/stream', (c) => {
  return c.streamText(async (stream) => {
    await stream.write('Hello ')
    await stream.sleep(1000)
    await stream.write('World!')
  })
})
ComandoDescrizione
import { cors } from 'hono/cors'Middleware CORS
import { logger } from 'hono/logger'Logging delle richieste
import { prettyJSON } from 'hono/pretty-json'Formattazione delle risposte JSON
import { basicAuth } from 'hono/basic-auth'Autenticazione Basic
import { bearerAuth } from 'hono/bearer-auth'Autenticazione Bearer token
import { jwt } from 'hono/jwt'Autenticazione JWT
import { compress } from 'hono/compress'Compressione Gzip/Brotli
import { etag } from 'hono/etag'Caching ETag
import { secureHeaders } from 'hono/secure-headers'Header di sicurezza
import { csrf } from 'hono/csrf'Protezione CSRF
import { timing } from 'hono/timing'Header Server-Timing
import { cache } from 'hono/cache'Controllo della cache
import { bodyLimit } from 'hono/body-limit'Limite dimensione corpo richiesta
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 globali
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 specifico per route
app.use('/api/*', bearerAuth({ token: 'my-secret-token' }))

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

// Valida parametri di query
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()

// Raggruppa route 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)

// API versionate
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 personalizzato
app.notFound((c) => {
  return c.json({ error: 'Not Found' }, 404)
})

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

// Lancia eccezioni 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 tipizzato
import { hc } from 'hono/client'
import type { AppType } from './server'

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

// Completamente tipizzato — autocompletamento per route + risposte
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 (type-safe)
const client = testClient(app)
const res = await client.hello.$get()
const body = await res.json()
// body.message === 'Hello!'

// Usando app.request() direttamente
const res2 = await app.request('/hello')
// res2.status === 200
  1. Usa i generics TypeScript per i binding dell’ambiente — Definisci Hono<{ Bindings: MyBindings }> per ottenere la sicurezza dei tipi per variabili d’ambiente, namespace KV e database D1.

  2. Valida tutti gli input con Zod — Usa @hono/zod-validator per la validazione di corpo, query e parametri della richiesta. Questo fornisce sicurezza a runtime e tipi TypeScript in un unico passaggio.

  3. Usa il client RPC per frontend-backend — Esporta AppType dal tuo server e usa hc<AppType>() sul client per la sicurezza dei tipi end-to-end senza generazione di codice.

  4. Applica i middleware con pattern di percorso — Usa app.use('/api/*', middleware) per limitare il middleware a route specifiche invece di applicare tutto globalmente.

  5. Raggruppa le route con new Hono().basePath() — Organizza gli endpoint correlati in istanze Hono separate e montale con app.route() per una separazione pulita.

  6. Gestisci gli errori globalmente — Usa app.onError() e app.notFound() per assicurarti che ogni errore restituisca una risposta JSON strutturata invece di stack trace.

  7. Usa c.executionCtx.waitUntil() — Per il lavoro in background su Cloudflare Workers (analytics, logging), usa waitUntil() per evitare di bloccare la risposta.

  8. Mantieni gli handler snelli — Estrai la logica di business in funzioni/moduli separati. Gli handler delle route dovrebbero analizzare l’input, chiamare la logica e restituire le risposte.

  9. Testa con testClient — Il client di test integrato di Hono e type-safe e non necessita di un server in esecuzione, rendendo i test unitari veloci e affidabili.

  10. Distribuisci dove si trovano i tuoi utenti — Hono funziona su ogni principale runtime edge. Scegli Cloudflare Workers per l’edge globale, Bun per la velocita pura o Node.js per la compatibilita dell’ecosistema.