Stacks Hono
Hono rend les choses simples faciles, mais aussi les choses difficiles. Il permet non seulement de renvoyer du JSON, mais également de construire des applications complètes avec des serveurs d’API REST et un client.
RPC
La fonctionnalité RPC de Hono permet de partager les spécifications d’une API avec seulement quelques modifications du code. Le client créé par hc lit ces spécifications et accède aux points d’entrée en respectant les types.
Les bibliothèques suivantes rendent cela possible.
- Hono — serveur d’API
- Zod — validateur
- Middleware Zod Validator
hc— client HTTP
Nous pouvons appeler cet ensemble la stack Hono. Créons un serveur d’API et un client à l’aide de ces composants.
Écrire l’API
Commencez par écrire un point d’entrée qui accepte une requête GET et renvoie du JSON.
import { Hono } from 'hono'
const app = new Hono()
app.get('/hello', (c) => {
return c.json({
message: `Hello!`,
})
})Validation avec Zod
Validez les données avec Zod pour obtenir la valeur du paramètre de requête.

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}`,
})
}
)Partager les types
Pour exposer la spécification du point d’entrée, exportez son type.
Attention
Pour que RPC puisse correctement inférer les routes, toutes les méthodes concernées doivent être chaînées, et le type du point d’entrée ou de l’application doit provenir d’une variable déclarée. Consultez les bonnes pratiques pour RPC pour en savoir plus.
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
Passons ensuite à l’implémentation côté client. Créez un objet client en passant le type AppType comme paramètre générique de hc. La saisie automatique fonctionne alors comme par magie : elle suggère les chemins des points d’entrée et les types de requêtes.

import type { AppType } from './server'
import { hc } from 'hono/client'
const client = hc<AppType>('/api')
const res = await client.hello.$get({
query: {
name: 'Hono',
},
})La Response est compatible avec l’API Fetch, mais les données obtenues avec json() sont typées.

const data = await res.json()
console.log(`${data.message}`)Partager les spécifications d’une API vous permet de détecter les changements côté serveur.

Avec React
Vous pouvez créer des applications sur Cloudflare Workers avec React.
Le serveur d’API.
// 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 appLe client avec React et 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>
)
}