>_
EngineeringNotes
← Back to All Backend Concepts
Concept 03

Routing

Routing is defined as the process of mapping URL paths to specific server-side logic.

01

Intent vs. Location

When a client makes a request to a server, two primary components determine how the server handles it: the HTTP Method and the URL Path.

🎯 The "What" (Intent)

HTTP Methods (GET, POST, PUT, DELETE) define the action or intent of a request. They tell the server what needs to be done.

📍 The "Where" (Location)

Routing defines the resource path. It tells the server exactly where to look or where to apply the intended action.

02

Static Routes

Static Routes are fixed URL paths that do not change. They are typically used to serve a specific collection of resources or a single, unchanging entity.

  • Exact string matching.
  • Always return the same type of data.
  • Examples: /api/books or /about-us.
server.js (Express)
app.get('/api/books', (req, res) => { // Fetch all books from database const books = [ { id: 1, title: '1984' }, { id: 2, title: 'Dune' } ]; res.status(200).json(books); });
03

Dynamic Routes

server.js (Express)
// The :id acts as a placeholder app.get('/api/users/:id', (req, res) => { // Extract specific parameter from URL const userId = req.params.id; // Look up user by ID in database... const user = { id: userId, name: 'Alice' }; res.status(200).json({ status: 'success', data: user }); });

Dynamic Routes contain variable parameters denoted by a colon (e.g., :id). This allows the server to extract specific real-time information directly from the requested URL path.

Instead of writing hundreds of static routes for every user (/users/1, /users/2), you write one dynamic route pattern (/users/:id). Express automatically parses the URL and places all dynamic variables inside the req.params object.

04

Query Parameters

Query parameters are key-value pairs added to the end of a URL (separated by ? and &). They are used primarily with GET requests to filter, sort, search, or paginate data without changing the core route.

https://api.example.com/api/products?category=electronics&sort=price_desc
Express Example
app.get('/api/products', (req, res) => { // Express automatically parses the query string into req.query const { category, sort } = req.query; // Output: { category: 'electronics', sort: 'price_desc' } console.log(req.query); // Complex database logic to filter by category and order by price // e.g., db.products.find({ category }).sort(sort) res.status(200).json({ status: "success", message: "Products filtered successfully", filters_applied: req.query }); });
05

Nested Routing

Nested Routing defines hierarchical relationships between resources. It naturally maps to how relational data is structured in your database (e.g., a User has many Posts).

Instead of fetching a post globally by its ID, nested routing ensures you are fetching a specific post belonging to a specific user. It reinforces ownership and context directly within the URL logic:

/api/users/:userId/posts/:postId
Express Example
app.get('/api/users/:userId/posts/:postId', (req, res) => { // Multiple dynamic parameters extracted simultaneously const { userId, postId } = req.params; // Verify the user exists, then find THEIR specific post // SQL Example: db.query( 'SELECT * FROM posts WHERE id = ? AND author_id = ?', [postId, userId] ); res.status(200).json({ user: userId, post: postId }); });
06

Route Versioning

As your API evolves, you will inevitably introduce breaking changes. Route Versioning helps manage these changes by including explicit version numbers directly in the URL (e.g., /api/v1/...). This allows you to safely deprecate older versions seamlessly while new clients adopt the updated endpoints without crashing existing apps.

API Versioning via Express Routers
const express = require('express'); const app = express(); const v1Router = express.Router(); const v2Router = express.Router(); // 🔴 Old Version 1 Implementation v1Router.get('/users', (req, res) => { res.json({ data: ['Alice', 'Bob'] }); // Returns simple array }); // 🟢 New Version 2 Implementation (Breaking Change) v2Router.get('/users', (req, res) => { res.json({ metadata: { count: 2 }, data: [{name: 'Alice'}, {name: 'Bob'}] // Returns array of objects }); }); // 🚀 Mount the routers to versioned prefix paths natively app.use('/api/v1', v1Router); app.use('/api/v2', v2Router);
07

Catch-All Routes

Global 404 Handler (Express)
// MUST be placed at the very BOTTOM of your routing file // after perfectly all other routes have been defined. app.all('*', (req, res, next) => { // You can either return a JSON response directly: res.status(404).json({ status: 'fail', message: `Can't find ${req.originalUrl} on this server!` }); // OR pass it down to a global error handling middleware: // next(new AppError(`Not Found: ${'{'}req.originalUrl{'}'}`, 404)); });

A Catch-All Route acts as an organized fallback mechanism. It cleanly captures requests for paths that do not exist (e.g., when a user makes a typo in the URL).

  • Returns a friendly, structured JSON "Not Found" error rather than the default, unresponsive HTML page provided by the web framework.
  • In Express, the asterisk (*) acts as a universal wildcard, matching literally any route.
  • app.all() catches all HTTP methods universally (GET, POST, PUT, DELETE, etc.).
  • Critical Requirement: Because routing evaluates sequentially from top to bottom, this must be the very last route declared in your codebase.