Hono-Stacks
Hono macht einfache Dinge einfach und schwierige Dinge ebenfalls einfach. Es eignet sich nicht nur zum Zurückgeben von JSON, sondern auch zum Entwickeln vollständiger Anwendungen mit REST-API-Servern und einem Client.
RPC
Mit der RPC-Funktion von Hono kannst du API-Spezifikationen mit nur wenigen Änderungen an deinem Code gemeinsam nutzen. Der von hc erzeugte Client liest die Spezifikation und greift typsicher auf die Endpunkte zu.
Die folgenden Bibliotheken ermöglichen dies.
- Hono – API-Server
- Zod – Validator
- Zod-Validator-Middleware
hc– HTTP-Client
Die Kombination dieser Komponenten bezeichnen wir als Hono-Stack. Erstellen wir damit einen API-Server und einen Client.
Die API schreiben
Schreibe zunächst einen Endpunkt, der eine GET-Anfrage entgegennimmt und JSON zurückgibt.
import { Hono } from 'hono'
const app = new Hono()
app.get('/hello', (c) => {
return c.json({
message: `Hello!`,
})
})Validierung mit Zod
Validiere mit Zod, um den Wert des Abfrageparameters zu erhalten.

import { zValidator } from '@hono/zod-validator'
import * as z from 'zod'
app.get(
'/hello',
zValidator(
'query',
z.object({
name: z.string(),
})
),
(c) => {
const { name } = c.req.valid('query')
return c.json({
message: `Hello! ${name}`,
})
}
)Typen gemeinsam nutzen
Exportiere den Typ des Endpunkts, um seine Spezifikation bereitzustellen.
Achtung
Damit RPC die Routen korrekt ableiten kann, müssen alle beteiligten Methoden verkettet sein. Außerdem muss der Typ des Endpunkts oder der Anwendung aus einer deklarierten Variablen abgeleitet werden. Weitere Informationen findest du unter Bewährte Vorgehensweisen für RPC.
const route = app.get(
'/hello',
zValidator(
'query',
z.object({
name: z.string(),
})
),
(c) => {
const { name } = c.req.valid('query')
return c.json({
message: `Hello! ${name}`,
})
}
)
export type AppType = typeof routeClient
Als Nächstes folgt die Implementierung auf der Clientseite. Erstelle ein Clientobjekt, indem du hc den Typ AppType als generischen Typ übergibst. Danach funktioniert die automatische Vervollständigung wie von selbst: Pfade der Endpunkte und Anfragetypen werden vorgeschlagen.

import type { AppType } from './server'
import { hc } from 'hono/client'
const client = hc<AppType>('/api')
const res = await client.hello.$get({
query: {
name: 'Hono',
},
})Die Response ist mit der Fetch-API kompatibel. Die Daten, die du mit json() abrufen kannst, sind jedoch typisiert.

const data = await res.json()
console.log(`${data.message}`)Durch gemeinsam genutzte API-Spezifikationen erkennst du Änderungen auf der Serverseite.

Mit React
Mit React kannst du Anwendungen auf Cloudflare Workers entwickeln.
Der API-Server.
// src/index.ts
import { Hono } from 'hono'
import * as z from 'zod'
import { zValidator } from '@hono/zod-validator'
const schema = z.object({
id: z.string(),
title: z.string(),
})
type Todo = z.infer<typeof schema>
const todos: Todo[] = []
const api = new Hono()
.post('/todo', zValidator('form', schema), (c) => {
const todo = c.req.valid('form')
todos.push(todo)
return c.json({
message: 'created!',
})
})
.get('/todo', (c) => {
return c.json({
todos,
})
})
export type AppType = typeof api
const app = new Hono()
app.route('/api', api)
export default appDer Client mit React und React Query.
// src/App.tsx
import {
useQuery,
useMutation,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import type { AppType } from '../functions/api/[[route]]'
import { hc, InferResponseType, InferRequestType } from 'hono/client'
const queryClient = new QueryClient()
const client = hc<AppType>('/api')
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
}
const Todos = () => {
const query = useQuery({
queryKey: ['todos'],
queryFn: async () => {
const res = await client.todo.$get()
return await res.json()
},
})
const $post = client.todo.$post
const mutation = useMutation<
InferResponseType<typeof $post>,
Error,
InferRequestType<typeof $post>['form']
>({
mutationFn: async (todo) => {
const res = await $post({
form: todo,
})
return await res.json()
},
onSuccess: async () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
onError: (error) => {
console.log(error)
},
})
return (
<div>
<button
onClick={() => {
mutation.mutate({
id: Date.now().toString(),
title: 'Write code',
})
}}
>
Add Todo
</button>
<ul>
{query.data?.todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
)
}