Routing, Middleware, Zod-Validation, JWT Auth, RPC-Mode und Cloudflare Workers — Claude Code als Hono-Experte fuer das schnellste Edge-API-Framework der Welt.
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.
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.
# 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
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
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)
// 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 }) })
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.
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()) }
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 }, })
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))
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.
bun add @hono/zod-validator zod
# oder
npm install @hono/zod-validator zod
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-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 }) } )
// 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) } )
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.
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) } })
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)
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.
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
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']
// 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]) }
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.
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.
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"
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` }) })
# 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
npm install -g wrangler und wrangler login fuer CF-Account-Verbindung
wrangler d1 create my-api-db — ID in wrangler.toml eintragen
wrangler kv:namespace create KV — ID eintragen
wrangler secret put JWT_SECRET fuer produktionssichere Konfiguration
wrangler deploy — deine API ist in <30s weltweit verfuegbar
wrangler secret put. Claude Code weist automatisch darauf hin und generiert
.env-basierte Konfigurationen fuer die lokale Entwicklung.
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