Last updated: 2026-09-23

Foundations & Setup

1.1 What Express.js Is and Why It Exists

Before Express existed, building a web server in Node.js meant doing everything yourself.

Node.js gave developers the ability to run JavaScript on the server — a genuinely revolutionary idea in 2009. But raw Node came with raw responsibilities. The native http module let you create a server, but it didn't care about your routing, your request parsing, or your response formatting. It handed you the lowest-level tools and stepped back.

Here's what a basic server looked like without Express:

const http = require("http");

const server = http.createServer((req, res) => {
  if (req.url === "/" && req.method === "GET") {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("Hello World");
  }
  // Every new route means another if/else branch...
});

server.listen(3000);

It works. But notice what's happening: you're manually checking the URL, manually checking the HTTP method, manually setting headers. Add ten more routes and you have a tangled chain of conditionals. Add middleware concerns like authentication or logging and the whole thing collapses under its own weight.

Express was created in 2010 by TJ Holowaychuk to solve exactly this problem. Inspired by Ruby's Sinatra framework, Express took the raw power of Node's HTTP module and wrapped it in a layer thin enough to stay out of your way, but thick enough to handle the repetitive parts .

The same server with Express:

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

app.get("/", (req, res) => {
  res.send("Hello World");
});

app.listen(3000);

The difference isn't just fewer lines. It's a different way of thinking. Instead of imperatively checking conditions, you declare intent: "when someone hits this route with this method, do this." Express handles the rest.

The Decision That Shaped Everything

In 2010, Express merged with a project called Connect, which had built a middleware architecture for Node.js . That merger was the pivot point. It gave Express its defining feature — the middleware pipeline — and set the framework on a path that would define it for the next fifteen years.

We'll cover middleware in depth later, but the key point here is: Express's design is not arbitrary. Every choice traces back to a specific problem someone was trying to solve. When you understand the problems, the design stops looking like arbitrary rules and starts looking like the only sensible answer.

Minimal by Conviction, Not by Accident

Express is often described as "unopinionated." That word can sound like a limitation, but it's closer to a philosophy.

Express refuses to make decisions for you. It won't tell you how to structure your folders, which database to use, or what templating engine to pick. That restraint is intentional — and it's the reason Express can fit almost anywhere. A small API and a massive enterprise application can both use Express without fighting the framework's assumptions.

What Express does provide is a tight set of essentials:

  • Routing (matching URLs to handlers)
  • Middleware management (composing reusable logic)
  • Request and response helpers (making common operations clean)
  • Template engine integration (rendering dynamic HTML)

Everything else — authentication, validation, database access, logging — comes from the ecosystem. This makes Express both lightweight and endlessly extensible. It also means you'll spend more time choosing your own tools than you would with a more opinionated framework. That's the trade-off, and it's a deliberate one.

1.2 A Brief History of Express

Understanding where Express came from explains a lot about why it works the way it does.

PeriodMilestone
2010TJ Holowaychuk releases Express, inspired by Sinatra
2010Connect merger introduces middleware architecture
2014StrongLoop acquires management rights
2015IBM acquires StrongLoop
2016Express placed under Node.js Foundation stewardship
2024Express 5.0 released after ten years of development
2025Express 5.2.1 stable release

The Connect merger in 2010 is the line that matters most. It gave Express its middleware pipeline, and that pipeline became the framework's identity. Everything else — the routing system, the request/response helpers, the error handling — builds on top of that foundation.

The long gap between Express 4 and Express 5 tells its own story. Ten years of development on a major version isn't indecision — it's the difficulty of evolving a framework that half the Node ecosystem depends on without breaking anyone. Express 5 finally shipped in October 2024, and the current stable release is 5.2.1 .

1.3 Setting Up a Project

Prerequisites

Express 5.x requires Node.js 18 or higher . Verify your installation:

node -v  # Should output v18.x.x or higher
npm -v   # npm is bundled with Node.js

Step-by-Step Setup

# 1. Create project directory
mkdir my-express-app
cd my-express-app

# 2. Initialize package.json
npm init -y

# 3. Install Express
npm install express

npm init -y generates a default package.json that tracks your project's metadata and dependencies. Installing Express pulls in the framework and its dependency tree into node_modules .

A Minimal Working Server

Create app.js:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(port, () => {
  console.log(`Server running at <http://localhost>:${port}`);
});

Run it:

node app.js

Visit http://localhost:3000 and you'll see "Hello World!" .

For development, Node's --watch flag (available in Node 18.11+) restarts the server automatically when files change:

node --watch app.js

This removes the need for third-party tools like nodemon in most cases.

1.4 TypeScript Support

Express is written in JavaScript and does not ship its own TypeScript types. The Express team has stated there are no plans to rewrite the library in TypeScript .

TypeScript users install community-maintained types from DefinitelyTyped:

npm install --save-dev typescript @types/express @types/node

A minimal tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Express infers req, res, and next types when handlers are passed directly to route methods. Explicit annotations are only necessary for error-handling middleware, which has a different signature, and for handler functions defined separately from their route .

Note on native TypeScript support in Node.js: Node.js 22.18.0+ and 23.6.0+ can execute .ts files directly by stripping types, but this does not perform type-checking. Run npx tsc separately for validation .


The Express.js research series draws from the official Express.js documentation at expressjs.com