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.

Installation

Neues Projekt erstellen

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

Zu bestehendem Projekt hinzufügen

BefehlBeschreibung
npm install honoHono in bestehendem Projekt installieren
yarn add honoMit Yarn installieren
pnpm add honoMit pnpm installieren
bun add honoMit Bun installieren

Starter-Vorlagen

# 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

Grundlegendes Routing

HTTP-Methoden

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

Hello World

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

Routenparameter

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

Anfrageverarbeitung

Query-Parameter

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

Anfrage-Body

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

Header & Metadaten

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

Anfrage-Beispiel

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

Antwort-Helfer

Antworttypen

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

Antwort-Header & Streaming

// 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!')
  })
})

Middleware

Eingebaute Middleware

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

Middleware-Verwendung

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`)
})

JWT-Authentifizierung

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

Validierung

Zod-Validator

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

Routengruppen

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)

Fehlerbehandlung

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

Laufzeitspezifische Einrichtung

Cloudflare Workers

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

Node.js

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

Bun

import { Hono } from 'hono'

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

export default { port: 3000, fetch: app.fetch }

Deno

import { Hono } from 'npm:hono'

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

Deno.serve(app.fetch)

RPC & Client

Typsicherer RPC-Client

// 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 }[] }

Testen

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

Best Practices

  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.