Understanding HTTP Server Requests and Responses

Introduction to HTTP Requests and Responses

Think of HTTP requests and responses like a conversation at a coffee shop. The customer (client) makes a request to the barista (server), and the barista responds with either the ordered drink or an explanation of why they can't fulfill the order. Every interaction follows this request-response pattern.

Let's look at a basic example of handling requests and responses:


const http = require('http');

const server = http.createServer((request, response) => {
    // Like a barista checking what the customer wants
    console.log('Customer ordered:', request.url);
    console.log('Order method:', request.method);
    console.log('Order details:', request.headers);

    // Preparing the response (like making the drink)
    response.writeHead(200, { 'Content-Type': 'text/plain' });
    response.end('Here\'s your order! ☕');
});

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

Understanding HTTP Request Properties

Think of an HTTP request like a detailed order form at a restaurant. Each part has its specific purpose:


const server = http.createServer((req, res) => {
    // Method: Like the type of service (dine-in, takeout, delivery)
    console.log(`Service type: ${req.method}`);

    // URL: Like the specific menu item being ordered
    console.log(`Order item: ${req.url}`);

    // Headers: Like special instructions for the order
    console.log('Special instructions:');
    console.log(req.headers);

    // Query parameters: Like customizations to the order
    const url = new URL(req.url, `http://${req.headers.host}`);
    console.log('Customizations:');
    console.log(url.searchParams);

    res.end('Order received!');
});
            

A more detailed example handling different types of requests:


const server = http.createServer(async (req, res) => {
    // Reading the body content (like listening to detailed instructions)
    let body = '';
    for await (const chunk of req) {
        body += chunk;
    }

    // Parsing JSON body if present (like interpreting complex orders)
    let orderDetails;
    if (req.headers['content-type'] === 'application/json') {
        try {
            orderDetails = JSON.parse(body);
        } catch (error) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            return res.end(JSON.stringify({
                error: 'Invalid order format'
            }));
        }
    }

    console.log('Complete order details:', {
        method: req.method,
        url: req.url,
        headers: req.headers,
        body: orderDetails || body
    });

    res.end('Order processed!');
});
            

Crafting HTTP Responses

Think of HTTP responses like preparing and serving dishes in a restaurant. Each response needs proper presentation and accompaniments:


const server = http.createServer((req, res) => {
    // Setting status code (like the order status)
    // 200: Order successful
    // 404: Item not found
    // 500: Kitchen problems
    
    if (req.url === '/menu') {
        // Serving the menu (JSON response)
        res.writeHead(200, {
            'Content-Type': 'application/json',
            'Cache-Control': 'max-age=3600' // Menu valid for an hour
        });
        
        const menu = {
            drinks: ['Coffee', 'Tea', 'Latte'],
            prices: [2.99, 2.50, 3.99]
        };
        
        res.end(JSON.stringify(menu));
    } else if (req.url === '/special') {
        // Serving HTML content (like a special menu card)
        res.writeHead(200, {
            'Content-Type': 'text/html',
            'X-Special-Offer': 'true'
        });
        
        res.end(`
            
                
                    

Today's Special

Buy one coffee, get one free!

`); } else { // Item not on menu res.writeHead(404, { 'Content-Type': 'application/json', 'X-Error-Type': 'ItemNotFound' }); res.end(JSON.stringify({ error: 'Item not found on menu' })); } });

Handling Different Content Types

Like a restaurant that serves different types of dishes, each needing specific preparation and presentation:


const server = http.createServer((req, res) => {
    // Content type handler (like different serving methods)
    const serveContent = (content, type) => {
        res.writeHead(200, { 'Content-Type': type });
        res.end(content);
    };

    switch (req.url) {
        case '/text':
            // Serving plain text (like a simple receipt)
            serveContent('Simple order confirmation', 'text/plain');
            break;

        case '/json':
            // Serving JSON (like a detailed order summary)
            serveContent(
                JSON.stringify({ order: 'confirmed', items: ['coffee'] }), 
                'application/json'
            );
            break;

        case '/html':
            // Serving HTML (like a formatted menu)
            serveContent(`
                
                    
                        

Order Confirmed

Thank you for your order!

`, 'text/html'); break; case '/binary': // Serving binary data (like a digital receipt) const buffer = Buffer.from('Receipt data'); serveContent(buffer, 'application/octet-stream'); break; default: res.writeHead(404); res.end('Item not found'); } });

Real-World Example: File Server

Let's build a simple file server that handles various content types:


const http = require('http');
const fs = require('fs').promises;
const path = require('path');

const MIME_TYPES = {
    '.html': 'text/html',
    '.css': 'text/css',
    '.js': 'application/javascript',
    '.json': 'application/json',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.gif': 'image/gif',
    '.pdf': 'application/pdf'
};

const server = http.createServer(async (req, res) => {
    try {
        // Remove query parameters and decode URL
        const filePath = path.join(
            'public', 
            decodeURIComponent(req.url.split('?')[0])
        );

        // Security check (prevent directory traversal)
        if (!filePath.startsWith('public/')) {
            res.writeHead(403);
            return res.end('Access denied');
        }

        const fileStats = await fs.stat(filePath);
        
        if (fileStats.isDirectory()) {
            // Serve directory listing
            const files = await fs.readdir(filePath);
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end(`
                
                    
                        

Directory: ${req.url}

    ${files.map(file => `
  • ${file}
  • `).join('')}
`); } else { // Serve file const ext = path.extname(filePath); const contentType = MIME_TYPES[ext] || 'application/octet-stream'; const content = await fs.readFile(filePath); res.writeHead(200, { 'Content-Type': contentType, 'Content-Length': content.length, 'Cache - Control': 'public, max-age=86400' }); res.end(content); } } catch (error) { if (error.code === 'ENOENT') { res.writeHead(404); res.end('File not found'); } else { res.writeHead(500); res.end('Server error'); } } }); server.listen(3000, () => { console.log('File server running on port 3000'); });

Error Handling and Status Codes

Like a restaurant handling different situations, HTTP servers need to communicate various outcomes:


const handleError = (res, status, message, details = null) => {
    res.writeHead(status, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({
        error: message,
        details: details,
        status: status
    }));
};

const server = http.createServer(async (req, res) => {
    try {
        if (req.url === '/coffee') {
            // 200: Success (Order completed)
            res.writeHead(200);
            res.end('☕ Coffee served!');
        } else if (req.url === '/maintenance') {
            // 503: Service Unavailable (Machine being cleaned)
            handleError(res, 503, 'Coffee machine under maintenance');
        } else if (req.url === '/tea') {
            // 501: Not Implemented (We don't serve tea)
            handleError(res, 501, 'Tea service not available');
        } else if (req.url === '/water') {
            // 429: Too Many Requests (Too many water orders)
            handleError(res, 429, 'Too many water requests, please wait');
        } else {
            // 404: Not Found (Item not on menu)
            handleError(res, 404, 'Item not found on menu');
        }
    } catch (error) {
        // 500: Internal Server Error (Kitchen problems)
        handleError(res, 500, 'Internal server error', error.message);
    }
});
            

Further Topics to Explore

To deepen your understanding of HTTP requests and responses, consider exploring:

Compression - Like packaging takeout orders efficiently

Streaming responses - Like serving a multi-course meal in sequence

Content negotiation - Like accommodating dietary preferences

Response caching - Like preparing popular dishes in advance

Request/Response interceptors - Like quality control checks

Rate limiting - Like managing a busy restaurant during peak hours