Zum Inhalt springen

Hono

Ultraschnelles Web-Framework für die Edge, unterstützt Cloudflare Workers, Deno, Bun, Vercel, AWS Lambda und Node.js ohne Abhängigkeiten.

BefehlBeschreibung
npm create hono@latest my-appNeues Hono-Projekt mit npm erstellen
yarn create hono my-appMit Yarn erstellen
pnpm create hono my-appMit pnpm erstellen
bun create hono my-appMit Bun erstellen
deno init --lib honoMit Deno erstellen
BefehlBeschreibung
npm install honoHono in bestehendem Projekt installieren
yarn add honoMit Yarn installieren
pnpm add honoMit pnpm installieren
bun add honoMit Bun installieren
# Projekt mit spezifischer Laufzeit-Vorlage erstellen
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
BefehlBeschreibung
app.get('/path', handler)GET-Anfragen verarbeiten
app.post('/path', handler)POST-Anfragen verarbeiten
app.put('/path', handler)PUT-Anfragen verarbeiten
app.delete('/path', handler)DELETE-Anfragen verarbeiten
app.patch('/path', handler)PATCH-Anfragen verarbeiten
app.options('/path', handler)OPTIONS-Anfragen verarbeiten
app.all('/path', handler)Alle HTTP-Methoden verarbeiten
app.on('PURGE', '/path', handler)Benutzerdefinierte Methoden verarbeiten
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
// Pfadparameter
app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id })
})

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

// Optionaler 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-ähnliche Muster
app.get('/user/:id{[0-9]+}', (c) => {
  const id = c.req.param('id')
  return c.json({ id: Number(id) })
})
BefehlBeschreibung
c.req.query('key')Einzelnen Query-Parameter abrufen
c.req.query()Alle Query-Parameter abrufen
c.req.queries('tags')Array von Werten für einen Schlüssel abrufen
BefehlBeschreibung
c.req.json()JSON-Body parsen
c.req.text()Body als Text abrufen
c.req.formData()Formulardaten parsen
c.req.blob()Body als Blob abrufen
c.req.arrayBuffer()Body als ArrayBuffer abrufen
c.req.parseBody()Body automatisch nach Content-Type parsen
BefehlBeschreibung
c.req.header('Content-Type')Anfrage-Header abrufen
c.req.header()Alle Header abrufen
c.req.methodHTTP-Methode abrufen
c.req.urlVollständige Anfrage-URL abrufen
c.req.pathAnfragepfad abrufen
c.req.rawRohes Request-Objekt abrufen
c.req.valid('json')Validierte Daten abrufen
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)
})
BefehlBeschreibung
c.text('Hello')Klartext-Antwort
c.json({ key: 'value' })JSON-Antwort
c.html('<h1>Hi</h1>')HTML-Antwort
c.redirect('/new-url')302-Weiterleitung
c.redirect('/new-url', 301)301 permanente Weiterleitung
c.notFound()404-Antwort
c.body(data)Roh-Body-Antwort
c.newResponse(body, status, headers)Vollständig benutzerdefinierte Antwort
// Header setzen
app.get('/api/data', (c) => {
  c.header('X-Custom-Header', 'value')
  c.header('Cache-Control', 'max-age=3600')
  return c.json({ data: 'example' })
})

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

// Antwort streamen
app.get('/stream', (c) => {
  return c.streamText(async (stream) => {
    await stream.write('Hello ')
    await stream.sleep(1000)
    await stream.write('World!')
  })
})
BefehlBeschreibung
import { cors } from 'hono/cors'CORS-Middleware
import { logger } from 'hono/logger'Anfrage-Protokollierung
import { prettyJSON } from 'hono/pretty-json'JSON-Antworten formatiert ausgeben
import { basicAuth } from 'hono/basic-auth'Basic-Authentifizierung
import { bearerAuth } from 'hono/bearer-auth'Bearer-Token-Authentifizierung
import { jwt } from 'hono/jwt'JWT-Authentifizierung
import { compress } from 'hono/compress'Gzip/Brotli-Komprimierung
import { etag } from 'hono/etag'ETag-Caching
import { secureHeaders } from 'hono/secure-headers'Sicherheits-Header
import { csrf } from 'hono/csrf'CSRF-Schutz
import { timing } from 'hono/timing'Server-Timing-Header
import { cache } from 'hono/cache'Cache-Steuerung
import { bodyLimit } from 'hono/body-limit'Anfrage-Body-Größenlimit
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()

// Globale 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,
}))

// Routenspezifische Middleware
app.use('/api/*', bearerAuth({ token: 'my-secret-token' }))

// Benutzerdefinierte 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)
  }
)

// Query-Parameter validieren
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()

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

// Versionierte 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'

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

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

// HTTP-Ausnahmen werfen
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 — vollständig typisiert
import { hc } from 'hono/client'
import type { AppType } from './server'

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

// Vollständig typisiert — Autovervollständigung für Routen + Antworten
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!' }))

// Mit testClient (typsicher)
const client = testClient(app)
const res = await client.hello.$get()
const body = await res.json()
// body.message === 'Hello!'

// Direkt mit app.request()
const res2 = await app.request('/hello')
// res2.status === 200
  1. TypeScript-Generics für Env-Bindings verwendenHono<{ Bindings: MyBindings }> definieren, um Typsicherheit für Umgebungsvariablen, KV-Namespaces und D1-Datenbanken zu erhalten.

  2. Alle Eingaben mit Zod validieren@hono/zod-validator für Request-Body-, Query- und Param-Validierung verwenden. Dies bietet Laufzeitsicherheit und TypeScript-Typen in einem Schritt.

  3. Den RPC-Client für Frontend-Backend verwendenAppType vom Server exportieren und hc<AppType>() auf dem Client für End-to-End-Typsicherheit ohne Code-Generierung verwenden.

  4. Middleware mit Pfadmustern anwendenapp.use('/api/*', middleware) verwenden, um Middleware auf bestimmte Routen zu beschränken, statt alles global anzuwenden.

  5. Routen mit new Hono().basePath() gruppieren — Zusammengehörige Endpunkte in separate Hono-Instanzen organisieren und mit app.route() einbinden für saubere Trennung.

  6. Fehler global behandelnapp.onError() und app.notFound() verwenden, um sicherzustellen, dass jeder Fehler eine strukturierte JSON-Antwort zurückgibt statt Stack-Traces.

  7. c.executionCtx.waitUntil() verwenden — Für Hintergrundarbeit auf Cloudflare Workers (Analytics, Logging) waitUntil() verwenden, um die Antwort nicht zu blockieren.

  8. Handler schlank halten — Geschäftslogik in separate Funktionen/Module auslagern. Route-Handler sollten Eingaben parsen, Logik aufrufen und Antworten zurückgeben.

  9. Mit testClient testen — Honos eingebauter Test-Client ist typsicher und benötigt keinen laufenden Server, was Unit-Tests schnell und zuverlässig macht.

  10. Dort deployen, wo Ihre Nutzer sind — Hono läuft auf jeder großen Edge-Runtime. Cloudflare Workers für globale Edge wählen, Bun für rohe Geschwindigkeit oder Node.js für Ökosystem-Kompatibilität.