Hono foundations
Start from Web Standards
Use Request, Response, URL, Headers, and fetch as the mental model beneath Hono.
Hono sits on top of the same Request and Response objects you already know from the browser Fetch API. That is the whole portability story. Learn those objects first, and the helpers make sense.
In this course we build a small bookmarks API. Every route reads a standard Request through context and returns a Response-compatible value. When you move from Node to Cloudflare Workers, those objects stay the same even when the entry file changes.
The helper route uses Hono’s c.json():
app.get('/bookmarks', (c) => {
return c.json([{ id: '1', title: 'Hono docs', url: 'https://hono.dev' }])
})
The explicit route builds a Response by hand:
app.get('/health', () => {
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'content-type': 'application/json' }
})
})
Both paths produce the same kind of object. c.req is a real Request. You can inspect c.req.method, c.req.url, and c.req.header('accept') without any Hono-specific magic. c.json() is just a shortcut that sets Content-Type and serializes the body for you.
A common mistake is to memorize helper names and ignore what they wrap. The happy path still works, so the gap stays hidden until you deploy somewhere new or attach custom middleware that expects a raw Response.
Write one route with helpers and one with an explicit Response. Compare status, headers, and body side by side. Change the Accept header and watch how the response metadata shifts. You should see the same JSON either way, with content-type: application/json set automatically on the helper path.
If you forget to return a response, Hono sends a 404. I hit that often when I refactor a handler and leave a bare c.json(...) on its own line without return. The client sees “Not Found” even though your handler ran.
Headers behave like the Fetch API too. c.header('cache-control', 'no-store') adds to the outgoing response before you return. That mirrors new Response(body, { headers: { ... } }).
Try this on your own project: add a /health route that returns JSON with an explicit Response, then rewrite it with c.json() and diff the outgoing headers.
Lesson completed