Hono: middleware, cookies, headers, redirects
By Flavio Copes
Hono middleware, cookies, headers, and redirects for everyday web apps. Built-in logger, basic auth, and CORS, plus custom middleware and signed cookies.
In the last post we covered the basics of Hono on Bun. We set up a small app, defined routes, and returned JSON and plain text. Now let’s look at the tools you reach for on almost every real project: middleware, headers, cookies, and redirects.
Middleware
Middleware is a function that runs during the request–response cycle. It can run before your route handler, after it, or both. You register it with app.use() and a path pattern.
The pattern works like a route path. '*' runs on every request. '/api/*' runs only on paths that start with /api/. Middleware on a matching path runs before the route handler for that same path.
Hono ships with built-in middleware. A logger is the easiest place to start:
import { logger } from 'hono/logger'
app.use('*', logger())
This logs every request to the console. Handy while you’re building.
Need to protect a route? Use HTTP basic auth:
import { basicAuth } from 'hono/basic-auth'
app.use('/dashboard/*', basicAuth({
username: 'admin',
password: 'secret',
}))
app.get('/dashboard', (c) => c.text('Logged in'))
Wrong credentials get a 401. The route handler only runs when the client passes the check.
For APIs that talk to a browser on another origin, add CORS:
import { cors } from 'hono/cors'
app.use('/api/*', cors())
That covers the common built-ins. Hono has more — compression, cache headers, JWT validation, and others. Check the official middleware docs when you need something specific.
You can also write your own middleware. The pattern is always the same — an async function that receives the context c and a next function:
app.use('*', async (c, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
c.header('X-Response-Time', `${ms}ms`)
})
Call await next() to pass control to the next middleware or route handler. Code after next() runs on the way out, so you can measure response time or tweak headers before the response goes back to the client.
You can stack middleware. Logger on '*', CORS on '/api/*', basic auth on '/admin/*' — each one runs in the order you registered it.
Headers
Every HTTP request and response carries HTTP response headers. Hono makes both sides easy.
Reading request headers is straightforward. Pass the header name to c.req.header():
app.get('/', (c) => {
const userAgent = c.req.header('User-Agent')
return c.text(userAgent ?? '')
})
Need every header on the request? Use c.req.raw.headers — it’s the underlying Headers object from the Web Fetch API.
To set a response header, use c.header():
c.header('Content-Type', 'text/html')
You can call it inside a route handler or in middleware after await next(). Multiple calls add multiple headers.
Cookies
Hono provides helpers for cookies in hono/cookie. Import what you need:
import { getCookie, setCookie, deleteCookie } from 'hono/cookie'
The context c is always the first argument. Set a cookie like this:
setCookie(c, 'username', 'Flavio')
Read it back:
const username = getCookie(c, 'username')
Delete it:
deleteCookie(c, 'username')
You can pass an options object as the fourth argument. The useful ones are httpOnly, path, expires, sameSite, and secure:
setCookie(c, 'session', 'abc123', {
path: '/',
httpOnly: true,
secure: true,
sameSite: 'Strict',
expires: new Date(Date.now() + 900000),
})
httpOnly keeps the cookie off client-side JavaScript. secure sends it only over HTTPS. sameSite controls cross-site behavior.
For cookies that hold sensitive data, use signed cookies:
import { getSignedCookie, setSignedCookie } from 'hono/cookie'
await setSignedCookie(c, 'token', 'user-42', 'my-secret')
const token = await getSignedCookie(c, 'my-secret', 'token')
setSignedCookie() and getSignedCookie() work like the regular helpers but add a signature. If someone changes the value in the browser, the signature won’t match and you can reject it.
Redirects
Redirects are common in web apps. Hono gives you c.redirect():
return c.redirect('/there')
The default status is 302. For a permanent redirect, pass 301:
return c.redirect('/there', 301)
Use 302 when the destination might change — login flows, temporary moves. Use 301 when the old URL is gone for good and search engines should update their index.
That’s the everyday toolkit. Middleware for cross-cutting logic, headers for metadata, cookies for session state, redirects for navigation. In the next post in this series we’ll deploy a Hono app to Cloudflare Workers.
Related posts about js: