Building a ServerIntermediate9 min06 / 8

Express & Routing

Learn how Express turns raw Node.js into a pleasant web server — with clean routes, URL parameters, and helpful request/response tools.

In the last lesson you built a Node.js HTTP server from scratch. It worked — but look at that code again: you had to check req.url with a long chain of if statements just to handle a handful of different paths. Add ten more routes and things get messy fast. Express is the most popular Node.js framework precisely because it solves this problem. Instead of one giant request handler, Express gives you a clean, readable API where each route is its own self-contained line. The result looks less like plumbing and more like a table of contents for your server.

#Why Use a Framework at All?

A framework is just someone else's code that handles the tedious, repetitive parts so you can focus on what makes your app unique. Express wraps Node's built-in http module and adds:

  • Routing — map any HTTP method and URL pattern to a function
  • Middleware — small, chainable functions that process requests (more on this in the next lesson)
  • Convenience helpersres.json(), res.send(), res.status(), and more

Express deliberately stays small. It gives you the rails, not the whole train.

Think of it like

Express is like a post office sorting room

Imagine all incoming mail arriving in one giant pile (Node's raw http server). Without Express, you personally dig through every envelope to decide what goes where. With Express, there is a sorting room with labeled slots — one for letters addressed to /users, one for /products, one for /login. Each piece of mail lands in the right slot automatically, and you only write the handler for that slot.

#Installing Express

npm install express adds Express to your project's node_modules and records it in package.json.
# Create a project folder and initialise it
mkdir my-server && cd my-server
npm init -y

# Install Express
npm install express

#Your First Express Server

Four steps: require Express, create an app, define a route, start listening.
// server.js
const express = require("express");

const app = express(); // create the application

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

app.listen(3000, () => {
  console.log("Server running at http://localhost:3000");
});

Notice how much cleaner this is compared to the raw http version. app.get tells Express: "when someone sends a GET request to /, run this function." The function receives two objects — req (the incoming request) and res (the outgoing response) — and you call methods on res to send something back.

#GET and POST: The Two Routes You'll Use Most

HTTP has several methods (also called verbs) that describe what a client wants to do. The two you'll reach for constantly are:

  • GET — retrieve data (loading a page, fetching a list of items)
  • POST — send data to the server (submitting a form, creating a new record)

Express has a method for each: app.get() and app.post().

app.get and app.post handle the same path (/users) but respond to different HTTP methods.
app.use(express.json()); // allow Express to read JSON request bodies

app.get("/users", (req, res) => {
  // Respond with a JSON array
  res.json([{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]);
});

app.post("/users", (req, res) => {
  const newUser = req.body; // the JSON the client sent
  console.log("Creating user:", newUser);
  res.status(201).json({ created: true, user: newUser });
});
Note

What is res.json vs res.send?

res.send() is the general-purpose response helper — it accepts a string, a Buffer, or an object. res.json() is a specialised version that always serialises its argument to JSON and sets the Content-Type header to application/json automatically. When you are building an API, prefer res.json() so clients can rely on the content type being correct.

#Route Parameters: Dynamic URLs with :id

Real applications rarely serve just static paths. You need routes like /users/42 or /products/t-shirt-blue where part of the URL is variable. Express handles this with route parameters — segments prefixed with a colon.

Whatever appears in that position in the URL gets captured and placed on the req.params object.

Visit /users/42 and req.params.id equals "42". Visit /users/99 and it equals "99".
app.get("/users/:id", (req, res) => {
  const userId = req.params.id; // captured from the URL
  console.log("Requested user ID:", userId);

  // In a real app you'd look this up in a database
  res.json({ id: userId, name: "Alice" });
});
Common mistake

Route params are always strings

Even if the URL is /users/42, req.params.id is the string "42", not the number 42. If you need to use it as a number — for example, to look something up in an array by index — convert it first: const id = Number(req.params.id). Comparing "42" === 42 will always be false and can cause silent, baffling bugs.

#Putting It All Together

A minimal but complete REST API for a books collection — three routes, three distinct behaviours.
const express = require("express");
const app = express();

app.use(express.json());

const books = [
  { id: 1, title: "Dune" },
  { id: 2, title: "Neuromancer" },
];

app.get("/books", (req, res) => {
  res.json(books);
});

app.get("/books/:id", (req, res) => {
  const id = Number(req.params.id);
  const book = books.find((b) => b.id === id);
  if (!book) {
    return res.status(404).json({ error: "Book not found" });
  }
  res.json(book);
});

app.post("/books", (req, res) => {
  const newBook = { id: books.length + 1, ...req.body };
  books.push(newBook);
  res.status(201).json(newBook);
});

app.listen(3000, () => console.log("Listening on port 3000"));

Look at how each route reads almost like a sentence: "when someone GETs /books, send back all books"; "when someone GETs /books/:id, find that book and send it"; "when someone POSTs to /books, add the new one and return it." This is why Express became so popular — it brings your server's structure to the surface.

Quick check

A client sends a GET request to `/products/shirt-blue`. Your route is `app.get("/products/:slug", ...)`. What is `req.params.slug`?

Tip

Use app.listen last, define routes first

Express registers routes in the order they appear in your file. app.listen simply starts accepting connections — it does not block or reorder anything. The convention is to define all your routes first, then call app.listen at the very end. That way the file reads top-to-bottom like a menu: here are the things this server can do, and here is where it opens its doors.

Key takeaways

  • Express wraps Node's built-in HTTP module to give you clean, readable routing instead of one giant if-chain.
  • `app.get()` and `app.post()` map an HTTP method plus a URL path to a handler function.
  • Route parameters like `:id` capture variable URL segments onto `req.params` — always as strings.
  • `res.json()` serialises data to JSON and sets the correct Content-Type header; prefer it over `res.send()` when building APIs.
  • `res.status(code)` lets you set HTTP status codes like 201 (Created) or 404 (Not Found) before sending the response.
Practice challenges
Test yourself · earn XP
0/4
Predict the output#1

A client sends a GET request to /users/42. What does this route print to the console?

predict-output
app.get("/users/:id", (req, res) => {
  const userId = req.params.id;
  console.log(userId);
  console.log(typeof userId);
  res.json({ id: userId });
});
Fix the bug#2

This route should return the book whose id matches the URL, but it always returns 404 even for /books/1. What's wrong?

fix-bug
const books = [{ id: 1, title: "Dune" }];

app.get("/books/:id", (req, res) => {
  const id = req.params.id;
  const book = books.find((b) => b.id === id);
  if (!book) {
    return res.status(404).json({ error: "Book not found" });
  }
  res.json(book);
});
Fill in the blank#3

Complete the response so it sends the user object back as JSON with an HTTP 201 (Created) status code.

app.post("/users", (req, res) => {
  const newUser = req.body;
  res.status(201).(newUser);
});
Reorder the lines#4

Put these lines in the right order to build a minimal Express server that responds on the / route.

1
app.get("/", (req, res) => res.send("Hello from Express!"));
2
const express = require("express");
3
const app = express();
4
app.listen(3000, () => console.log("Server running"));
Your turn
Practice exercise

Build a tiny Express API for a to-do list. It needs three routes: GET /todos returns all todos as JSON; GET /todos/:id returns a single todo by its numeric id, or a 404 JSON error if not found; POST /todos reads req.body.task and adds a new todo to the list, responding with 201 and the created todo. Start with the two seed todos already in the starter code.

Try it yourself — a starting point to build on:

starter.js
const express = require("express");
const app = express();

app.use(express.json());

const todos = [
  { id: 1, task: "Learn Express" },
  { id: 2, task: "Build an API" },
];

// TODO: GET /todos — respond with the full todos array

// TODO: GET /todos/:id — find by numeric id, return 404 if missing

// TODO: POST /todos — read req.body.task, push a new todo, respond 201

app.listen(3000, () => console.log("Listening on port 3000"));