Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

MiddlewareImportUsed for
logger()hono/loggerrequest log
cors()hono/corsaccess from other domains
secureHeaders()hono/secure-headerssecurity headers
etag()hono/etagcaching with code 304
basicAuth()hono/basic-authsign-in with username and password
prettyJSON()hono/pretty-jsonindented 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.

Thanks for reading!

Donate to the site — we work hard for you.