javascript-today

Express Routing and Middleware

Express routing is how you define endpoints (URLs) that your application responds to. As your app grows, good routing organization becomes essential.

Route Methods

Express supports all HTTP methods:

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

// GET request
app.get('/users', (req, res) => {
  res.send('Get all users');
});

// POST request
app.post('/users', (req, res) => {
  res.send('Create a user');
});

// PUT request
app.put('/users/:id', (req, res) => {
  res.send(`Update user ${req.params.id}`);
});

// DELETE request
app.delete('/users/:id', (req, res) => {
  res.send(`Delete user ${req.params.id}`);
});

// Handle multiple methods on same route
app.route('/book')
  .get((req, res) => res.send('Get book'))
  .post((req, res) => res.send('Add book'))
  .put((req, res) => res.send('Update book'));

Route Parameters

Capture dynamic values from the URL:

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

// Multiple parameters
app.get('/posts/:year/:month/:slug', (req, res) => {
  const { year, month, slug } = req.params;
  res.json({ year, month, slug });
});
// GET /posts/2024/01/hello-world

// Optional parameter
app.get('/products/:category/:subcategory?', (req, res) => {
  res.json(req.params);
});
// GET /products/electronics → { category: 'electronics' }
// GET /products/electronics/phones → { category: 'electronics', subcategory: 'phones' }

// Parameter validation with regex
app.get('/user/:id(\\d+)', (req, res) => {
  // Only matches numeric IDs
  res.send(`User ${req.params.id}`);
});

Parameter types are always strings - convert when needed:

app.get('/products/:id', (req, res) => {
  const id = parseInt(req.params.id);
  
  if (isNaN(id)) {
    return res.status(400).json({ error: 'ID must be a number' });
  }
  
  res.json({ id, name: 'Product' });
});

Query Strings

Access URL query parameters:

app.get('/search', (req, res) => {
  const { q, page, limit } = req.query;
  
  res.json({
    searchTerm: q,
    page: page || 1,
    limit: limit || 10
  });
});
// GET /search?q=nodejs&page=2&limit=20
// → { searchTerm: 'nodejs', page: '2', limit: '20' }

Query parameters are also strings:

app.get('/api/products', (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const minPrice = parseFloat(req.query.minPrice) || 0;
  
  res.json({ page, limit, minPrice });
});

Multiple values:

// GET /filter?tags=nodejs&tags=express&tags=api
app.get('/filter', (req, res) => {
  console.log(req.query.tags); 
  // ['nodejs', 'express', 'api'] or 'nodejs' if single value
  
  const tags = Array.isArray(req.query.tags) 
    ? req.query.tags 
    : [req.query.tags];
  
  res.json({ tags });
});

Route Patterns

Match multiple URLs with patterns:

// Wildcard - matches any path starting with /files/
app.get('/files/*', (req, res) => {
  res.send(`File path: ${req.params[0]}`);
});
// GET /files/documents/report.pdf → File path: documents/report.pdf

// Character class
app.get('/users/:id([0-9]+)', (req, res) => {
  res.send(`User ${req.params.id}`);
});

// Optional parts
app.get('/blog/:year/:month?/:day?', (req, res) => {
  res.json(req.params);
});
// Matches: /blog/2024, /blog/2024/01, /blog/2024/01/15

Router Organization

As apps grow, organize routes with express.Router():

// routes/users.js
const express = require('express');
const router = express.Router();

// Define routes
router.get('/', (req, res) => {
  res.json([{ id: 1, name: 'Alice' }]);
});

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

router.post('/', (req, res) => {
  res.status(201).json({ message: 'User created' });
});

router.put('/:id', (req, res) => {
  res.json({ message: 'User updated' });
});

router.delete('/:id', (req, res) => {
  res.json({ message: 'User deleted' });
});

module.exports = router;
// routes/posts.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.json([{ id: 1, title: 'First Post' }]);
});

router.post('/', (req, res) => {
  res.status(201).json({ message: 'Post created' });
});

module.exports = router;
// server.js - Mount routers
const express = require('express');
const app = express();

// Import routers
const usersRouter = require('./routes/users');
const postsRouter = require('./routes/posts');

// Mount routers at paths
app.use('/api/users', usersRouter);
app.use('/api/posts', postsRouter);

// Now:
// /api/users → users router
// /api/users/123 → users router with id
// /api/posts → posts router

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

Route Middleware

Apply middleware to specific routes:

// Middleware function
function authenticate(req, res, next) {
  const token = req.headers.authorization;
  
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  // Verify token (simplified)
  if (token === 'secret-token') {
    next(); // Continue to route handler
  } else {
    res.status(403).json({ error: 'Invalid token' });
  }
}

// Apply to single route
app.get('/api/private', authenticate, (req, res) => {
  res.json({ message: 'Private data' });
});

// Apply to multiple routes
app.get('/api/admin/users', authenticate, (req, res) => {
  res.json({ users: [] });
});

app.post('/api/admin/settings', authenticate, (req, res) => {
  res.json({ message: 'Settings updated' });
});

// Apply to all routes on a router
const adminRouter = express.Router();
adminRouter.use(authenticate); // All routes need authentication

adminRouter.get('/users', (req, res) => {
  res.json({ users: [] });
});

app.use('/api/admin', adminRouter);

Request Object Properties

Access request data:

app.get('/api/info', (req, res) => {
  res.json({
    method: req.method,           // 'GET'
    url: req.url,                 // '/api/info?page=1'
    path: req.path,               // '/api/info'
    params: req.params,           // URL parameters
    query: req.query,             // Query string
    headers: req.headers,         // Request headers
    ip: req.ip,                   // Client IP
    protocol: req.protocol,       // 'http' or 'https'
    hostname: req.hostname,       // 'localhost'
    originalUrl: req.originalUrl  // Full URL
  });
});

Response Methods

Different ways to send responses:

// Send text
res.send('Hello');

// Send JSON
res.json({ message: 'Success' });

// Set status and send
res.status(404).send('Not found');
res.status(201).json({ id: 123 });

// Redirect
res.redirect('/new-location');
res.redirect(301, '/permanent-location');

// Send file
res.sendFile('/path/to/file.pdf');

// Download file
res.download('/path/to/file.pdf');

// Set header and send
res.set('Content-Type', 'text/html');
res.send('<h1>HTML Response</h1>');

// End response without data
res.end();

Complete Example

// server.js
const express = require('express');
const app = express();

// Parse JSON bodies
app.use(express.json());

// In-memory data
const products = [
  { id: 1, name: 'Laptop', price: 999, category: 'electronics' },
  { id: 2, name: 'Mouse', price: 29, category: 'electronics' },
  { id: 3, name: 'Desk', price: 299, category: 'furniture' }
];

// List products with filtering
app.get('/api/products', (req, res) => {
  const { category, minPrice, maxPrice } = req.query;
  
  let filtered = products;
  
  if (category) {
    filtered = filtered.filter(p => p.category === category);
  }
  
  if (minPrice) {
    filtered = filtered.filter(p => p.price >= parseFloat(minPrice));
  }
  
  if (maxPrice) {
    filtered = filtered.filter(p => p.price <= parseFloat(maxPrice));
  }
  
  res.json(filtered);
});

// Get single product
app.get('/api/products/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const product = products.find(p => p.id === id);
  
  if (!product) {
    return res.status(404).json({ error: 'Product not found' });
  }
  
  res.json(product);
});

// Create product
app.post('/api/products', (req, res) => {
  const newProduct = {
    id: products.length + 1,
    name: req.body.name,
    price: req.body.price,
    category: req.body.category
  };
  
  products.push(newProduct);
  res.status(201).json(newProduct);
});

// Update product
app.put('/api/products/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const product = products.find(p => p.id === id);
  
  if (!product) {
    return res.status(404).json({ error: 'Product not found' });
  }
  
  if (req.body.name) product.name = req.body.name;
  if (req.body.price) product.price = req.body.price;
  if (req.body.category) product.category = req.body.category;
  
  res.json(product);
});

// Delete product
app.delete('/api/products/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const index = products.findIndex(p => p.id === id);
  
  if (index === -1) {
    return res.status(404).json({ error: 'Product not found' });
  }
  
  products.splice(index, 1);
  res.json({ message: 'Product deleted' });
});

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

Test the API:

# List all products
curl http://localhost:3000/api/products

# Filter by category
curl http://localhost:3000/api/products?category=electronics

# Filter by price range
curl http://localhost:3000/api/products?minPrice=50&maxPrice=500

# Get single product
curl http://localhost:3000/api/products/1

# Create product
curl -X POST http://localhost:3000/api/products \
  -H "Content-Type: application/json" \
  -d '{"name":"Keyboard","price":79,"category":"electronics"}'

Best Practices

DO:

  • Use descriptive route names
  • Organize routes with Router
  • Validate URL parameters
  • Convert parameter types appropriately
  • Use proper HTTP status codes
  • Return consistent response formats

DON’T:

  • Put all routes in one file
  • Forget to validate input
  • Use GET for operations that change data
  • Return different response formats

Summary

Feature Example
Route parameter app.get('/users/:id', ...)
Query string req.query.page
Multiple params /posts/:year/:month/:slug
Router const router = express.Router()
Mount router app.use('/api', router)
Route middleware app.get('/path', middleware, handler)