Aller au contenu

Bonnes pratiques ​

Hono est très flexible. Vous pouvez écrire votre application comme vous le souhaitez. Certaines bonnes pratiques méritent toutefois d’être suivies.

Éviter les « contrôleurs » lorsque c’est possible ​

Dans la mesure du possible, évitez de créer des « contrôleurs à la Ruby on Rails ».

ts
// 🙁
// A RoR-like Controller
const booksList = (c: Context) => {
  return c.json('list books')
}

app.get('/books', booksList)

Le problème concerne les types. Par exemple, le paramètre de chemin ne peut pas être inféré dans le contrôleur sans écrire des types génériques complexes.

ts
// 🙁
// A RoR-like Controller
const bookPermalink = (c: Context) => {
  const id = c.req.param('id') // Can't infer the path param
  return c.json(`get ${id}`)
}

Vous n’avez donc pas besoin de contrôleurs à la RoR : écrivez les gestionnaires directement après les définitions des chemins.

ts
// 😃
app.get('/books/:id', (c) => {
  const id = c.req.param('id') // Can infer the path param
  return c.json(`get ${id}`)
})

factory.createHandlers() dans hono/factory ​

Si vous souhaitez quand même créer un contrôleur à la RoR, utilisez factory.createHandlers() dans hono/factory. L’inférence de types fonctionnera alors correctement.

ts
import { createFactory } from 'hono/factory'
import { logger } from 'hono/logger'

// ...

// 😃
const factory = createFactory()

const middleware = factory.createMiddleware(async (c, next) => {
  c.set('foo', 'bar')
  await next()
})

const handlers = factory.createHandlers(logger(), middleware, (c) => {
  return c.json(c.var.foo)
})

app.get('/api', ...handlers)

Construire une application plus grande ​

Utilisez app.route() pour construire une application plus grande sans créer de « contrôleurs à la Ruby on Rails ».

Si votre application possède des points de terminaison /authors et /books et que vous souhaitez les séparer de index.ts, créez authors.ts et books.ts.

ts
// authors.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.json('list authors'))
app.post('/', (c) => c.json('create an author', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))

export default app
ts
// books.ts
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.json('list books'))
app.post('/', (c) => c.json('create a book', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))

export default app

Importez-les ensuite et montez-les sur les chemins /authors et /books avec app.route().

ts
// index.ts
import { Hono } from 'hono'
import authors from './authors'
import books from './books'

const app = new Hono()

// 😃
app.route('/authors', authors)
app.route('/books', books)

export default app

Pour utiliser les fonctionnalités RPC ​

Le code ci-dessus fonctionne bien pour les usages habituels. Cependant, pour utiliser la fonctionnalité RPC, vous pouvez obtenir le type correct en chaînant les appels comme suit.

ts
// authors.ts
import { Hono } from 'hono'

const app = new Hono()
  .get('/', (c) => c.json('list authors'))
  .post('/', (c) => c.json('create an author', 201))
  .get('/:id', (c) => c.json(`get ${c.req.param('id')}`))

export default app
export type AppType = typeof app

Si vous transmettez le type de app à hc, il obtiendra le type correct.

ts
import type { AppType } from './authors'
import { hc } from 'hono/client'

// 😃
const client = hc<AppType>('http://localhost') // Typed correctly

Pour plus de détails, consultez la page RPC.

Bonnes pratiques pour les requêtes HEAD ​

Comprendre la gestion de HEAD par Hono ​

Hono traite automatiquement les requêtes HEAD en les convertissant en requêtes GET et en supprimant le corps de la réponse. Ce comportement est intégré à la couche de répartition du framework et intervient avant la correspondance des routes.

✅ À faire : utiliser les routes GET pour les requêtes HEAD ​

typescript
// GOOD: This GET route automatically handles HEAD requests
app.get('/api/users', async (c) => {
  const users = await getUsers()
  c.header('X-Total-Count', users.length.toString())
  return c.json(users)
})

// HEAD /api/users will return:
// - Same headers as GET (including X-Total-Count)
// - Status 200
// - No body (null)

✅ À faire : utiliser un middleware pour la logique propre à HEAD ​

typescript
// GOOD: Use middleware when HEAD needs different behavior
app.use('/api/resource', async (c, next) => {
  await next()

  // Add HEAD-specific headers after the handler
  if (c.req.method === 'HEAD') {
    c.header('X-HEAD-Processed', 'true')
    // Don't compute expensive body content for HEAD
    c.res = new Response(null, c.res)
  }
})

❌ À éviter : créer des gestionnaires HEAD dédiés ​

typescript
// BAD: This won't work as expected
app.head('/api/users', (c) => {
  // This handler will NEVER be called
  c.header('X-Custom', 'value')
  return c.text('ignored')
})

// BAD: Using on() also won't work
app.on('HEAD', '/api/users', (c) => {
  // Still converted to GET before route matching
})

Considérations de performances ​

  • Évitez les opérations coûteuses dans les gestionnaires GET si vous attendez de nombreuses requêtes HEAD : utilisez un middleware pour détecter HEAD et éviter la génération du corps
  • Les en-têtes de cache fonctionnent de la même façon : les réponses HEAD respectent les mêmes règles de cache que GET
  • Compatibilité des middlewares : la plupart fonctionnent avec HEAD, mais les middlewares qui traitent le corps (comme la compression) ignorent automatiquement les requêtes HEAD

Tester les requêtes HEAD ​

typescript
// Always test both GET and HEAD responses
it('handles HEAD requests correctly', async () => {
  const getRes = await app.request('/api/users')
  const headRes = await app.request('/api/users', { method: 'HEAD' })

  expect(headRes.status).toBe(getRes.status)
  expect(headRes.headers.get('X-Total-Count')).toBe(
    getRes.headers.get('X-Total-Count')
  )
  expect(headRes.body).toBe(null)
})

Remarques ​

  • La conversion automatique de HEAD assure la cohérence des en-têtes entre les réponses GET et HEAD
  • Ce comportement est identique dans tous les environnements d’exécution de Hono (Cloudflare Workers, Deno, Bun, Node.js)
  • Si vous avez besoin de logiques complètement différentes pour HEAD et GET, envisagez des points de terminaison différents plutôt que de remplacer la gestion de HEAD du framework

Publié sous licence MIT.