Express.js was created in 2010. That’s 16 years ago. Your framework is old enough to get a driver’s license.
There are better options now. Let me show you what I switched to and why I’m never going back.
What’s Actually Wrong with Express.js?
Look, I’m not here to trash a framework that served us well. But if you’re starting a new project today, you need to understand what you’re signing up for.
Problem 1: No Native TypeScript Support
Express was built for JavaScript in a pre-TypeScript world. In 2026, TypeScript is the default for serious Node.js backend work, and Express still treats it like an afterthought. You install @types/express, and you get type definitions that are community-maintained and perpetually incomplete.
Compare an Express route handler with a Hono equivalent:
express-route.ts
// Express — bolted-on types, no inference
app.get("/user/:id", (req: Request, res: Response) => {
const id = req.params.id; // string | undefined
res.json({ id });
});
hono-route.ts
// Hono — TypeScript-first, full inference
app.get("/user/:id", (c) => {
const id = c.req.param("id"); // string inferred
return c.json({ id });
});
It’s a small example, but multiply that friction across a real codebase. Hono, Elysia, and Fastify were designed for TypeScript from day one. Express was retrofitted — and it shows.
Problem 2: Middleware Hell
The (req, res, next) pattern seemed elegant in 2012. In practice, it creates deeply nested chains where one forgotten next() call silently hangs your server. Error handling is bolted on with a special four-argument function signature (err, req, res, next) that feels like a hack because, well, it is one.
Express.js middleware debugging is the kind of problem that makes you question your career choices at 2 AM. Modern frameworks use plugin systems (Fastify), functional composition (Hono), or declarative hooks (Elysia) that are significantly easier to reason about.
Problem 3: Performance
This is where the Express.js performance issues become impossible to ignore. Here are realistic benchmark numbers for a simple JSON serialization test:
Source — https://fastify.dev/benchmarks
https://fastify.dev/benchmarks/
In an Express.js vs Fastify benchmark, Fastify handles 5x the throughput.
For a hobby project? Doesn’t matter. For a production API handling real traffic? That’s the difference between one server and four.
The Modern Replacements
So if you’re ready to migrate from Express, what do you actually pick? There’s no single answer — it depends on your runtime, your team, and your deployment target. Here are the three Express alternative frameworks I’d recommend in 2026.
1. Fastify — “Express, But Actually Good”
Best for: Teams migrating from Express who want a familiar feel with modern performance.
Fastify is the gentlest on-ramp. It keeps the request/response mental model you already know but adds built-in JSON schema validation, a proper plugin system, and that 3x performance bump you saw above. It’s a modern Node.js framework that respects your existing knowledge while pushing you forward.
import Fastify from "fastify";
const app = Fastify({ logger: true });
app.get("/hello", async (request, reply) => {
return { message: "Hello, Fastify" };
});
app.listen({ port: 3000 });
Choose Fastify when: You want Express-like patterns without Express-era baggage.
2. Hono — “The Edge-Native Framework”
Best for: Serverless and edge computing — Cloudflare Workers, AWS Lambda, Deno Deploy.
Hono’s superpower is portability. It runs on Node, Deno, Bun, and Cloudflare Workers with the same code. The entire framework is ~14KB. If you’re building a TypeScript Node.js backend that might need to run anywhere, Hono is the safest bet
import { Hono } from "hono";
const app = new Hono();
app.get("/hello", (c) => {
return c.json({ message: "Hello, Hono" });
});
export default app;
Choose Hono when: You’re building serverless/edge APIs or want maximum runtime portability.
3. Elysia — “The Speed Demon”
Best for: Bun runtime users who want the absolute fastest framework available.
Elysia is purpose-built for Bun, and it shows. End-to-end type safety, the fastest benchmarks in the ecosystem, and built-in Swagger documentation out of the box. If you’ve already committed to Bun, Elysia is the obvious choice.
import { Elysia } from "elysia";
new Elysia()
.get("/hello", () => ({ message: "Hello, Elysia" }))
.listen(3000);
Choose Elysia when: You’re on Bun and want screaming-fast performance with zero compromises on DX.
Migration in 5 Minutes: Express → Hono
Talk is cheap. Let me show you a real before-and-after.
Here’s a basic REST API in Express with a couple routes, middleware, and error handling:
// Express.js — the "classic" way
const express = require("express");
const app = express();
app.use(express.json()); // manual body parsing
// auth middleware — hope you don't forget next()
app.use((req, res, next) => {
const token = req.headers.authorization;
if (!token) return res.status(401).json({ error: "Unauthorized" });
req.user = verifyToken(token);
next();
});
app.get("/posts", (req, res) => {
const posts = getPostsByUser(req.user.id);
res.json(posts);
});
app.post("/posts", (req, res) => {
const post = createPost(req.user.id, req.body);
res.status(201).json(post);
});
// error handler — special 4-arg signature, easy to forget
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});
app.listen(3000);
Now the same API in Hono:
// Hono — TypeScript-first, no middleware guessing games
import { Hono } from "hono";
import { bearerAuth } from "hono/bearer-auth";
const app = new Hono();
// declarative auth — no next(), no silent failures
app.use("/*", bearerAuth({ verifyToken }));
app.get("/posts", async (c) => {
const user = c.get("user");
return c.json(await getPostsByUser(user.id));
});
app.post("/posts", async (c) => {
const user = c.get("user");
const body = await c.req.json();
return c.json(await createPost(user.id, body), 201);
});
// built-in error handling via onError
app.onError((err, c) => c.json({ error: err.message }, 500));
export default app;
Less code. Better types. 5x faster. Same result.
When You SHOULD Still Use Express
I’d lose all credibility if I didn’t say this: there are legitimate reasons to stick with Express.
If you have a legacy project with deep dependencies on Express-specific middleware — Passport strategies, custom middleware stacks, years of battle-tested plumbing — a migration might cost more than it saves. Don’t rewrite for the sake of rewriting.
If your entire team knows Express and the migration cost clearly outweighs the benefit, stay put. Ship features, not framework migrations.
And if you’re building a quick prototype where framework choice genuinely doesn’t matter? Use whatever gets you to production fastest.
Express isn’t dead — it’s just not the right default choice anymore. In 2026, you should have to justify choosing Express, not the other way around.
The Node.js ecosystem has grown up. It’s time our backend choices grew up with it.
If you found this helpful, follow me for more honest takes on modern web development. I write about AWS, AI, and full-stack engineering every week.
Comments
Loading comments…