Chapter 13 of 18 · Node.js for Beginners — Complete Free Tutorial

Routes and Route Parameters

Every API is a map of URLs to behaviour. The Node.js for Beginners series now builds that map with Express: defining routes for each HTTP method, extracting ids from paths with parameters, reading filters from query strings, and validating bodies — while laying the first stones of the to-do REST API you will finish in chapter 18.

Route anatomy

A route pairs a method and a path with a handler. The handler receives the request, writes the response, and Express takes care of everything else.

app.get('/api/todos', (req, res) => {
  res.json({ todos: [] });
});

app.post('/api/todos', (req, res) => {
  res.status(201).json({ id: 1, title: 'Learn routes', done: false });
});
curl -s http://localhost:3000/api/todos
curl -s -X POST http://localhost:3000/api/todos \
  -H 'Content-Type: application/json' -d '{"title":"Learn routes"}'

Expected output:

{"todos":[]}
{"id":1,"title":"Learn routes","done":false}

The verbs map to the methods from chapter 10: app.get, app.post, app.put, app.patch, app.delete. Express 5 also accepts app.route(path) to group several methods for one path without repeating it.

Route parameters

A colon marks a placeholder — a parameter — which Express extracts into req.params.

app.get('/api/todos/:id', (req, res) => {
  const { id } = req.params;               // always a string
  if (id !== '1') return res.status(404).json({ error: 'todo not found' });
  res.json({ id: 1, title: 'Learn routes', done: false });
});

app.get('/api/todos/:id/comments/:slug', (req, res) => {
  res.json(req.params);                    // both parameters together
});
curl -s http://localhost:3000/api/todos/1
curl -s http://localhost:3000/api/todos/1/comments/first

Expected output:

{"id":1,"title":"Learn routes","done":false}
{"id":"1","slug":"first"}
  • Parameters arrive as strings — convert with Number() or validate with a regex (/^\d+$/) before using them as numbers.
  • Express decodes percent-escapes for you, so /todos/a%20b yields 'a b'.
  • A trailing wildcard such as app.get('/files/*', …) changed in Express 5 — use app.use('/files', …) or a named splat to stay version-proof.

Query strings

Everything after ? is optional filtering — never part of the path identity.

app.get('/api/todos', (req, res) => {
  const { done, limit = '10' } = req.query;
  let results = todos;
  if (done !== undefined) results = results.filter((t) => String(t.done) === done);
  res.json({ count: results.length, limit: Number(limit), todos: results.slice(0, Number(limit)) });
});
curl -s 'http://localhost:3000/api/todos?done=false&limit=5'

Expected output: {"count":2,"limit":5,"todos":[…]} — an object with the filtered rows. Values arrive as strings (or arrays for repeated keys), so coerce and bound them: never trust limit to be a sane number from a public caller.

Reading and validating the body

With express.json() registered, POST and PATCH bodies land on req.body. Validation stays your job — and the cheapest place to fail is the start of the handler:

app.post('/api/todos', (req, res) => {
  const { title, done = false } = req.body ?? {};

  if (typeof title !== 'string' || title.trim().length === 0) {
    return res.status(400).json({ error: 'title must be a non-empty string' });
  }
  if (typeof done !== 'boolean') {
    return res.status(400).json({ error: 'done must be a boolean' });
  }

  const todo = { id: nextId++, title: title.trim(), done };
  todos.push(todo);
  res.status(201).location(`/api/todos/${todo.id}`).json(todo);
});

Expected output: a valid body answers 201 with a Location header pointing at the new resource; a missing title answers 400 with a machine-readable message. Return early on failure so the happy path stays unindented — the guard-clause habit from Conditionals and Control Flow transfers directly.

Route order decides the winner

Registration orderWhat happens
Specific route before generic/api/todos/mine matches its own handler first
Generic :id firstmine is captured as an id — confusing 404s
Catch-all 404 lastUnhandled paths fall through to a proper 404 instead of Express's HTML default

Express matches top to bottom and stops at the first handler that responds. Register static paths and special segments before parameterised ones, and always finish with a not-found handler.

Common mistakes

  • Trusting req.params types — ids are strings; compare and cast deliberately.
  • Skipping body validation — whatever the client sends becomes your data; validate at the edge.
  • Registering routes in the wrong order — parameterised routes shadowing literal ones.
  • Forgetting return before an error response — execution continues and may send a second response (Express then throws headers-sent errors).
  • Leaving off the 404 fallback — Express's default HTML error page breaks JSON clients.

Practice checklist

  1. Create GET, POST and DELETE routes for /api/todos and exercise each with curl.
  2. Add /api/todos/:id and confirm an unknown id answers 404 with JSON.
  3. Implement ?done=true filtering and test both values plus the absent case.
  4. Validate a body two ways (wrong type, empty string) and verify 400 responses.
  5. Register a literal route after :id and observe it never matching — then fix the order.

Routes answer; the next building block decides what happens before they answer. Next: Middleware Explained.

Quiz: Routes and Route Parameters

5 questions · 70% to pass

Frequently Asked Questions

Node.js is a runtime that executes JavaScript outside the browser. It combines Google's V8 engine with APIs for files, networks, processes and streams, plus a package ecosystem (npm). You use it to build servers, command-line tools, APIs and anything that needs JavaScript running on a machine rather than in a page.

JavaScript is the language; Node.js is one environment that runs it. In the browser the language comes with a DOM, events and window APIs — in Node those are replaced by the file system, network and process modules. Syntax, functions, arrays, promises and async/await are identical, so your JavaScript knowledge transfers directly.

Browser code is sandboxed: it cannot read your files or open arbitrary ports. Node code has full machine access, which is why it is used for servers and tools but also why you never run untrusted code in it. There is no document or window in Node — the equivalents are global, process and modules.

npm is JavaScript's package manager and public registry. package.json describes your project: name, entry point, dependencies with version ranges, and scripts such as npm run dev. Installing a package copies it into node_modules and records the exact intent in package-lock.json so every machine installs identical versions.

Express is a minimal web framework for Node: a few thousand lines that give you routing, middleware, request parsing and static files on top of the raw HTTP server. It is the most common starting point for REST APIs and the backbone of this tutorial's project chapters.

Both work. CommonJS (require/module.exports) is the classic Node format; ES modules (import/export) are the standard and activated in a package.json with "type": "module". Modern projects favour ESM for browser compatibility, but you will read both in existing code, so this course teaches each.

Middleware are functions that run between the incoming request and the final response, in order: body parsing, logging, authentication, rate limits, error handling. Each receives the request, response and next function and can respond itself or pass control along — Express's pipeline model in one sentence.

A JSON file is perfect for learning CRUD: no setup, readable data, easy to reset. The moment you need concurrent writes, querying or multiple users, move to a real database — MySQL/PostgreSQL for relational data or MongoDB for documents. The storage chapter isolates persistence behind one module so swapping it later is straightforward.

A REST API exposes resources over HTTP using verbs on URLs: GET /api/todos lists, POST creates, PUT or PATCH updates, DELETE removes, and everything answers with JSON plus correct status codes (200, 201, 400, 404, 500). It is stateless — each request carries its own authentication — which makes it easy to cache, scale and consume from any frontend.

Node apps are long-running processes, not static files: deploy them to a Node-friendly host (a VPS, Render, Railway or Fly.io), keep secrets in environment variables, terminate HTTPS at a reverse proxy or platform edge, and send logs somewhere durable. The final chapter covers the full checklist including health checks and graceful shutdown.