API & Framework

Hono.js mit Claude Code:
Ultraschnelle Web-APIs 2026

Routing, Middleware, Zod-Validation, JWT Auth, RPC-Mode und Cloudflare Workers — Claude Code als Hono-Experte fuer das schnellste Edge-API-Framework der Welt.

📅 5. Mai 2026 ⏱ 10 min Lesezeit 🏷 API & Framework ✍ agentic-movers Team

Hono.js ist das schnellste Web-Framework fuer JavaScript-Edge-Runtimes. Es laeuft auf Cloudflare Workers, Deno, Bun, Node.js und AWS Lambda — ohne Kompromisse bei Performance oder Typsicherheit. Mit Claude Code als intelligenten Pair-Programmer wird API-Entwicklung nicht nur schneller, sondern auch deutlich robuster.

In diesem Tutorial zeigen wir dir, wie Claude Code dich bei jedem Schritt des Hono-Workflows unterstuetzt: vom initialen Setup ueber Middleware-Schichtung, Zod-basierte Validierung und JWT-Authentication bis zum typsicheren RPC-Client und dem finalen Cloudflare-Workers-Deployment.

<1ms Cold Start auf CF Workers
140k+ req/s auf Bun
6 Runtimes unterstuetzt
100% TypeScript-nativ
Claude Code Vorteil Claude Code kennt die Hono-API vollstaendig — inkl. RPC-Types, Zod-Integration und Wrangler-Config. Einfach natuerlichsprachlich beschreiben, Claude generiert produktionsreifen Code.

Setup 1. Hono Setup & Routing

app.get app.post app.put app.delete Chained Routes

Hono installiert sich in Sekunden. Der Einstieg ist minimal — ein new Hono() reicht, um eine vollstaendige API zu starten. Claude Code erledigt das Setup auf Wunsch vollautomatisch, inkl. TypeScript-Konfiguration und Bun/Node-Adapter.

Installation & Projekt-Init

terminal
# Neues Hono-Projekt mit Bun
bun create hono@latest my-api
cd my-api
bun install

# Alternativ mit npm
npm create hono@latest my-api
cd my-api
npm install

Grundstruktur: new Hono()

src/index.ts
import { Hono } from 'hono'

// Typisierte App-Instanz mit Environment-Bindings
type Env = {
  Bindings: {
    DB: D1Database
    KV: KVNamespace
    JWT_SECRET: string
  }
}

const app = new Hono<Env>()

// GET-Route mit URL-Parameter
app.get('/users/:id', async (c) => {
  const id = c.req.param('id')
  return c.json({ id, name: 'Alice' })
})

// POST-Route
app.post('/users', async (c) => {
  const body = await c.req.json()
  return c.json({ created: true, data: body }, 201)
})

// PUT-Route
app.put('/users/:id', async (c) => {
  const id = c.req.param('id')
  const body = await c.req.json()
  return c.json({ updated: true, id, data: body })
})

// DELETE-Route
app.delete('/users/:id', async (c) => {
  const id = c.req.param('id')
  return c.json({ deleted: true, id })
})

export default app

Chained Routes & Route-Gruppen

src/routes/products.ts
import { Hono } from 'hono'

const products = new Hono()

// Chained Routes: .get().post().put().delete() an einem Pfad
products
  .get('/', async (c) => {
    return c.json({ products: [] })
  })
  .post('/', async (c) => {
    const body = await c.req.json()
    return c.json({ created: body }, 201)
  })
  .get('/:id', async (c) => {
    return c.json({ id: c.req.param('id') })
  })
  .put('/:id', async (c) => {
    return c.json({ updated: true })
  })
  .delete('/:id', async (c) => {
    return c.json({ deleted: true })
  })

export default products

// In src/index.ts einbinden:
// app.route('/products', products)
Claude Code Prompt "Erstelle eine Hono-API mit CRUD-Routen fuer /products und /users, typisiert mit TypeScript und mit separaten Route-Dateien pro Resource."

Query-Parameter & Headers lesen

src/index.ts
// Query-Parameter
app.get('/search', (c) => {
  const q = c.req.query('q') ?? ''
  const page = parseInt(c.req.query('page') ?? '1')
  const limit = parseInt(c.req.query('limit') ?? '20')
  return c.json({ q, page, limit, results: [] })
})

// Headers lesen
app.get('/me', (c) => {
  const userAgent = c.req.header('user-agent')
  const accept = c.req.header('accept')
  return c.json({ userAgent, accept })
})

MW 2. Middleware: CORS, Logger, Auth & Custom

cors logger bearerAuth custom app.use()

Hono liefert alle wichtigen Middlewares out-of-the-box. Durch das Middleware-System werden Querschnittsbelange sauber voneinander getrennt. Claude Code generiert auf Anfrage komplette Middleware-Stacks mit korrekter Reihenfolge und Typisierung.

CORS & Logger

src/index.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { prettyJSON } from 'hono/pretty-json'
import { secureHeaders } from 'hono/secure-headers'
import { timing } from 'hono/timing'

const app = new Hono()

// Logger fuer alle Requests
app.use('*', logger())

// CORS mit detaillierter Konfiguration
app.use('*', cors({
  origin: ['https://agentic-movers.com', 'http://localhost:3000'],
  allowHeaders: ['Content-Type', 'Authorization'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  exposeHeaders: ['X-Request-Id'],
  maxAge: 86400,
  credentials: true,
}))

// Security Headers (HSTS, CSP, etc.)
app.use('*', secureHeaders())

// Server-Timing fuer Performance-Monitoring
app.use('*', timing())

// Pretty JSON in Development
if (process.env.NODE_ENV !== 'production') {
  app.use('*', prettyJSON())
}

Bearer Auth Middleware

src/middleware/auth.ts
import { bearerAuth } from 'hono/bearer-auth'

// Einfacher Bearer-Auth fuer API-Keys
const apiAuth = bearerAuth({
  token: process.env.API_SECRET!,
})

// Nur bestimmte Routen schuetzen
app.use('/admin/*', apiAuth)
app.use('/api/v1/*', apiAuth)

// Mehrere valide Tokens
const multiAuth = bearerAuth({
  token: ['token-alice', 'token-bob', process.env.ADMIN_TOKEN!],
})

// Dynamische Token-Validierung (z.B. aus DB)
const dynamicAuth = bearerAuth({
  verifyToken: async (token, c) => {
    const db = c.env.DB
    const result = await db
      .prepare('SELECT id FROM api_keys WHERE token = ?')
      .bind(token)
      .first()
    return result !== null
  },
})

Custom Middleware schreiben

src/middleware/request-id.ts
import { createMiddleware } from 'hono/factory'

// Request-ID Middleware
export const requestId = createMiddleware(async (c, next) => {
  const id = crypto.randomUUID()
  c.set('requestId', id)
  c.res.headers.set('X-Request-Id', id)
  await next()
})

// Rate-Limiter Middleware (einfach, fuer CF KV)
export const rateLimit = (maxReqs: number, windowSec: number) =>
  createMiddleware(async (c, next) => {
    const ip = c.req.header('CF-Connecting-IP') ?? 'unknown'
    const key = `rate:${ip}`
    const current = parseInt(await c.env.KV.get(key) ?? '0')
    if (current >= maxReqs) {
      return c.json({ error: 'Too Many Requests' }, 429)
    }
    await c.env.KV.put(key, String(current + 1), {
      expirationTtl: windowSec
    })
    await next()
  })

// Einbinden:
// app.use('*', requestId)
// app.use('/api/*', rateLimit(100, 60))
Reihenfolge beachten Middlewares werden in der Registrierungsreihenfolge ausgefuehrt. Logger immer zuerst, Auth vor Business-Logik, Error-Handler am Ende. Claude Code hilft dir, die optimale Reihenfolge fuer deinen Stack zu ermitteln.

Zod 3. Zod Validator: Typsichere Request-Validierung

zValidator body query params header

Mit dem @hono/zod-validator Paket validierst du alle Request-Teile typsicher. Der Compiler weiss exakt, welche Felder im Handler verfuegbar sind — kein manuelles Type-Casting, keine Runtime-Surprises.

Installation

terminal
bun add @hono/zod-validator zod
# oder
npm install @hono/zod-validator zod

Body-Validierung

src/routes/users.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

// Schema-Definitionen
const createUserSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  age: z.number().int().min(18).max(120),
  role: z.enum(['admin', 'user', 'viewer']).default('user'),
  metadata: z.record(z.unknown()).optional(),
})

const updateUserSchema = createUserSchema.partial()

type CreateUser = z.infer<typeof createUserSchema>

const users = new Hono()

// POST mit Body-Validierung
users.post('/', zValidator('json', createUserSchema), async (c) => {
  // Vollstaendig typisiert, kein Type-Assertion noetig
  const { name, email, age, role } = c.req.valid('json')
  return c.json({ created: true, name, email, age, role }, 201)
})

// PUT mit Partial-Schema
users.put('/:id', zValidator('json', updateUserSchema), async (c) => {
  const id = c.req.param('id')
  const data = c.req.valid('json')
  return c.json({ updated: true, id, data })
})

Query- & Params-Validierung

src/routes/products.ts
// Query-Parameter validieren
const searchSchema = z.object({
  q: z.string().min(1),
  page: z.coerce.number().int().positive().default(1),
  limit: z.coerce.number().int().min(1).max(100).default(20),
  category: z.enum(['all', 'ai', 'tools', 'news']).default('all'),
})

app.get(
  '/search',
  zValidator('query', searchSchema),
  async (c) => {
    const { q, page, limit, category } = c.req.valid('query')
    return c.json({ q, page, limit, category, results: [] })
  }
)

// URL-Parameter validieren
const paramsSchema = z.object({
  id: z.string().uuid('Muss eine gueltige UUID sein'),
})

app.get(
  '/products/:id',
  zValidator('param', paramsSchema),
  async (c) => {
    const { id } = c.req.valid('param')
    return c.json({ id, product: null })
  }
)

Custom Error-Handling fuer Validierungsfehler

src/routes/users.ts
// Eigene Fehlerbehandlung bei Validierungsfehlern
users.post(
  '/register',
  zValidator('json', createUserSchema, (result, c) => {
    if (!result.success) {
      return c.json({
        error: 'Validation failed',
        issues: result.error.issues.map((i) => ({
          field: i.path.join('.'),
          message: i.message,
          code: i.code,
        })),
      }, 422)
    }
  }),
  async (c) => {
    const user = c.req.valid('json')
    // user ist hier vollstaendig typisiert
    return c.json({ success: true, user }, 201)
  }
)
Claude Code Tipp "Generiere Zod-Schemas fuer meine User- und Product-Entities basierend auf diesem TypeScript-Interface und fuege zValidator-Middleware fuer alle CRUD-Routen hinzu, inklusive Custom-Error-Formatting."

JWT 4. JWT Authentication: Sign, Verify & Protected Routes

jwt Middleware sign() verify() getPayload()

Hono liefert eine eingebaute JWT-Integration, die auf Web-Crypto-APIs basiert und dadurch in allen Edge-Runtimes ohne Node.js-Abhaengigkeiten laeuft. Mit Claude Code implementierst du vollstaendige Auth-Flows in wenigen Prompts.

Login-Route: Token generieren

src/routes/auth.ts
import { Hono } from 'hono'
import { sign, verify } from 'hono/jwt'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

const auth = new Hono()

// Login: JWT generieren
auth.post('/login', zValidator('json', loginSchema), async (c) => {
  const { email, password } = c.req.valid('json')
  const secret = c.env.JWT_SECRET

  // Passwort pruefen (hier vereinfacht)
  const user = await findUser(email, password)
  if (!user) {
    return c.json({ error: 'Ungueltige Anmeldedaten' }, 401)
  }

  // JWT mit Payload signieren
  const payload = {
    sub: user.id,
    email: user.email,
    role: user.role,
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24, // 24h
  }

  const token = await sign(payload, secret, 'HS256')

  return c.json({
    access_token: token,
    token_type: 'Bearer',
    expires_in: 86400,
  })
})

// Refresh-Token Logik
auth.post('/refresh', async (c) => {
  const refreshToken = c.req.header('X-Refresh-Token')
  if (!refreshToken) {
    return c.json({ error: 'Kein Refresh-Token' }, 401)
  }
  try {
    const payload = await verify(refreshToken, c.env.JWT_SECRET)
    const newToken = await sign(
      { ...payload, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 86400 },
      c.env.JWT_SECRET
    )
    return c.json({ access_token: newToken })
  } catch {
    return c.json({ error: 'Ungültiger Token' }, 401)
  }
})

Protected Routes mit JWT Middleware

src/index.ts
import { jwt } from 'hono/jwt'

// JWT-Middleware fuer geschuetzte Routes
const jwtAuth = jwt({
  secret: (c) => c.env.JWT_SECRET, // dynamisch aus CF-Env
  alg: 'HS256',
})

// Geschuetzte Route-Gruppe
const api = new Hono()
api.use('*', jwtAuth)

api.get('/profile', (c) => {
  // Payload aus dem verifizierten Token
  const payload = c.get('jwtPayload')
  return c.json({
    userId: payload.sub,
    email: payload.email,
    role: payload.role,
  })
})

// Rollenbasierte Autorisierung
api.get('/admin/users', (c) => {
  const payload = c.get('jwtPayload')
  if (payload.role !== 'admin') {
    return c.json({ error: 'Forbidden' }, 403)
  }
  return c.json({ users: [] })
})

app.route('/auth', auth)
app.route('/api', api)
🔐
HS256 / RS256
Symmetrisch und asymmetrisch unterstuetzt
Web Crypto API
Kein Node.js-Crypto noetig, laeuft ueberall
🎯
Typsicherer Payload
Payload-Types via Generics definierbar
🔄
Refresh-Flow
Access + Refresh-Token Muster einfach umsetzbar

RPC 5. Hono RPC: Typsichere API-Clients ohne Codegen

hc() AppType InferResponseType InferRequestType

Der groesste Hono-Killer-Feature: typsichere RPC-Clients ohne jegliche Codegenerierung. Dein Frontend kennt alle API-Endpunkte, Parameter und Response-Types — direkt aus dem Server-Code. Claude Code nutzt dieses Feature, um Frontend-Backend-Integration in einem Schritt zu generieren.

Server: Typisierte Route-App exportieren

src/index.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()

const route = app
  .get('/posts', (c) => {
    return c.json({
      posts: [{ id: '1', title: 'Hello Hono', views: 42 }],
    })
  })
  .get('/posts/:id', (c) => {
    const id = c.req.param('id')
    return c.json({ id, title: 'Hello', content: 'World' })
  })
  .post(
    '/posts',
    zValidator('json', z.object({
      title: z.string(),
      content: z.string(),
    })),
    async (c) => {
      const { title, content } = c.req.valid('json')
      return c.json({ id: 'new-id', title, content }, 201)
    }
  )

// AppType fuer den Client exportieren
export type AppType = typeof route
export default app

Client: Vollstaendig typisiert mit hc()

src/client.ts
import { hc } from 'hono/client'
import type { AppType } from './index'
import type { InferResponseType, InferRequestType } from 'hono/client'

// Typisierter Client erstellen
const client = hc<AppType>('https://api.agentic-movers.com')

// GET /posts — vollstaendig typisiert
const res = await client.posts.$get()
const data = await res.json()
// data.posts ist: Array<{ id: string; title: string; views: number }>

// GET /posts/:id
const post = await client.posts[':id'].$get({ param: { id: '1' } })

// POST /posts mit typisiertem Body
const created = await client.posts.$post({
  json: {
    title: 'Hono RPC ist fantastisch',
    content: 'Keine Codegen noetig!',
  },
})

// Types aus der API inferieren
type PostsResponse = InferResponseType<typeof client.posts.$get>
type CreatePostInput = InferRequestType<typeof client.posts.$post>['json']

RPC mit Auth-Headers

src/api.ts
// Client-Factory mit Auth-Header
function createApiClient(token: string) {
  return hc<AppType>('https://api.agentic-movers.com', {
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  })
}

// React Hook Beispiel
function useApi() {
  const token = useAuthToken() // dein Auth-Store
  return useMemo(() => createApiClient(token), [token])
}
Monorepo-Setup Bei Monorepos (Turborepo, Nx) kannst du das AppType als separates Package exportieren. Frontend und Backend sind dann immer typsynchron — ohne GraphQL-Schema, ohne OpenAPI-Spec, ohne Codegen-Build-Step. Claude Code erstellt dieses Setup auf Anfrage in unter einer Minute.

CF 6. Cloudflare Workers Deploy: Wrangler, D1, KV & R2

wrangler.toml D1 Database KV Storage R2 Bucket Durable Objects

Cloudflare Workers sind die natuerliche Heimat fuer Hono-Apps. Mit Wrangler deployst du deine API in Sekunden in über 300 Rechenzentren weltweit. Claude Code kennt alle Wrangler-Konfigurationsoptionen und generiert produktionsreife CF-Setups auf Anfrage.

Wrangler-Konfiguration

wrangler.toml
name = "my-hono-api"
main = "src/index.ts"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]

# D1 Datenbank (SQLite auf CF Edge)
[[d1_databases]]
binding = "DB"
database_name = "my-api-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# KV Namespace (Key-Value Store)
[[kv_namespaces]]
binding = "KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# R2 Bucket (Object Storage)
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-api-assets"

# Environment-Variablen (Secrets via wrangler secret put)
[vars]
ENVIRONMENT = "production"
API_VERSION = "v1"

# Staging-Environment
[env.staging]
name = "my-hono-api-staging"

[[env.staging.d1_databases]]
binding = "DB"
database_name = "my-api-db-staging"
database_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"

D1 Datenbank-Integration

src/routes/posts.ts
import { Hono } from 'hono'

type Env = {
  Bindings: {
    DB: D1Database
    KV: KVNamespace
    BUCKET: R2Bucket
    JWT_SECRET: string
  }
}

const posts = new Hono<Env>()

// D1 Query
posts.get('/', async (c) => {
  const { results } = await c.env.DB
    .prepare('SELECT * FROM posts ORDER BY created_at DESC LIMIT 20')
    .all()
  return c.json({ posts: results })
})

// KV Cache-Pattern
posts.get('/:id', async (c) => {
  const id = c.req.param('id')
  const cacheKey = `post:${id}`

  // Cache pruefen
  const cached = await c.env.KV.get(cacheKey, 'json')
  if (cached) {
    c.res.headers.set('X-Cache', 'HIT')
    return c.json(cached)
  }

  // DB Query
  const post = await c.env.DB
    .prepare('SELECT * FROM posts WHERE id = ?')
    .bind(id)
    .first()

  if (!post) {
    return c.json({ error: 'Post nicht gefunden' }, 404)
  }

  // In KV cachen (300 Sekunden)
  await c.env.KV.put(cacheKey, JSON.stringify(post), { expirationTtl: 300 })
  c.res.headers.set('X-Cache', 'MISS')
  return c.json(post)
})

// R2 File Upload
posts.post('/:id/image', async (c) => {
  const id = c.req.param('id')
  const formData = await c.req.formData()
  const file = formData.get('image') as File

  await c.env.BUCKET.put(`posts/${id}/image`, file.stream(), {
    httpMetadata: { contentType: file.type },
  })

  return c.json({ uploaded: true, key: `posts/${id}/image` })
})

Deployment-Workflow

terminal
# Lokale Entwicklung mit Miniflare
bun run wrangler dev

# Secrets setzen (niemals in wrangler.toml!)
wrangler secret put JWT_SECRET
wrangler secret put API_SECRET

# D1-Migrationen
wrangler d1 execute my-api-db --file=./migrations/001_init.sql
wrangler d1 execute my-api-db --remote --file=./migrations/001_init.sql

# Deploy auf Production
bun run wrangler deploy

# Deploy auf Staging
bun run wrangler deploy --env staging

# Logs streamen
wrangler tail
Secrets niemals in wrangler.toml JWT-Secret, API-Keys und Datenbankpasswoerter gehoeren ausschliesslich in wrangler secret put. Claude Code weist automatisch darauf hin und generiert .env-basierte Konfigurationen fuer die lokale Entwicklung.

Mit Claude Code zur Hono-API in Minuten

Starte deinen kostenlosen Trial und erlebe, wie Claude Code vollstaendige Hono-APIs mit Routing, Middleware, Zod-Validierung und Cloudflare-Deployment generiert — produktionsreif, typsicher, sofort einsetzbar.

Kostenlos testen →

Kein Kreditkarte erforderlich • 14-Tage Trial • Sofort starten