Built-in middleware
Much middleware does not need to be written by hand: it already ships with the hono package and is attached in one line. Nothing extra to install; each one is imported from its own subpath, such as hono/logger.
logger
Prints every request and response to the terminal:
// src/index.ts
import { logger } from 'hono/logger'
app.use(logger())After a request to /posts the terminal shows two lines: <-- GET /posts on the way in and --> GET /posts 200 3ms on the way out. Attach it first so that it sees every request.
cors
A browser does not let a page from one domain read API responses from another domain unless the server explicitly allows it. This rule is called CORS. If the blog frontend runs on http://localhost:5173 and the API on port 3000, you need permission:
// src/index.ts
import { cors } from 'hono/cors'
app.use('/posts/*', cors({ origin: 'http://localhost:5173' }))Now fetch('http://localhost:3000/posts') from a page on port 5173 gets the data. Calling cors() without options allows any domain — do that only for public APIs.
secureHeaders
Adds a set of security headers such as X-Content-Type-Options: nosniff and X-Frame-Options:
// src/index.ts
import { secureHeaders } from 'hono/secure-headers'
app.use(secureHeaders())curl -i http://localhost:3000/posts shows these headers in the response.
etag
An ETag is a fingerprint of the response content. The browser sends it with the next request, and if the data has not changed, the server answers with a short 304 code and no body:
// src/index.ts
import { etag } from 'hono/etag'
app.use('/posts/*', etag())The response gets an etag header, and repeated browser requests receive 304.
basicAuth
Simple protection with a username and password. The browser shows a sign-in dialog itself:
// src/index.ts
import { basicAuth } from 'hono/basic-auth'
app.use('/admin/*', basicAuth({ username: 'admin', password: 'secret' }))
app.get('/admin', (c) => c.text('Control panel'))Open http://localhost:3000/admin and the browser asks for a username and password. Without them the response is 401. For an API with users JWT fits better; it has its own lesson.
Summary
| Middleware | Import | Used for |
|---|---|---|
logger() | hono/logger | request log |
cors() | hono/cors | access from other domains |
secureHeaders() | hono/secure-headers | security headers |
etag() | hono/etag | caching with code 304 |
basicAuth() | hono/basic-auth | sign-in with username and password |
prettyJSON() | hono/pretty-json | indented JSON with ?pretty |
Common mistake
Writing middleware with an address as app.use('/admin', ...) and being surprised that /admin/posts is not protected. An address without an asterisk matches only /admin itself. To cover everything inside, write /admin/* — in Hono this pattern matches both /admin and the nested addresses.