Tips for Porting an Express App to Hono
If you maintain an Express API and you’re considering a move to Hono, the first thing worth understanding is that this isn’t a find-and-replace migration. Express is built on Node’s http module and its own req/res objects. Hono is built on the Fetch API and Web Standards. That difference shapes everything: routing, middleware, request handling, and responses.
Here’s what you need to know before you start.
Key Takeaways
- Express and Hono rest on fundamentally different foundations: Node’s
httpmodule versus the Fetch API and Web Standards. - Hono replaces Express’s separate
reqandresobjects with a single context objectc, and handlers must return aResponse. - Middleware signatures differ — Express uses
(req, res, next)while Hono uses(c, next)with an awaitednext(). - Body parsing happens directly inside Hono handlers rather than through global middleware.
- An incremental migration, starting with stateless JSON routes, is far safer than a full rewrite.
Understand the Architectural Difference First
Express wraps Node’s IncomingMessage and ServerResponse. Every req and res object is Node-specific. Hono, by contrast, works with standard Request and Response objects — the same ones you’d use in a browser or a Cloudflare Worker.
When running Hono on Node.js, you use the @hono/node-server adapter, which bridges the gap. But the handler itself stays runtime-agnostic.
// Express
app.get('/users/:id', async (req, res) => {
const user = await db.findById(req.params.id)
res.json(user)
})
// Hono
app.get('/users/:id', async (c) => {
const user = await db.findById(c.req.param('id'))
return c.json(user)
})
The shape is similar, but c (the context object) replaces both req and res. You return a Response rather than calling methods on res.
Migrating Node.js APIs to Hono: Start with Stateless Routes
The safest place to begin porting Express apps to Hono is with stateless, JSON-only routes — no file system access, no Node-specific streams, no session middleware. These translate cleanly.
Routes that depend on Node-specific APIs (req.socket, res.locals, res.sendFile) need more thought. Isolate those first so you know what you’re dealing with before committing to a full migration.
Discover how at OpenReplay.com.
Hono Middleware Migration: Don’t Assume Compatibility
Express middleware follows the (req, res, next) pattern. Hono middleware uses (c, next) and typically calls await next(). They are not interchangeable.
// Express middleware
app.use((req, res, next) => {
req.startTime = Date.now()
next()
})
// Hono equivalent
app.use(async (c, next) => {
c.set('startTime', Date.now())
await next()
})
For common packages, Hono has official equivalents:
| Express | Hono Equivalent |
|---|---|
cors() | hono/cors |
helmet() | hono/secure-headers |
express.json() | Built-in via c.req.json() |
morgan | hono/logger or a custom middleware |
Rewrite middleware intentionally rather than trying to wrap Express middleware — the abstraction rarely holds cleanly.
Request Body and Response Handling
In Express 5, body parsing is still middleware-based. In Hono, you parse the body directly in the handler:
// Hono body parsing
app.post('/items', async (c) => {
const body = await c.req.json()
return c.json({ received: body }, 201)
})
Responses are always returned, never mutated. There’s no res.status(201).json(...) — instead you pass the status as a second argument to c.json().
Error Handling
Express uses a four-argument error handler (err, req, res, next). Hono uses app.onError:
app.onError((err, c) => {
console.error(err)
return c.json({ error: 'Internal Server Error' }, 500)
})
Migrate Incrementally, Not All at Once
A full rewrite rarely goes smoothly. A better approach:
- Port one isolated route group at a time.
- Run Hono alongside Express during the transition.
- Rewrite middleware explicitly — don’t wrap it.
- Leave Node-specific dependencies (file handling, legacy auth) for last.
Conclusion
Porting Express apps to Hono is straightforward once you accept that the two frameworks rest on different foundations. The routing syntax is familiar enough that most routes translate in minutes. The real work lies in middleware and anything that touches Node-specific APIs directly. Approach those deliberately, and the migration becomes manageable.
FAQs
Yes. A common pattern is to put a reverse proxy or a small Node entry point in front of both, routing specific paths to the Hono app and leaving the rest with Express. This lets you migrate route groups one at a time and roll back quickly if something breaks, rather than committing to a single risky cutover.
Hono runs on Node.js, Bun, Deno, Cloudflare Workers, AWS Lambda, and several other runtimes. On Node, you use the @hono/node-server adapter to bridge the Fetch API model with Node's http module. The same handler code remains portable across runtimes, which is one of Hono's main advantages over Express.
Generally, no. Express middleware depends on Node's req and res objects and the next callback, while Hono middleware operates on a Fetch-style context. Some community adapters exist, but they tend to be brittle. The recommended approach is to rewrite middleware against Hono's API, since most common needs already have official or built-in equivalents.
Hono is generally faster than Express, particularly for routing and JSON responses, thanks to its lightweight router and Fetch-based design. Real-world gains depend on your workload — database queries and external calls usually dominate. Treat performance as a useful side effect of migrating, not the primary reason, unless your benchmarks show routing overhead is a real bottleneck.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.