Just added: Algorithms you can run and practice
Updated Jul 10, 2026 Edit on GitHub

What Express Is & Your First Server

You know JavaScript and want to put something on the web - an API a phone app or frontend can talk to. You could build it on Node's raw HTTP server (that guide is worth seeing once), but you'd rewrite the same plumbing every time: matching URLs, parsing bodies, sending JSON. That plumbing, packaged into something small and well-worn, is Express.

Express is deliberately tiny - a thin layer over node:http. It gives you two things: a clean way to route requests, and a middleware system for everything else. Body parsing, auth, logging, validation - none of that is built in; you add it as middleware when you need it. That minimalism is why over a decade of Node jobs, tutorials, and production servers run on it. It's the framework you're most likely to meet.

📝 Express - the dominant minimalist web framework for Node.js. Small core (routing + middleware), everything else bolted on. It sits on top of Node's HTTP server rather than replacing it. If you've read What a Framework Even Is, Express is the textbook case: small and unopinionated, staying out of your way.

The mental model: a pipeline of functions

💡 An Express app is a pipeline of (req, res, next) functions, and a route is one of them bound to a method and a path. A request flows through an ordered chain; each function can read the request, change it, send a response, or call next() to pass it along. Routing, body parsing, auth, error handling - all the same shape, different costumes. This chain is the heart of Express (full treatment in Phase 3). For now: request → functions → response.

A route is the simplest member of that family - a function bound to one HTTP method (GET) and one path (/). Let's build one.

Your first server

One install gets you the framework:

npm install express

What just happened: npm downloaded Express into node_modules and recorded it in package.json. This assumes you've run npm init -y first - do that if you haven't.

Now the smallest server that does something. Create index.js:

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

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

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

What just happened: four moves you'll use forever.

  • const app = express(); creates your application object - it holds your routes and middleware.
  • app.get('/', handler) registers a route: "when a GET request hits /, run this handler." You never call the handler yourself - Express calls it when a matching request arrives (the framework's "don't call us, we'll call you" relationship).
  • The handler gets (req, res) - the request and the response. res.send(...) writes the string as the response body and finishes the request, with Express setting sensible headers for you.
  • app.listen(3000, ...) starts the HTTP server on port 3000; the callback fires once it's up.

Run it with plain Node - Express is just a library, no special CLI:

node index.js
$ node index.js
listening on http://localhost:3000

Open a second terminal and hit it:

curl localhost:3000
$ curl localhost:3000
Hello from Express

What just happened: curl sent a GET / request, Express matched it to your route, called the handler, and sent back the string. A working web server in seven lines.

⚠️ The handler must end the request - exactly once. Every request needs a response. If your handler never calls res.send (or res.json, res.end), the client hangs until it times out. Call res.send twice and Node throws ERR_HTTP_HEADERS_SENT - you can't send a response that's already been sent. One request, one response.

📝 CommonJS vs ESM. The example uses require (CommonJS), the long-standing Node default. If your package.json has "type": "module", use import express from 'express'; instead - everything else is identical. We stick with require here so examples run on any setup.

Sending JSON, not just text

A string is fine for "hello world," but real backends speak JSON. Add a second route:

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

app.get('/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime() });
});

What just happened: res.json(obj) serializes an object to JSON and sets Content-Type: application/json automatically - that header is the real difference from res.send(JSON.stringify(obj)), which leaves you to set it yourself. res.send is the generalist (strings, Buffers, even objects, guessing the type); reach for res.json whenever you mean JSON.

Restart the server (Ctrl+C, then node index.js - no auto-reload yet, that's a later phase) and check the new route:

$ curl localhost:3000/health
{"status":"ok","uptime":4.21}

What just happened: the request matched the second route, Express called its handler, serialized the object, and sent it back with the JSON content type. Add a hundred routes and it's this idea a hundred times.

The running example: a tasks API

Across this guide we'll grow one real service so each concept lands on something concrete. Meet the tasks API - a small to-do backend where each task looks like this:

const tasks = [
  { id: 1, title: 'Learn Express routing', done: false },
  { id: 2, title: 'Understand middleware', done: false },
];

app.get('/tasks', (req, res) => {
  res.json(tasks);
});

What just happened: tasks is an in-memory array of objects. GET /tasks returns it as JSON - the first endpoint of an API we'll grow into full create/read/update/delete (CRUD). In-memory means data resets on restart; fine for learning, and we'll cover real storage later.

$ curl localhost:3000/tasks
[{"id":1,"title":"Learn Express routing","done":false},{"id":2,"title":"Understand middleware","done":false}]

You've now seen the entire shape of an Express endpoint - method, path, handler, response. Next: routes that carry data (a task's id in the URL, query strings, separate router files) - Phase 2: Routing.

Recap

  1. Express is the minimalist Node.js web framework - a thin layer over node:http. Small core (routing + middleware), everything else added on. Install with npm install express.
  2. The big idea: an Express app is a pipeline of (req, res, next) functions, and a route is one bound to a method + path. Routing, parsing, auth, and errors are all that same shape (Phase 3).
  3. A first server is four moves: express() creates the app; app.get(path, handler) registers a route; the handler gets (req, res); app.listen(port) starts it. Run with plain node index.js.
  4. res.send vs res.json: res.json(obj) serializes an object and sets Content-Type: application/json. Use it whenever you mean JSON.
  5. ⚠️ Every request needs exactly one response. Skip it and the client hangs; respond twice and Node throws ERR_HTTP_HEADERS_SENT.
  6. Our running example is a tasks API ({ id, title, done }), starting from GET /tasks and growing into full CRUD across the guide.

Quick check

Three questions on what has to stick - what Express is, how a first server is wired, and how to return JSON:

[
  {
    "q": "What is Express, in one line?",
    "choices": [
      "A minimalist web framework that's a thin layer over Node's built-in http module, giving you routing and a middleware system",
      "A standalone web server written in C that replaces Node entirely",
      "A database for storing JSON documents in Node apps",
      "A frontend UI library for building components in the browser"
    ],
    "answer": 0,
    "explain": "Express is the dominant minimalist Node.js web framework. It doesn't replace Node's HTTP server - it sits on top of node:http and adds clean routing plus a middleware system, leaving everything else to middleware you add."
  },
  {
    "q": "In `app.get('/', (req, res) => { ... })`, what does this line do and who calls the handler?",
    "choices": [
      "It registers a route for GET requests to '/', and Express calls the handler when a matching request arrives",
      "It immediately runs the handler once and caches the result",
      "It sends a GET request to '/' and returns the response",
      "It defines a route but you must call the handler yourself in app.listen"
    ],
    "answer": 0,
    "explain": "app.get(path, handler) registers a route. You never call the handler yourself - Express matches incoming requests by method and path and calls the handler for you. That's the framework's 'don't call us, we'll call you' relationship."
  },
  {
    "q": "What does `res.json(obj)` do that `res.send(JSON.stringify(obj))` does not?",
    "choices": [
      "It serializes the object to JSON AND automatically sets the Content-Type header to application/json",
      "It saves the object to a database before responding",
      "It validates that the object matches a schema",
      "Nothing - they are exactly identical in every way"
    ],
    "answer": 0,
    "explain": "Both produce a JSON string, but res.json also sets Content-Type: application/json automatically, so the client knows it's receiving JSON. With res.send(JSON.stringify(obj)) you'd have to set that header yourself. Reach for res.json whenever you mean JSON."
  }
]

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. What is Express, in one line?

2. In `app.get('/', (req, res) => { ... })`, what does this line do and who calls the handler?

3. What does `res.json(obj)` do that `res.send(JSON.stringify(obj))` does not?