Zum Inhalt springen

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.

ts
// 🙁
// 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.

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}`)
}

Du musst deshalb keine Controller nach RoR-Vorbild erstellen und solltest Handler direkt nach den Pfaddefinitionen schreiben.

ts
// 😃
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.

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)

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.

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

Importiere diese anschließend und binde sie mit app.route() an die Pfade /authors und /books an.

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

Wenn 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.

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

Wenn du den Typ von app an hc übergibst, erhält der Client den korrekten Typ.

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

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

Weitere 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 ​

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)

✅ Empfohlen: Middleware für HEAD-spezifische Logik verwenden ​

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)
  }
})

❌ Nicht empfohlen: Eigene HEAD-Handler erstellen ​

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
})

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 ​

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)
})

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

Veröffentlicht unter der MIT-Lizenz.