Most Express 4 apps should go to Express 5, and should do it this quarter. Move to Fastify only if you are already planning a large refactor of your request handling, and to Hono only if the app has to leave Node.js for Cloudflare Workers, Deno, or Bun's own server. The comparisons that rank for this search treat it as a greenfield question and benchmark a hello-world route in all three frameworks. That is useful if you are starting from an empty folder. If you have forty routes and a team that knows req and res by heart, the question you actually have is how much of that code each option makes you touch.

Why an Express 4 app has to move at all

Express 5.0 shipped in September 2024, after a decade with 4.x as the only major version anyone used. On March 31, 2025, Express 5.1 became the default latest tag on npm, which means a fresh npm install express now gives you version 5. The same announcement laid out a support plan in which October 1, 2026 was the earliest date 4.x would start winding down, with a note that 4.x is a special case and may get longer support.

That date has passed. The official support page still listed 4.x as supported when we checked, so nothing breaks this week, but every month you wait is another month in which new middleware releases and answers online assume version 5.

Express 5 vs Fastify vs Hono for an existing app

QuestionExpress 5Fastify 5Hono
Minimum Node.js182018.14.1 (via @hono/node-server)
Your Express middlewareMostly runs as-isRuns through @fastify/express, which its own README calls a transition toolDoes not run; different handler signature
Route handlersSmall edits, mostly path strings and response helpersRewritten around request, reply and JSON SchemaRewritten around a context object that returns a Response
Automated helpOfficial codemodsNone for handlersNone for handlers
Runs outside Node.jsBun, through Node compatibilityNode.js firstWorkers, Deno, Bun, Node.js, Lambda
What you get for the workAsync errors handled, safer routing, a supported versionSchema validation, fast serialization, scoped plugins, Pino loggingPortability and typed client calls through the RPC helper

Fastify's Node 20 floor surprises people. If your production image is still on Node 18, that is a second migration hiding inside the first one.

What actually breaks in the Express 5 upgrade

Route strings break first. Express 5 moved to a stricter path matcher, so a bare wildcard like app.get('/*') now needs a name ('/*splat', or '/{*splat}' if it should also match the root). Optional parameters moved into braces, so '/:file.:ext?' becomes '/:file{.:ext}'. Regex characters inside path strings are gone; pass an array of paths instead. Grep your router files for *, ? and ( before you do anything else.

The second category is behavior changes that throw no error. express.urlencoded() now defaults to extended: false, so nested form fields parse differently unless you set the option back. req.query uses the simple parser and is read-only. req.body is undefined rather than an empty object when no parser ran, which breaks every if (req.body.foo) check that used to fail softly. And express.static() stops serving dotfiles, so a /.well-known folder for ACME challenges or app links quietly starts returning 404 until you mount it with dotfiles: 'allow'.

The removed helpers are the easy part: app.del(), req.param(), res.sendfile(), and the old argument orders for res.send(body, status) and res.redirect(url, status). The Express team publishes codemods for most of them, and you can run the whole set with npx codemod@latest @expressjs/v5-migration-recipe. Run it on a clean branch and read the diff line by line. A codemod that changes a redirect's argument order is exactly the kind of edit that looks right in review and turns out wrong in production.

The payoff is real. Express 5 forwards a rejected promise from an async handler to your error middleware, which Express 4 never did. In a 4.x app, an await that throws without a try block leaves the request hanging until the client gives up. Nothing logs an error. That is what makes it so easy to ship.

When Fastify is worth the rewrite

Fastify is the bigger rewrite. Handlers take request and reply, routes declare JSON Schema for their input and output, and code is organized into plugins whose decorators and hooks stay scoped to the plugin that registered them. That last part pays off in a codebase several teams work in at once, because one team's hook can no longer change another team's routes.

The response schema deserves a warning. Fastify serializes replies through the schema you declare, and any field you did not declare is dropped from the output. That is great for not leaking a passwordHash column. It is miserable when someone adds a field to the database query, forgets the schema, and spends an afternoon wondering why the client never sees it.

The bridge is @fastify/express, and its README is blunt that it should not be a long-term solution. It runs Express middleware inside Fastify's onRequest hook, does not support HTTP/2, and documents that combining Express's body-parser with Fastify routes can hang POST requests with bodies. Use it to move one route group at a time. If the bridge is still in your package.json a year later, you have two frameworks and the advantages of neither.

Speed is usually the reason given for this move, and it is the weakest one. On the REST API test in our Bun vs Deno review, where PostgreSQL, JWT checks and JSON serialization ran on every request, swapping the entire runtime under an Express-compatible framework moved average response time from 4.8ms to 4.1ms. The database owned the rest. A framework swap sits higher up the stack than a runtime swap, and if your service spends its time waiting on Postgres, the framework's hello-world score will not show up on your latency graph.

When Hono is the right destination

Hono earns a rewrite when the app needs to run somewhere Express cannot follow. It is built on the web-standard Request and Response, so the same routes run on Cloudflare Workers, Deno, Bun and Node.js. If you are moving an API next to a frontend on the edge (our Vercel vs Cloudflare Pages comparison covers that hosting decision), Hono is the framework that comes along without adapters. Its RPC client also gives a TypeScript frontend typed calls into your routes with no code generation step, which is a better reason to switch than any benchmark.

The cost is that nothing from your Express middleware stack carries over. Hono middleware has a different shape, so each piece gets replaced with a Hono equivalent or rewritten. On Node.js you can still reach the raw IncomingMessage through c.env.incoming, but code that leans on that will not run on Workers, and portability was the whole reason you came. If the runtime question is still open, settle it first with our Bun vs Node.js vs Deno benchmark. Hono and Express both run on Bun already, so a runtime change does not force a framework change.

A note on Express 4 apps that never get touched

If the service is frozen, internal, and nobody has opened the repo in a year, the upgrade can wait for the next time someone has to change it anyway; just add npm outdated to that day's checklist.

Our pick for teams on Express 4

Upgrade to Express 5. Start with the path grep, run the codemods on a branch, fix urlencoded and static by hand, then delete the try/catch wrappers that only existed to call next(err). Budget a few days for a typical API, plus a careful review. It fixes the async error bug and gets you onto the version npm now installs by default.

Move to Fastify when you were going to rewrite the request layer anyway, for schema validation or because several teams need plugin boundaries. Do it route group by route group behind the bridge, and give the bridge a removal date.

Move to Hono when the deployment target is changing. Not before.

Whatever you pick, our full index of head-to-head developer tool reviews covers the pieces around it, from ORMs to API clients, and our testing methodology explains how we rate them.