KRYZO.

· Backend guide · 8 min read

Node.js and Express Routing

Express keeps a Node.js server organised. This guide covers routes, parameters and middleware.

Node.js server code defining Express routes and middleware

Node.js gives you a runtime for JavaScript on the server. Its built-in http module can serve requests, but you end up matching URLs and parsing bodies by hand. Express is a small framework that handles those chores so you can focus on route logic.

A server without and with Express

A bare Node.js server checks the request method and URL manually. Express replaces that plumbing with readable route definitions and a request object that is easier to work with.

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

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

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

Each route pairs a method and a path with a handler that receives the request and response objects.

GET and POST routes

Routes are grouped by HTTP method, which maps directly onto what the request does. A GET reads data, and a POST creates it after validating the body.

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

app.post("/notes", (req, res) => {
  const note = { id: nextId++, title: req.body.title };
  notes.push(note);
  res.status(201).json(note);
});

Returning the created record with a 201 status gives the client an identifier it can use immediately.

Route parameters and query strings

A route parameter is a named segment in the path, available on req.params. Query strings come after the question mark and appear on req.query.

app.get("/notes/:id", (req, res) => {
  const note = notes.find((item) => item.id === Number(req.params.id));
  if (!note) return res.status(404).json({ error: "Not found" });
  res.json(note);
});

// GET /search?q=express -> req.query.q === "express"

Use parameters to identify a specific resource and query strings for optional filters such as search terms or pagination.

Splitting routes with express.Router

As an app grows, keeping every route in one file becomes unwieldy. express.Router creates a mini application you can mount under a base path.

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

router.get("/", (req, res) => res.json(notes));

module.exports = router;

// app.js
app.use("/notes", require("./routes/notes"));

Each file owns one resource, so the project stays navigable even with many endpoints.

Middleware and why next matters

Middleware are functions that run before the final handler. Each receives the request, the response and next, and can inspect, modify or end the request.

app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

Calling next() passes control to the following middleware or route. Forget it and the request hangs, waiting forever. Middleware runs in the order it is registered, which is why logging and parsing usually appear near the top.

Parsing JSON bodies

Express does not parse request bodies by default. Add the built-in JSON parser so that req.body is populated for POST and PUT requests.

app.use(express.json());

Register it before the routes that read the body, and always validate what arrives rather than trusting the client.

Error-handling middleware

Four arguments

An error handler is recognised by its signature of four parameters, so keep all four even if unused.

Placed last

Register it after all routes and middleware so it catches anything they pass along.

Return a status

Respond with a clear status code and a safe message, and log the details on the server.

app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: "Internal server error" });
});

With routes, middleware and one clear error handler, an Express application stays easy to read and safe to extend.

Back to all blogs