From bc22304e31ab4ff5a873cf2649de3e767bfc0bb3 Mon Sep 17 00:00:00 2001 From: Martin DONADIEU Date: Wed, 6 May 2026 22:46:33 +0200 Subject: [PATCH 1/4] Add HEAD request best practices to documentation Added best practices for handling HEAD requests in Hono framework, including guidelines for using GET routes, middleware, and performance considerations. --- docs/guides/best-practices.md | 115 ++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/docs/guides/best-practices.md b/docs/guides/best-practices.md index 48eb86eb..a0291cda 100644 --- a/docs/guides/best-practices.md +++ b/docs/guides/best-practices.md @@ -112,6 +112,121 @@ app.route('/books', books) export default app ``` +Based on my analysis of Hono's HEAD request handling, here's what I would add to the best practices guide: + +--- + +## HEAD Request Best Practices + +### Understanding Hono's HEAD Handling + +Hono automatically handles HEAD requests by converting them to GET requests and stripping the response body [1](#2-0) . This behavior is built into the framework's dispatch layer and happens before route matching occurs. + +### ✅ Do: Use GET Routes for HEAD Requests + +```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) +``` + +### ✅ Do: Use Middleware for HEAD-Specific Logic + +```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) + } +}) +``` + +### ❌ Don't: Try to Create Dedicated HEAD Handlers + +```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 +}) +``` + +### Performance Considerations + +- **Avoid expensive operations in GET handlers if you expect many HEAD requests**: Use middleware to detect HEAD and skip body generation +- **Cache headers work identically**: HEAD responses respect the same caching rules as GET +- **Middleware compatibility**: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requests [2](#2-1) + +### Testing HEAD Requests + +```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) +}) +``` + +### Migration Note + +If you're upgrading from Hono v3, remove any `app.head()` routes as they're no longer needed [3](#2-2) . Your existing GET routes will automatically handle HEAD requests. + +--- + +## Notes + +- The automatic HEAD conversion ensures consistent headers between GET and HEAD responses +- This behavior is consistent across all Hono runtimes (Cloudflare Workers, Deno, Bun, Node.js) +- If you need completely different logic for HEAD vs GET, consider using different endpoints rather than trying to override the framework's HEAD handling + +Wiki pages you might want to explore: +- [Hono Application Class and HonoBase (honojs/hono)](/wiki/honojs/hono#2.1) + +### Citations + +**File:** src/hono-base.ts (L406-410) +```typescript + // Handle HEAD method + if (method === 'HEAD') { + return (async () => + new Response(null, await this.#dispatch(request, executionCtx, env, 'GET')))() + } +``` + +**File:** src/middleware/compress/index.ts (L46-46) +```typescript + ctx.req.method === 'HEAD' || // HEAD request +``` + +**File:** docs/MIGRATION.md (L34-34) +```markdown +- Hono - `app.head()` is no longer used. `app.get()` implicitly handles the HEAD method. +``` + + ### If you want to use RPC features The code above works well for normal use cases. From 78ffc732713d4a38d616c85d5c81fd5994e1209a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 20:47:09 +0000 Subject: [PATCH 2/4] ci: apply automated fixes --- docs/guides/best-practices.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/guides/best-practices.md b/docs/guides/best-practices.md index a0291cda..0f042337 100644 --- a/docs/guides/best-practices.md +++ b/docs/guides/best-practices.md @@ -144,7 +144,7 @@ app.get('/api/users', async (c) => { // 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') @@ -174,7 +174,7 @@ app.on('HEAD', '/api/users', (c) => { - **Avoid expensive operations in GET handlers if you expect many HEAD requests**: Use middleware to detect HEAD and skip body generation - **Cache headers work identically**: HEAD responses respect the same caching rules as GET -- **Middleware compatibility**: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requests [2](#2-1) +- **Middleware compatibility**: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requests [2](#2-1) ### Testing HEAD Requests @@ -183,9 +183,11 @@ app.on('HEAD', '/api/users', (c) => { 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.headers.get('X-Total-Count')).toBe( + getRes.headers.get('X-Total-Count') + ) expect(headRes.body).toBe(null) }) ``` @@ -203,30 +205,36 @@ If you're upgrading from Hono v3, remove any `app.head()` routes as they're no l - If you need completely different logic for HEAD vs GET, consider using different endpoints rather than trying to override the framework's HEAD handling Wiki pages you might want to explore: + - [Hono Application Class and HonoBase (honojs/hono)](/wiki/honojs/hono#2.1) ### Citations **File:** src/hono-base.ts (L406-410) + ```typescript - // Handle HEAD method - if (method === 'HEAD') { - return (async () => - new Response(null, await this.#dispatch(request, executionCtx, env, 'GET')))() - } +// Handle HEAD method +if (method === 'HEAD') { + return (async () => + new Response( + null, + await this.#dispatch(request, executionCtx, env, 'GET') + ))() +} ``` **File:** src/middleware/compress/index.ts (L46-46) + ```typescript ctx.req.method === 'HEAD' || // HEAD request ``` **File:** docs/MIGRATION.md (L34-34) + ```markdown - Hono - `app.head()` is no longer used. `app.get()` implicitly handles the HEAD method. ``` - ### If you want to use RPC features The code above works well for normal use cases. From 92c232142741cacdfef09f7a0b957e4852d912d8 Mon Sep 17 00:00:00 2001 From: Martin DONADIEU Date: Wed, 6 May 2026 22:47:50 +0200 Subject: [PATCH 3/4] Update best practices guide with HEAD request section Add best practices for handling HEAD requests in Hono. --- docs/guides/best-practices.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/guides/best-practices.md b/docs/guides/best-practices.md index 0f042337..505de69e 100644 --- a/docs/guides/best-practices.md +++ b/docs/guides/best-practices.md @@ -112,10 +112,6 @@ app.route('/books', books) export default app ``` -Based on my analysis of Hono's HEAD request handling, here's what I would add to the best practices guide: - ---- - ## HEAD Request Best Practices ### Understanding Hono's HEAD Handling From 063a166f2fab46b2aa43547105272a1bc4faaf45 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Sat, 9 May 2026 19:27:45 +0900 Subject: [PATCH 4/4] fixed the document --- docs/guides/best-practices.md | 103 +++++++++++----------------------- 1 file changed, 33 insertions(+), 70 deletions(-) diff --git a/docs/guides/best-practices.md b/docs/guides/best-practices.md index 505de69e..cccd045a 100644 --- a/docs/guides/best-practices.md +++ b/docs/guides/best-practices.md @@ -112,11 +112,41 @@ app.route('/books', books) export default app ``` +### If you want to use RPC features + +The code above works well for normal use cases. +However, if you want to use the `RPC` feature, you can get the correct type by chaining as follows. + +```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 +``` + +If you pass the type of the `app` to `hc`, it will get the correct type. + +```ts +import type { AppType } from './authors' +import { hc } from 'hono/client' + +// 😃 +const client = hc('http://localhost') // Typed correctly +``` + +For more detailed information, please see [the RPC page](/docs/guides/rpc#using-rpc-with-larger-applications). + ## HEAD Request Best Practices ### Understanding Hono's HEAD Handling -Hono automatically handles HEAD requests by converting them to GET requests and stripping the response body [1](#2-0) . This behavior is built into the framework's dispatch layer and happens before route matching occurs. +Hono automatically handles HEAD requests by converting them to GET requests and stripping the response body. This behavior is built into the framework's dispatch layer and happens before route matching occurs. ### ✅ Do: Use GET Routes for HEAD Requests @@ -170,7 +200,7 @@ app.on('HEAD', '/api/users', (c) => { - **Avoid expensive operations in GET handlers if you expect many HEAD requests**: Use middleware to detect HEAD and skip body generation - **Cache headers work identically**: HEAD responses respect the same caching rules as GET -- **Middleware compatibility**: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requests [2](#2-1) +- **Middleware compatibility**: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requests ### Testing HEAD Requests @@ -188,75 +218,8 @@ it('handles HEAD requests correctly', async () => { }) ``` -### Migration Note - -If you're upgrading from Hono v3, remove any `app.head()` routes as they're no longer needed [3](#2-2) . Your existing GET routes will automatically handle HEAD requests. - ---- - -## Notes +### Notes - The automatic HEAD conversion ensures consistent headers between GET and HEAD responses - This behavior is consistent across all Hono runtimes (Cloudflare Workers, Deno, Bun, Node.js) - If you need completely different logic for HEAD vs GET, consider using different endpoints rather than trying to override the framework's HEAD handling - -Wiki pages you might want to explore: - -- [Hono Application Class and HonoBase (honojs/hono)](/wiki/honojs/hono#2.1) - -### Citations - -**File:** src/hono-base.ts (L406-410) - -```typescript -// Handle HEAD method -if (method === 'HEAD') { - return (async () => - new Response( - null, - await this.#dispatch(request, executionCtx, env, 'GET') - ))() -} -``` - -**File:** src/middleware/compress/index.ts (L46-46) - -```typescript - ctx.req.method === 'HEAD' || // HEAD request -``` - -**File:** docs/MIGRATION.md (L34-34) - -```markdown -- Hono - `app.head()` is no longer used. `app.get()` implicitly handles the HEAD method. -``` - -### If you want to use RPC features - -The code above works well for normal use cases. -However, if you want to use the `RPC` feature, you can get the correct type by chaining as follows. - -```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 -``` - -If you pass the type of the `app` to `hc`, it will get the correct type. - -```ts -import type { AppType } from './authors' -import { hc } from 'hono/client' - -// 😃 -const client = hc('http://localhost') // Typed correctly -``` - -For more detailed information, please see [the RPC page](/docs/guides/rpc#using-rpc-with-larger-applications).