
Express review
Minimal web framework for Node.js with routing, middleware and HTTP helpers
Verdict
Express gives you routing, middleware and HTTP helpers in a small core, with official middleware for bodies, CORS, sessions and uploads, and Express 5 now handles errors from async handlers. The catch is that it gives you routing and middleware only, so validation, OpenAPI docs, WebSockets, project structure and types all come from third-party packages that you have to pick and keep up to date.
Best for
Small APIs and teams that want a minimal core with a large middleware choice
Not for
Teams that want validation, OpenAPI docs and typed routes in the framework
Pros
- Official middleware for bodies, CORS, cookies, sessions, uploads and logging
- Express 5 passes errors from async handlers to the error handler
- Both the 5.x and 4.x lines still get releases
- Run under the OpenJS Foundation
Cons
- TypeScript types come from DefinitelyTyped, not the project
- No official validation, OpenAPI or WebSocket packages
- The official generator has not been released since 2019
Is Express right for your project?
Describe what you need and AI will check the fit against this review.
Express features and capabilities
Routing and middleware
Routes and middleware are plain functions chained in order, so the request flow is easy to follow and any part can be swapped.
Official middleware set
The Express team maintains middleware for body parsing, CORS, cookies, sessions, compression, file uploads and request logging.
Async error handling in Express 5
A thrown error or rejected promise in an async handler goes to the error middleware, so handlers no longer need try and catch around every await.
Codemods for the move to 5.x
Official codemods update route syntax and removed APIs, which shortens the upgrade from Express 4.
Template engines
Express can render server-side HTML with many template engines, so the same app can serve pages and JSON.
- TypeScript firstWritten in TypeScript or ships its own types, with typed routes or handlers
- PartlyTypes from @types/express
- Request validationValidates bodies, params or queries against a schema, in the core or an official package
- PartlyCommunity packages such as express-validator
- OpenAPI docsGenerates an OpenAPI spec or Swagger UI from routes
- PartlyCommunity packages only
- Beyond Node.jsOfficially runs on Bun, Deno or edge runtimes such as Cloudflare Workers
- NoNode.js only in the docs
- WebSocketsBuilt in or through an official plugin
- PartlyCommunity packages only
- Project structureModules, dependency injection or a fixed layout, not only routing
- No
- Database layerAn official ORM, query builder or data layer
- No
- AuthenticationAn official auth module, plugin or strategy system
- PartlyOfficial session middleware; Passport is third-party
At a glance
- Type
- Minimal web framework
- Stack
- JavaScript / Node.js 18+
Free vs paid editions
What you get when you self-host, and what needs a paid plan.
Express is free and has no paid edition or official hosting. It is an OpenJS Foundation project.
Running Express
Express 5 needs Node.js 18 or later, and TypeScript projects add @types/express.
New cloud server to ready
4 s
Memory in use at idle
75 MB of 4 GB
Reads per second
10,963
Writes per second
7,270
Startup
0.46 s
Version tested
5.3.0 · Oct 31, 2026
- Express has no validator of its own, so the app checks the body with Zod and writes the 400 response itself.
- Install took 4 seconds: 66 packages and 13 MB with Zod. Median latency was 4.4 ms for reads at 50 connections, 7.1 ms at the 99th percentile.
- A body with broken JSON returned an HTML page saying Bad Request. An API needs its own error handler to answer in JSON.
import express from "express";
import { z } from "zod";
// 100 items kept in memory, so the test measures the framework, not a database.
const items = new Map();
for (let i = 1; i <= 100; i++) items.set(i, { id: i, name: `Item ${i}`, price: i * 1.5, tags: ["sample"] });
let nextId = 101;
const Item = z.object({ name: z.string().min(1).max(100), price: z.number().min(0), tags: z.array(z.string()).max(10).optional() });
const app = express();
app.use(express.json());
app.get("/health", (req, res) => res.json({ status: "ok" }));
app.get("/items/:id", (req, res) => {
const item = items.get(Number(req.params.id));
if (!item) return res.status(404).json({ error: "Not found" });
res.json(item);
});
app.post("/items", (req, res) => {
const parsed = Item.safeParse(req.body);
if (!parsed.success) return res.status(400).json({ error: "Invalid item", issues: parsed.error.issues });
const item = { id: nextId++, ...parsed.data };
items.set(item.id, item);
res.status(201).json(item);
});
app.listen(Number(process.env.PORT ?? 3000), "127.0.0.1");{
"error": "Invalid item",
"issues": [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"name"
],
"message": "Too small: expected string to have >=1 characters"
},
{
"origin": "number",
"code": "too_small",
"minimum": 0,
"inclusive": true,
"path": [
"price"
],
"message": "Too small: expected number to be >=0"
}
]
}What you can do with Express
Small JSON APIs
Build a REST API with a few routes and the official body-parser, CORS and logging middleware, with little to learn.
Backends for existing Node.js apps
Add an API or webhook receiver to a project whose team already knows Express and its middleware.
Server-rendered pages
Serve HTML from a template engine alongside JSON endpoints in the same app.