Understanding HTTP Server Routes and Routing

Introduction to Server Routing

Think of server routing like a sophisticated postal system in a large office building. Just as mail needs to be sorted and delivered to the correct department and person, HTTP requests need to be directed to the appropriate handlers in your application. The routing system is like the building's directory and the mail room staff combined - it knows where everything should go and makes sure it gets there.

Let's look at a basic routing example:


const http = require('http');

const server = http.createServer((req, res) => {
    // The URL is like the address on an envelope
    const path = req.url;
    
    // The HTTP method is like the type of mail (letter, package, express delivery)
    const method = req.method;

    if (path === '/welcome' && method === 'GET') {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end('Welcome to our office building!');
    } else {
        res.writeHead(404);
        res.end('Address not found');
    }
});

server.listen(3000, () => {
    console.log('Mail room is open on port 3000');
});
            

Route Parameters and Dynamic Routing

Imagine a hotel with numbered rooms. Just as you can find any room using its number without needing to list every room explicitly, dynamic routes let you handle many URLs with a single route pattern:


const server = http.createServer((req, res) => {
    // Match routes like /room/101, /room/102, etc.
    const roomMatch = req.url.match(/^\/room\/(\d+)$/);
    
    if (roomMatch && req.method === 'GET') {
        const roomNumber = roomMatch[1];
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({
            room: roomNumber,
            message: `Welcome to room ${roomNumber}`
        }));
    }
});
            

A more sophisticated example with multiple parameter types:


const server = http.createServer((req, res) => {
    // Creating a simple route parser
    const parseRoute = (template, url) => {
        const templateParts = template.split('/');
        const urlParts = url.split('/');
        const params = {};

        if (templateParts.length !== urlParts.length) return null;

        for (let i = 0; i < templateParts.length; i++) {
            if (templateParts[i].startsWith(':')) {
                // This is a parameter
                const paramName = templateParts[i].slice(1);
                params[paramName] = urlParts[i];
            } else if (templateParts[i] !== urlParts[i]) {
                return null;
            }
        }

        return params;
    };

    // Example route: /hotel/:floor/room/:number
    const params = parseRoute('/hotel/:floor/room/:number', req.url);
    if (params) {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({
            floor: params.floor,
            roomNumber: params.number,
            message: `Room ${params.number} on floor ${params.floor}`
        }));
    }
});
            

Route Organization and Middleware

Think of middleware as security checkpoints in a building. Before visitors reach their final destination, they might need to sign in, get a badge, or be escorted. Here's how we can implement this concept:


// Middleware function - like a security checkpoint
const logRequest = (req, res, next) => {
    const timestamp = new Date().toISOString();
    console.log(`${timestamp}: ${req.method} ${req.url}`);
    next(); // Proceed to the next checkpoint or final destination
};

// Authentication middleware - like checking visitor ID
const authenticate = (req, res, next) => {
    const authHeader = req.headers.authorization;
    if (!authHeader) {
        res.writeHead(401);
        return res.end('Please show your ID');
    }
    next();
};

// Router implementation
class Router {
    constructor() {
        this.routes = [];
        this.middleware = [];
    }

    use(middleware) {
        this.middleware.push(middleware);
    }

    addRoute(method, path, handler) {
        this.routes.push({ method, path, handler });
    }

    async handle(req, res) {
        // Run all middleware first
        for (const middleware of this.middleware) {
            await new Promise(resolve => middleware(req, res, resolve));
        }

        // Find matching route
        const route = this.routes.find(r => 
            r.method === req.method && r.path === req.url
        );

        if (route) {
            return route.handler(req, res);
        }

        res.writeHead(404);
        res.end('Not Found');
    }
}
            

Using our router:


const router = new Router();

// Add middleware
router.use(logRequest);
router.use(authenticate);

// Define routes
router.addRoute('GET', '/lobby', (req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Welcome to the lobby!');
});

router.addRoute('GET', '/office', (req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('This is the office area');
});

// Create server with router
const server = http.createServer((req, res) => {
    router.handle(req, res);
});
            

Pattern Matching and Advanced Routing

Consider a library where books are organized by category, author, and title. We can create a sophisticated routing system to handle this hierarchy:


class AdvancedRouter {
    constructor() {
        this.routes = [];
    }

    addRoute(method, pattern, handler) {
        // Convert pattern to regex
        const regexPattern = pattern
            .replace(/:[a-zA-Z]+/g, '([^/]+)')
            .replace(/\*/g, '.*');
        
        const regex = new RegExp(`^${regexPattern}$`);
        this.routes.push({ method, regex, handler });
    }

    async handle(req, res) {
        const route = this.routes.find(r => {
            return r.method === req.method && 
                   r.regex.test(req.url);
        });

        if (route) {
            const matches = req.url.match(route.regex);
            const params = matches.slice(1);
            return route.handler(req, res, params);
        }

        res.writeHead(404);
        res.end('Not Found');
    }
}

// Usage example
const router = new AdvancedRouter();

router.addRoute('GET', '/books/:category/:author/:title', (req, res, params) => {
    const [category, author, title] = params;
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ category, author, title }));
});

router.addRoute('GET', '/books/:category/*', (req, res, params) => {
    const [category, rest] = params;
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ 
        category,
        subPath: rest
    }));
});
            

Real-World Application: RESTful API Router

Let's build a complete example of a blog API with proper routing:


class BlogRouter {
    constructor() {
        this.routes = new Map();
        this.middleware = [];
    }

    use(middleware) {
        this.middleware.push(middleware);
    }

    route(method, path, handler) {
        const key = `${method}:${path}`;
        this.routes.set(key, handler);
        return this;
    }

    async handle(req, res) {
        // Parse JSON body if present
        if (req.headers['content-type'] === 'application/json') {
            const buffers = [];
            for await (const chunk of req) {
                buffers.push(chunk);
            }
            req.body = JSON.parse(Buffer.concat(buffers).toString());
        }

        // Run middleware
        for (const middleware of this.middleware) {
            await new Promise(resolve => middleware(req, res, resolve));
        }

        const key = `${req.method}:${req.url}`;
        const handler = this.routes.get(key);

        if (handler) {
            return handler(req, res);
        }

        res.writeHead(404);
        res.end('Not Found');
    }
}

// Usage example
const router = new BlogRouter();

// Middleware for logging
router.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();
});

// Blog routes
router
    .route('GET', '/posts', (req, res) => {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ posts: [] }));
    })
    .route('POST', '/posts', (req, res) => {
        // Create new post
        const post = req.body;
        res.writeHead(201, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(post));
    })
    .route('GET', '/posts/:id', (req, res) => {
        // Get single post
    })
    .route('PUT', '/posts/:id', (req, res) => {
        // Update post
    })
    .route('DELETE', '/posts/:id', (req, res) => {
        // Delete post
    });

// Create server
const server = http.createServer((req, res) => {
    router.handle(req, res);
});

server.listen(3000, () => {
    console.log('Blog API server running on port 3000');
});
            

Further Topics to Explore

To deepen your understanding of HTTP routing, consider exploring:

Route versioning - Managing API versions, like having different editions of a map for different time periods

Route validation - Ensuring requests meet specific criteria, like checking if visitors have the right clearance level

Route documentation - Generating API documentation from route definitions, like creating a building directory automatically

Route testing - Writing tests for your routes, like running fire drills to ensure emergency exits work

Route optimization - Improving route matching performance, like optimizing the mail sorting process

Content negotiation - Handling different response formats, like providing information in multiple languages