TechLead
Lesson 7 of 16
5 min read
Node.js

Building HTTP Servers with Node.js

Build complete REST APIs from scratch with the Node.js http module: routing, POST data parsing, CORS, static files, and the HTTP lifecycle

The HTTP Module Deep Dive

While frameworks like Express simplify HTTP handling, understanding the raw http module is essential. It teaches you what frameworks abstract away and helps you debug issues at the protocol level.

🌐 HTTP Request Lifecycle

Client Sends Request
Server Receives
Parse & Route
Process
Send Response

Handling POST Data and JSON Bodies

Request bodies arrive in chunks that must be collected and parsed:

const http = require('http');

function parseBody(req) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    req.on('data', chunk => chunks.push(chunk));
    req.on('end', () => {
      const body = Buffer.concat(chunks).toString();
      try {
        resolve(JSON.parse(body));
      } catch {
        resolve(body); // Not JSON, return raw string
      }
    });
    req.on('error', reject);
  });
}

const server = http.createServer(async (req, res) => {
  if (req.method === 'POST' && req.url === '/api/users') {
    const body = await parseBody(req);
    console.log('Received:', body);

    // Validate
    if (!body.name || !body.email) {
      res.writeHead(400, { 'Content-Type': 'application/json' });
      return res.end(JSON.stringify({ error: 'Name and email required' }));
    }

    // Process and respond
    const user = { id: Date.now(), ...body };
    res.writeHead(201, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(user));
  }
});

server.listen(3000);

Building a Router

const http = require('http');

// Simple pattern-matching router
class Router {
  constructor() {
    this.routes = [];
  }

  add(method, path, handler) {
    // Convert /users/:id to regex
    const paramNames = [];
    const pattern = path.replace(/:([^/]+)/g, (_, name) => {
      paramNames.push(name);
      return '([^/]+)';
    });
    this.routes.push({
      method,
      regex: new RegExp(`^${pattern}$`),
      paramNames,
      handler
    });
  }

  get(path, handler) { this.add('GET', path, handler); }
  post(path, handler) { this.add('POST', path, handler); }
  put(path, handler) { this.add('PUT', path, handler); }
  delete(path, handler) { this.add('DELETE', path, handler); }

  resolve(method, url) {
    for (const route of this.routes) {
      if (route.method !== method) continue;
      const match = url.match(route.regex);
      if (match) {
        const params = {};
        route.paramNames.forEach((name, i) => params[name] = match[i + 1]);
        return { handler: route.handler, params };
      }
    }
    return null;
  }
}

// Usage
const router = new Router();
const users = [];

router.get('/api/users', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(users));
});

router.get('/api/users/:id', (req, res, params) => {
  const user = users.find(u => u.id === params.id);
  if (!user) {
    res.writeHead(404, { 'Content-Type': 'application/json' });
    return res.end(JSON.stringify({ error: 'Not found' }));
  }
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(user));
});

const server = http.createServer((req, res) => {
  const { pathname } = new URL(req.url, `http://${req.headers.host}`);
  const result = router.resolve(req.method, pathname);

  if (result) {
    result.handler(req, res, result.params);
  } else {
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Route not found' }));
  }
});

server.listen(3000);

Serving Static Files

const http = require('http');
const fs = require('fs');
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',
  '.svg':  'image/svg+xml',
  '.ico':  'image/x-icon'
};

function serveStatic(baseDir) {
  return (req, res) => {
    // Prevent directory traversal attacks
    const safePath = path.normalize(req.url).replace(/^(\.\.\/)+/, '');
    let filePath = path.join(baseDir, safePath);

    // Default to index.html for directories
    if (filePath.endsWith('/')) filePath += 'index.html';

    const ext = path.extname(filePath);
    const contentType = MIME_TYPES[ext] || 'application/octet-stream';

    const stream = fs.createReadStream(filePath);
    stream.on('open', () => {
      res.writeHead(200, { 'Content-Type': contentType });
      stream.pipe(res);
    });
    stream.on('error', () => {
      res.writeHead(404);
      res.end('File not found');
    });
  };
}

const server = http.createServer(serveStatic('./public'));
server.listen(3000);

CORS Headers

const http = require('http');

function setCORSHeaders(res, origin = '*') {
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Max-Age', '86400'); // Cache preflight 24h
}

const server = http.createServer(async (req, res) => {
  // Handle CORS for all routes
  setCORSHeaders(res, 'https://myapp.com');

  // Preflight request
  if (req.method === 'OPTIONS') {
    res.writeHead(204);
    return res.end();
  }

  // Your API routes here
  if (req.url === '/api/data') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'CORS-enabled response' }));
  }
});

server.listen(3000);

Query Strings and URL Parsing

const http = require('http');

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);

  // Parse query parameters
  // URL: /search?q=nodejs&page=2&limit=10
  const query = url.searchParams.get('q');      // 'nodejs'
  const page = parseInt(url.searchParams.get('page') || '1');
  const limit = parseInt(url.searchParams.get('limit') || '20');

  console.log({ pathname: url.pathname, query, page, limit });

  // Filter and paginate
  if (url.pathname === '/api/search') {
    const results = database.filter(item =>
      item.title.toLowerCase().includes(query.toLowerCase())
    );
    const paginated = results.slice((page - 1) * limit, page * limit);

    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({
      results: paginated,
      total: results.length,
      page,
      totalPages: Math.ceil(results.length / limit)
    }));
  }
});

server.listen(3000);

💡 Key Takeaways

  • • Request bodies arrive in chunks; collect and parse them manually
  • • Build a simple router with regex for parameter extraction
  • • Stream static files instead of reading them entirely into memory
  • • Always handle CORS preflight OPTIONS requests
  • • Use the URL constructor for robust URL and query string parsing

Continue Learning