Routing & Route Parameters

Define routes, handle HTTP methods, and work with dynamic route parameters

Express Routing Basics

Routing refers to how an application responds to client requests to specific endpoints. Each route can have one or more handler functions that are executed when the route is matched.

🛣️ Route Definition

app.METHOD(PATH, HANDLER)

// METHOD: get, post, put, patch, delete, all
// PATH: URL path or pattern
// HANDLER: function(req, res) or function(req, res, next)

HTTP Methods

import express from 'express';
const app = express();

app.use(express.json());

// GET - Retrieve data
app.get('/users', (req, res) => {
  res.json([{ id: 1, name: 'John' }]);
});

// POST - Create new resource
app.post('/users', (req, res) => {
  const { name, email } = req.body;
  res.status(201).json({ id: Date.now(), name, email });
});

// PUT - Replace entire resource
app.put('/users/:id', (req, res) => {
  const { id } = req.params;
  const userData = req.body;
  res.json({ id, ...userData });
});

// PATCH - Partial update
app.patch('/users/:id', (req, res) => {
  const { id } = req.params;
  const updates = req.body;
  res.json({ id, ...updates, updated: true });
});

// DELETE - Remove resource
app.delete('/users/:id', (req, res) => {
  const { id } = req.params;
  res.json({ message: `User ${id} deleted` });
});

// ALL - Match all HTTP methods
app.all('/api/*', (req, res, next) => {
  console.log(`API request: ${req.method} ${req.path}`);
  next();
});

Route Parameters

// Basic parameter
app.get('/users/:id', (req, res) => {
  const { id } = req.params;
  res.json({ userId: id });
});
// GET /users/123 → { userId: "123" }

// Multiple parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
  const { userId, postId } = req.params;
  res.json({ userId, postId });
});
// GET /users/5/posts/42 → { userId: "5", postId: "42" }

// Optional parameters (use ? or separate routes)
app.get('/products/:category/:id?', (req, res) => {
  const { category, id } = req.params;
  if (id) {
    res.json({ category, productId: id });
  } else {
    res.json({ category, products: [] });
  }
});

// Parameter with regex constraint
app.get('/users/:id(\\d+)', (req, res) => {
  // Only matches if :id is numeric
  res.json({ userId: parseInt(req.params.id) });
});

// Param middleware - runs for specific param
app.param('id', (req, res, next, id) => {
  // Validate or fetch resource
  if (!/^\d+$/.test(id)) {
    return res.status(400).json({ error: 'Invalid ID format' });
  }
  req.resourceId = parseInt(id);
  next();
});

Query Strings

// Query parameters are in req.query
app.get('/search', (req, res) => {
  const { q, page = 1, limit = 10, sort } = req.query;
  
  res.json({
    query: q,
    page: parseInt(page as string),
    limit: parseInt(limit as string),
    sort: sort || 'createdAt'
  });
});
// GET /search?q=express&page=2&limit=20
// → { query: "express", page: 2, limit: 20, sort: "createdAt" }

// TypeScript: Type your query parameters
interface SearchQuery {
  q?: string;
  page?: string;
  limit?: string;
  sort?: 'asc' | 'desc';
}

app.get('/search', (req: Request<{}, {}, {}, SearchQuery>, res) => {
  const { q, page, limit, sort } = req.query;
  // Now typed!
});

Express Router

// routes/users.ts - Modular routing
import { Router } from 'express';

const router = Router();

// All routes here are prefixed with whatever you mount on
router.get('/', (req, res) => {
  res.json([{ id: 1, name: 'User 1' }]);
});

router.get('/:id', (req, res) => {
  res.json({ id: req.params.id });
});

router.post('/', (req, res) => {
  res.status(201).json(req.body);
});

router.put('/:id', (req, res) => {
  res.json({ id: req.params.id, ...req.body });
});

router.delete('/:id', (req, res) => {
  res.status(204).send();
});

export default router;

// app.ts - Mount the router
import express from 'express';
import userRoutes from './routes/users';
import productRoutes from './routes/products';

const app = express();

app.use(express.json());

// Mount routers with prefix
app.use('/api/users', userRoutes);      // /api/users, /api/users/:id
app.use('/api/products', productRoutes); // /api/products, /api/products/:id

Route Chaining

// Chain methods for the same route path
app.route('/articles')
  .get((req, res) => {
    res.json({ articles: [] });
  })
  .post((req, res) => {
    res.status(201).json(req.body);
  });

app.route('/articles/:id')
  .get((req, res) => {
    res.json({ id: req.params.id });
  })
  .put((req, res) => {
    res.json({ id: req.params.id, ...req.body });
  })
  .delete((req, res) => {
    res.status(204).send();
  });

Route Patterns

// Wildcard matching
app.get('/files/*', (req, res) => {
  const filePath = req.params[0]; // Everything after /files/
  res.json({ path: filePath });
});
// GET /files/images/photo.jpg → { path: "images/photo.jpg" }

// String patterns
app.get('/ab?cd', (req, res) => {
  // Matches /acd or /abcd
  res.send('ab?cd');
});

app.get('/ab+cd', (req, res) => {
  // Matches /abcd, /abbcd, /abbbcd, etc.
  res.send('ab+cd');
});

app.get('/ab*cd', (req, res) => {
  // Matches /abcd, /abXcd, /abANYTHINGcd
  res.send('ab*cd');
});

// Regex routes
app.get(/.*fly$/, (req, res) => {
  // Matches /butterfly, /dragonfly, etc.
  res.send('ends with fly');
});

TypeScript Route Types

import { Request, Response, NextFunction } from 'express';

// Define types for params, body, query
interface UserParams {
  id: string;
}

interface CreateUserBody {
  name: string;
  email: string;
  password: string;
}

interface UserQuery {
  include?: string;
}

// Request<Params, ResBody, ReqBody, Query>
app.post('/users', 
  (req: Request<{}, {}, CreateUserBody>, res: Response) => {
    const { name, email, password } = req.body; // Typed!
    res.json({ name, email });
  }
);

app.get('/users/:id',
  (req: Request<UserParams, {}, {}, UserQuery>, res: Response) => {
    const { id } = req.params;        // string
    const { include } = req.query;    // string | undefined
    res.json({ id, include });
  }
);

✅ Routing Best Practices

  • • Use Router() to organize routes by feature/resource
  • • Keep route handlers small - move logic to controllers
  • • Use meaningful HTTP methods (GET read, POST create, etc.)
  • • Validate params and query strings early
  • • Use plural nouns for resource routes (/users, /products)
  • • Version your API routes (/api/v1/users)