PermissiveMITTested by Geekflare

Express review

Minimal web framework for Node.js with routing, middleware and HTTP helpers

Data refreshedMethodologyHow we tested
Director of Geekflare

Rity Anand is a director of Geekflare. She has extensive experience managing business technology and a special interest in emerging technologies.

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
Ask Sparkian

Is Express right for your project?

Describe what you need and AI will check the fit against this review.

Or try:

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.
app.js, after npm i express zod
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");
OutputPassed5.3.0 · 400, error body written by my handler from Zod's issues
Request: POST /items with {"name": "", "price": -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.