Aller au contenu

Hono

Framework web ultra-rapide pour l’edge, supportant Cloudflare Workers, Deno, Bun, Vercel, AWS Lambda et Node.js sans aucune dépendance.

CommandeDescription
npm create hono@latest my-appCréer un nouveau projet Hono avec npm
yarn create hono my-appCréer avec Yarn
pnpm create hono my-appCréer avec pnpm
bun create hono my-appCréer avec Bun
deno init --lib honoCréer avec Deno
CommandeDescription
npm install honoInstaller Hono dans un projet existant
yarn add honoInstaller avec Yarn
pnpm add honoInstaller avec pnpm
bun add honoInstaller avec Bun
# Créer un projet avec un modèle de runtime spécifique
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
CommandeDescription
app.get('/path', handler)Gérer les requêtes GET
app.post('/path', handler)Gérer les requêtes POST
app.put('/path', handler)Gérer les requêtes PUT
app.delete('/path', handler)Gérer les requêtes DELETE
app.patch('/path', handler)Gérer les requêtes PATCH
app.options('/path', handler)Gérer les requêtes OPTIONS
app.all('/path', handler)Gérer toutes les méthodes HTTP
app.on('PURGE', '/path', handler)Gérer les méthodes personnalisées
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
// Paramètres de chemin
app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id })
})

// Paramètres multiples
app.get('/posts/:postId/comments/:commentId', (c) => {
  const { postId, commentId } = c.req.param()
  return c.json({ postId, commentId })
})

// Paramètre optionnel
app.get('/api/:version?/users', (c) => {
  const version = c.req.param('version') || 'v1'
  return c.json({ version })
})

// Joker
app.get('/files/*', (c) => {
  const path = c.req.path
  return c.text(`File path: ${path}`)
})

// Motifs de type regex
app.get('/user/:id{[0-9]+}', (c) => {
  const id = c.req.param('id')
  return c.json({ id: Number(id) })
})
CommandeDescription
c.req.query('key')Obtenir un paramètre de requête unique
c.req.query()Obtenir tous les paramètres de requête
c.req.queries('tags')Obtenir un tableau de valeurs pour une clé
CommandeDescription
c.req.json()Parser le corps JSON
c.req.text()Obtenir le corps en texte
c.req.formData()Parser les données de formulaire
c.req.blob()Obtenir le corps en Blob
c.req.arrayBuffer()Obtenir le corps en ArrayBuffer
c.req.parseBody()Parser automatiquement le corps selon le type de contenu
CommandeDescription
c.req.header('Content-Type')Obtenir un en-tête de requête
c.req.header()Obtenir tous les en-têtes
c.req.methodObtenir la méthode HTTP
c.req.urlObtenir l’URL complète de la requête
c.req.pathObtenir le chemin de la requête
c.req.rawObtenir l’objet Request brut
c.req.valid('json')Obtenir les données validées
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)
})
CommandeDescription
c.text('Hello')Réponse en texte brut
c.json({ key: 'value' })Réponse JSON
c.html('<h1>Hi</h1>')Réponse HTML
c.redirect('/new-url')Redirection 302
c.redirect('/new-url', 301)Redirection 301 permanente
c.notFound()Réponse 404
c.body(data)Réponse avec corps brut
c.newResponse(body, status, headers)Réponse personnalisée complète
// Définir les en-têtes
app.get('/api/data', (c) => {
  c.header('X-Custom-Header', 'value')
  c.header('Cache-Control', 'max-age=3600')
  return c.json({ data: 'example' })
})

// Définir le code de statut
app.post('/items', async (c) => {
  const item = await c.req.json()
  return c.json(item, 201)
})

// Réponse en streaming
app.get('/stream', (c) => {
  return c.streamText(async (stream) => {
    await stream.write('Hello ')
    await stream.sleep(1000)
    await stream.write('World!')
  })
})
CommandeDescription
import { cors } from 'hono/cors'Middleware CORS
import { logger } from 'hono/logger'Journalisation des requêtes
import { prettyJSON } from 'hono/pretty-json'Formatage JSON lisible
import { basicAuth } from 'hono/basic-auth'Authentification basique
import { bearerAuth } from 'hono/bearer-auth'Authentification par jeton Bearer
import { jwt } from 'hono/jwt'Authentification JWT
import { compress } from 'hono/compress'Compression Gzip/Brotli
import { etag } from 'hono/etag'Cache ETag
import { secureHeaders } from 'hono/secure-headers'En-têtes de sécurité
import { csrf } from 'hono/csrf'Protection CSRF
import { timing } from 'hono/timing'En-tête Server-Timing
import { cache } from 'hono/cache'Contrôle du cache
import { bodyLimit } from 'hono/body-limit'Limite de taille du corps de requête
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()

// Middleware global
app.use('*', logger())
app.use('*', secureHeaders())
app.use('*', cors({
  origin: ['https://example.com'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowHeaders: ['Content-Type', 'Authorization'],
  maxAge: 86400,
}))

// Middleware spécifique à une route
app.use('/api/*', bearerAuth({ token: 'my-secret-token' }))

// Middleware personnalisé
app.use('*', async (c, next) => {
  const start = Date.now()
  await next()
  const duration = Date.now() - start
  c.header('X-Response-Time', `${duration}ms`)
})
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 })
})
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)
  }
)

// Valider les paramètres de requête
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 })
  }
)
const app = new Hono()

// Grouper les routes avec basePath
const api = new Hono().basePath('/api')
api.get('/users', (c) => c.json({ users: [] }))
api.post('/users', (c) => c.json({ created: true }))
app.route('/', api)

// APIs versionnées
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)
import { HTTPException } from 'hono/http-exception'

// 404 personnalisé
app.notFound((c) => {
  return c.json({ error: 'Not Found' }, 404)
})

// Gestionnaire d'erreurs global
app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return err.getResponse()
  }
  console.error(err)
  return c.json({ error: 'Internal Server Error' }, 500)
})

// Lancer des exceptions HTTP
app.get('/admin', (c) => {
  if (!c.req.header('Authorization')) {
    throw new HTTPException(401, { message: 'Unauthorized' })
  }
  return c.json({ admin: true })
})
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
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 })
import { Hono } from 'hono'

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

export default { port: 3000, fetch: app.fetch }
import { Hono } from 'npm:hono'

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

Deno.serve(app.fetch)
// 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 — entièrement typé
import { hc } from 'hono/client'
import type { AppType } from './server'

const client = hc<AppType>('http://localhost:3000')

// Entièrement typé — autocomplétion pour les routes + réponses
const res = await client.api.users.$get()
const data = await res.json()
// data: { users: { id: number, name: string }[] }
import { testClient } from 'hono/testing'

const app = new Hono()
  .get('/hello', (c) => c.json({ message: 'Hello!' }))

// Utilisation de testClient (type-safe)
const client = testClient(app)
const res = await client.hello.$get()
const body = await res.json()
// body.message === 'Hello!'

// Utilisation directe de app.request()
const res2 = await app.request('/hello')
// res2.status === 200
  1. Utiliser les génériques TypeScript pour les bindings d’environnement — Définissez Hono<{ Bindings: MyBindings }> pour obtenir la sécurité des types pour les variables d’environnement, les namespaces KV et les bases de données D1.

  2. Valider toutes les entrées avec Zod — Utilisez @hono/zod-validator pour la validation du corps de requête, des paramètres de requête et des paramètres d’URL. Cela fournit la sécurité à l’exécution et les types TypeScript en une seule étape.

  3. Utiliser le client RPC pour le frontend-backend — Exportez AppType depuis votre serveur et utilisez hc<AppType>() côté client pour une sécurité des types de bout en bout sans génération de code.

  4. Appliquer le middleware avec des motifs de chemin — Utilisez app.use('/api/*', middleware) pour limiter le middleware à des routes spécifiques plutôt que de tout appliquer globalement.

  5. Grouper les routes avec new Hono().basePath() — Organisez les endpoints liés dans des instances Hono séparées et montez-les avec app.route() pour une séparation propre.

  6. Gérer les erreurs globalement — Utilisez app.onError() et app.notFound() pour vous assurer que chaque erreur retourne une réponse JSON structurée au lieu de traces de pile.

  7. Utiliser c.executionCtx.waitUntil() — Pour le travail en arrière-plan sur Cloudflare Workers (analytics, journalisation), utilisez waitUntil() pour éviter de bloquer la réponse.

  8. Garder les handlers légers — Extrayez la logique métier dans des fonctions/modules séparés. Les handlers de route devraient parser l’entrée, appeler la logique et retourner les réponses.

  9. Tester avec testClient — Le client de test intégré de Hono est type-safe et n’a pas besoin d’un serveur en cours d’exécution, rendant les tests unitaires rapides et fiables.

  10. Déployer là où sont vos utilisateurs — Hono fonctionne sur tous les principaux runtimes edge. Choisissez Cloudflare Workers pour l’edge global, Bun pour la vitesse brute, ou Node.js pour la compatibilité de l’écosystème.