What Express Adds

What Express Adds to Node's HTTP Server

An Express 24,430 application is a listener function. const app = express() returns a function of (req, res) you could hand to http.createServer yourself, and app.listen() is the convenience that does it. The sockets, the llhttp parser and the IncomingMessage/ServerResponse objects of An HTTP Server by Hand are unchanged; Express decorates those objects and inserts a dispatcher in front of your code.

Where Express sits on top of Node's HTTP server
Where Express sits on top of Node's HTTP server

This is the Express equivalent of the hand-written endpoint in An HTTP Server by Hand, plus a second route. No URL construction, no method comparison, no writeHead with a hand-computed Content-Length, no body chunks.

The same JSON endpoint in ExpressJavaScript
import express from 'express';
const app = express();
app.use(express.json());
const books = [{ id: 1, title: 'Node in Practice', authorId: 7 }];
app.get('/books/:id', (req, res) => {
  const book = books.find((b) => b.id === Number(req.params.id));
  book ? res.json(book) : res.status(404).json({ error: 'not found' });
});
app.post('/books', (req, res) => {
  const book = { id: books.length + 1, ...req.body };
  books.push(book);
  res.status(201).location(`/books/${book.id}`).json(book);
});
app.listen(3000, () => console.log('listening on http://localhost:3000'));

A curl 3,008 -i -X POST localhost:3000/books with a JSON -d body and a content-type header returns:

Output of 1
HTTP/1.1 201 Created
X-Powered-By: Express
Location: /books/2
Content-Type: application/json; charset=utf-8
Content-Length: 49
ETag: W/"31-PpkM77O+WL8dmUJcxpE3ZT5qLe8"
{"id":2,"title":"Express in Action","authorId":3}

Four of those headers you never wrote. res.json serialized the object, set Content-Type with the charset, computed Content-Length from the UTF-8 byte length, and hashed the body into a weak ETag so a repeat request carrying If-None-Match can be answered with 304 (ETags and Conditional Requests). res.status and res.location are chainable setters. On the request side req.params.id came from the :id segment, and req.body came from express.json(), which drained the stream, enforced a 100 kB default limit and rejected malformed JSON with a 400 before your handler ran. Remove X-Powered-By, which advertises your framework for no benefit, with app.disable('x-powered-by') or Helmet 10,736 (Security Headers with Helmet).