Files
What you'll learn
- Log every request and response with logger(), and route the lines somewhere with its print function
- Tag every request with requestId(): reuse the id a client sent, generate one otherwise, expose it on the response and on the context
- Prettify JSON on demand with prettyJSON() and its ?pretty query
Logging and request ids
The Basics course ended with an API that works. The first thing an API that runs for other people needs is not another route but a way to see what happened: which requests came in, what they got back, how long it took, and, when a client reports a problem, which of the thousand requests of that minute was theirs. Hono ships three small middleware for that, and this course opens with them because every later lesson's console will read better with them in place.
logger()
import { logger } from 'hono/logger';
app.use(logger());
app.get('/', (c) => c.text('Hello Hono!'));
Two lines per request: <-- GET / when it arrives and --> GET / 200 3ms when the response goes out, with the path including its query string and the time in milliseconds or, past a second, in seconds. On a terminal the status is coloured; the NO_COLOR environment variable turns that off, and platforms without process.env print plain text.
By default the lines go to console.log. logger(fn) takes a print function instead, (message, ...rest) => void, which is how the lines reach a log service, a file, or a ring buffer a debug route can show. The docs point out the same function is useful for your own lines, customLogger('Blog saved:', 'ID: 1'), so they land in the same place with the same shape.
requestId()
A log line alone cannot be matched to a client's complaint. An id that travels with the request can: the client sends it, every log line carries it, the response returns it.
import { requestId } from 'hono/request-id';
app.use('*', requestId());
app.get('/', (c) => c.text(`Your request id is ${c.get('requestId')}`));
The middleware reads X-Request-Id from the request. When the client sent one, that value is used; when it did not, crypto.randomUUID() makes one. Either way the id goes on the response as X-Request-Id and on the context as requestId, where handlers and later middleware read it with c.get(). An incoming value is only reused when it is short enough (limitLength, 255 by default) and made of letters, digits, _, - and =; anything else is replaced, so a client cannot inject arbitrary text into your logs through the header. headerName renames the header, and an empty string stops the middleware reading or writing it while still setting the variable; generator replaces the UUID, which is how a platform's own id, Cloudflare's ray id or AWS Lambda's request id, becomes the app's.
The type of c.get('requestId') comes from RequestIdVariables, exported by the module: new Hono<{ Variables: RequestIdVariables }>(), the same Variables generic as in Basics.
prettyJSON()
Compact JSON is right for programs and hard on people. prettyJSON() re-serialises any JSON response with two-space indentation when the request carries ?pretty, and leaves every other response alone:
import { prettyJSON } from 'hono/pretty-json';
app.use(prettyJSON());
app.get('/', (c) => c.json({ message: 'Hono!' }));
GET /?pretty answers the indented form. space changes the indentation, query the parameter name, and force: true prettifies every JSON response, which is convenient in development and a waste of bytes in production.
Order
All three are registered above the routes, and their order among themselves is the order of the onion from Basics: logger() first sees the request first and the response last, which is what a timing needs. requestId() before anything that logs means the id exists when the first line is written.
Your task
The cats API has a /log route that shows an empty array.
- Log every request and response with
logger(), keeping each line in thelogarray as well as printing it, so/logshows them. - Tag every request with
requestId(), reusing the client'sX-Request-Idwhen there is one. - Answer
GET /cats/:idwith the cat plus the request's id underrequestId. - Prettify JSON responses with
prettyJSON()when the request asks with?pretty.
Send GET /cats?pretty from the request bar, then GET /log.
When it fails
/logstays empty while the console shows the lines:logger()was given no function, so the lines went only toconsole.log. Pass a function that does both.- The response carries a different
x-request-idthan the one sent: the id sent contained a space or a character outside the allowed set, or was overlimitLength, and was replaced. c.get('requestId')is a type error: the app was created withoutRequestIdVariablesin its generic.GET /cats?prettyis still compact:prettyJSON()was registered after the route, or the response is notapplication/json.
Remember
logger()prints an incoming and an outgoing line per request;logger(fn)sends them elsewhere.requestId()reuses a valid incomingX-Request-Idor generates one, on the response and onc.get('requestId'); type the app withRequestIdVariables.prettyJSON()indents JSON when the request has?pretty.- Register all three above the routes; the logger first.
Stuck? Show a hint
logger(fn) calls fn(message, ...rest) for each line; a function that pushes the joined line into an array and also prints it does both jobs. requestId() reads X-Request-Id, sets the same header on the response and c.get('requestId'); type the app with RequestIdVariables. prettyJSON() acts on any JSON response when the request has ?pretty.
Press Run tests to start the app. Its log appears here.Graded endpoints
requestId() reuses the incoming X-Request-Id and puts it on the response
c.get('requestId') inside the handler is the same id, so a log line and a response can be matched
With ?pretty the same JSON is indented by two spaces; without it, compact
Still the 404 from Basics; the logger records the 404 like any status
logger()'s print function kept every line: an incoming arrow per request and an outgoing one with the status and the time