Skip to content
Express.js ex getting-started 5 min read

What Is Express.js?

Express.js is a minimal, unopinionated web framework for Node.js. It sits directly on top of Node’s built-in http module and gives you a clean API for routing requests, composing middleware, and shaping responses — without dictating how you structure the rest of your application. First released in 2010, it remains the most widely used Node.js server framework and the foundation that countless other tools (and a few full frameworks) are built on. This page explains what Express is, the philosophy behind it, what you can build with it, and how Express 4 relates to the newer Express 5.

A thin layer over Node’s http module

Node ships with a low-level http module that can already run a server. The problem is that it is too low-level for real applications: you parse URLs by hand, branch on req.method and req.url, set headers manually, and serialize every response yourself.

const http = require("http");

const server = http.createServer((req, res) => {
  if (req.method === "GET" && req.url === "/hello") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ message: "Hello" }));
  } else {
    res.writeHead(404);
    res.end("Not Found");
  }
});

server.listen(3000);

Express wraps that same machinery and turns it into a declarative, expressive API. The equivalent below is shorter, reads top to bottom, and handles content types and status codes for you.

const express = require("express");
const app = express();

app.get("/hello", (req, res) => {
  res.json({ message: "Hello" });
});

app.listen(3000, () => console.log("Listening on http://localhost:3000"));

Output:

Listening on http://localhost:3000

A GET /hello request now returns:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"message":"Hello"}

Minimal and unopinionated

Express deliberately does very little on its own. It provides routing, middleware, and a thin layer of request/response helpers — and stops there. It does not pick a database, an ORM, a template engine, a validation library, or a project layout for you. Those decisions are yours.

That is what “unopinionated” means in practice: Express hands you a few sharp primitives and gets out of the way. The trade-off is freedom with responsibility — you assemble the pieces your app needs, which keeps the core tiny and flexible but means a production app combines Express with several supporting libraries.

Middleware: the central concept

The single idea that powers Express is middleware — functions that run in order for every matching request. Each middleware receives the request (req), the response (res), and a next function. It can read or modify req/res, end the response, or call next() to pass control to the next function in the chain.

const express = require("express");
const app = express();

// Built-in middleware: parse JSON request bodies
app.use(express.json());

// Custom middleware: log every request
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
});

// Route handler is just the final middleware in the chain
app.post("/users", (req, res) => {
  res.status(201).json({ id: 1, name: req.body.name });
});

app.listen(3000);

This request → middleware → response pipeline is what makes Express composable. Authentication, logging, body parsing, CORS, error handling — each is just another middleware you app.use().

Organizing with the Router

As an app grows, you split routes into modular pieces using express.Router(). A router is a mini-application with its own middleware and routes that you mount under a base path.

// routes/users.js
const express = require("express");
const router = express.Router();

router.get("/", async (req, res) => {
  const users = await db.findAllUsers();
  res.json(users);
});

router.get("/:id", async (req, res) => {
  const user = await db.findUser(req.params.id);
  if (!user) return res.status(404).json({ error: "Not found" });
  res.json(user);
});

module.exports = router;
// app.js
const usersRouter = require("./routes/users");
app.use("/users", usersRouter); // GET /users, GET /users/:id

Tip: Modern handlers can be async. In Express 4 you must wrap async errors and pass them to next(); in Express 5, rejected promises are forwarded to your error handler automatically.

What you can build

Because Express is a general-purpose HTTP toolkit, it scales from a one-file script to a large backend:

  • REST and JSON APIs — the most common use case, serving mobile and single-page-app frontends.
  • Server-rendered web apps — paired with a template engine like EJS, Pug, or Handlebars.
  • Backend-for-frontend gateways — aggregating and proxying calls to internal services.
  • Webhooks and integrations — small endpoints that receive and process events.
  • The base of larger frameworks — tools like NestJS can run on Express under the hood.

Express 4 vs Express 5

Express 4 was the stable standard for nearly a decade. Express 5 became the default npm install express release in 2024 and modernizes the framework while keeping the same familiar API.

AspectExpress 4Express 5
Async error handlingManual try/catch + next(err)Rejected promises auto-forwarded
Path patternsLoose regex-style routesStricter, safer path matching
Node.js supportOlder Node versionsNode 18+
Deprecated APIsMany still presentRemoved for a cleaner surface
MigrationMostly drop-in for typical apps

For new projects, start on Express 5. For existing Express 4 apps, the upgrade is usually small, and most route and middleware code carries over unchanged.

Best Practices

  • Keep route handlers thin — push business logic into separate service modules and let routes handle HTTP concerns.
  • Split routes into routers by resource and mount them with app.use() to keep app.js small.
  • Always register express.json() (and other parsers) before the routes that depend on them.
  • Add a dedicated error-handling middleware ((err, req, res, next) => {}) as the last middleware in the stack.
  • Prefer Express 5 for new apps so async errors are handled for you, and pin your Express major version in package.json.
  • Treat Express as a small core and add only the ecosystem libraries (validation, auth, CORS) your app actually needs.
Last updated June 14, 2026
Was this helpful?