Bewährte Vorgehensweisen
Hono ist sehr flexibel. Du kannst deine Anwendung nach deinen Vorstellungen schreiben. Es gibt jedoch bewährte Vorgehensweisen, denen du möglichst folgen solltest.
Möglichst keine „Controller“ erstellen
Wenn möglich, solltest du keine „Controller wie in Ruby on Rails“ erstellen.
// 🙁
// A RoR-like Controller
const booksList = (c: Context) => {
return c.json('list books')
}
app.get('/books', booksList)Das Problem betrifft die Typen. Zum Beispiel lässt sich der Pfadparameter im Controller nicht inferieren, ohne komplexe Generics zu schreiben.
// 🙁
// 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}`)
}Du musst deshalb keine Controller nach RoR-Vorbild erstellen und solltest Handler direkt nach den Pfaddefinitionen schreiben.
// 😃
app.get('/books/:id', (c) => {
const id = c.req.param('id') // Can infer the path param
return c.json(`get ${id}`)
})factory.createHandlers() in hono/factory
Wenn du trotzdem einen Controller nach RoR-Vorbild erstellen möchtest, verwende factory.createHandlers() aus hono/factory. Damit funktioniert die Typinferenz korrekt.
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)Eine größere Anwendung erstellen
Verwende app.route(), um eine größere Anwendung zu erstellen, ohne „Controller wie in Ruby on Rails“ anzulegen.
Wenn deine Anwendung die Endpunkte /authors und /books enthält und du sie aus index.ts in separate Dateien auslagern möchtest, erstelle authors.ts und books.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// 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 appImportiere diese anschließend und binde sie mit app.route() an die Pfade /authors und /books an.
// 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 appWenn du RPC-Funktionen verwenden möchtest
Der obige Code funktioniert für normale Anwendungsfälle gut. Wenn du jedoch die Funktion RPC verwenden möchtest, erhältst du den korrekten Typ durch die folgende Verkettung.
// 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 appWenn du den Typ von app an hc übergibst, erhält der Client den korrekten Typ.
import type { AppType } from './authors'
import { hc } from 'hono/client'
// 😃
const client = hc<AppType>('http://localhost') // Typed correctlyWeitere Informationen findest du auf der RPC-Seite.
Bewährte Vorgehensweisen für HEAD-Anfragen
Honos Verarbeitung von HEAD verstehen
Hono verarbeitet HEAD-Anfragen automatisch, indem es sie in GET-Anfragen umwandelt und den Antwort-Body entfernt. Dieses Verhalten ist in die Dispatch-Schicht des Frameworks eingebaut und erfolgt vor dem Routenabgleich.
✅ Empfohlen: GET-Routen für HEAD-Anfragen verwenden
// 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)✅ Empfohlen: Middleware für HEAD-spezifische Logik verwenden
// 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)
}
})❌ Nicht empfohlen: Eigene HEAD-Handler erstellen
// 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
})Leistungsaspekte
- Vermeide aufwendige Operationen in GET-Handlern, wenn du viele HEAD-Anfragen erwartest: Erkenne HEAD mithilfe von Middleware und überspringe die Erzeugung des Bodys
- Cache-Header funktionieren identisch: HEAD-Antworten folgen denselben Caching-Regeln wie GET
- Middleware-Kompatibilität: Die meisten Middlewares funktionieren mit HEAD, aber Middleware zur Body-Verarbeitung (etwa Komprimierung) überspringt HEAD-Anfragen automatisch
HEAD-Anfragen testen
// 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)
})Hinweise
- Die automatische Umwandlung von HEAD sorgt für konsistente Header zwischen GET- und HEAD-Antworten
- Dieses Verhalten ist in allen Hono-Laufzeitumgebungen gleich (Cloudflare Workers, Deno, Bun, Node.js)
- Wenn du für HEAD und GET völlig unterschiedliche Logik benötigst, solltest du unterschiedliche Endpunkte verwenden, statt die HEAD-Verarbeitung des Frameworks zu überschreiben