> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 05. HTTP Module & Web Server

> Create a raw HTTP server in Node.js without frameworks.

# HTTP Module

The `http` module allows Node.js to transfer data over the Hyper Text Transfer Protocol (HTTP). It is the foundation for web servers in Node.js--every framework you will use later (Express, Fastify, Koa) is built on top of this module.

Understanding the raw `http` module is like learning to drive a manual transmission before switching to automatic. You will rarely use it directly in production, but knowing how it works gives you the ability to debug framework-level issues, understand what middleware is actually doing under the hood, and make informed decisions about when a framework is helping versus getting in your way.

```javascript theme={null}
const http = require('http');
```

## Creating a Server

The `http.createServer()` method includes a request listener function which is automatically added to the `'request'` event.

```javascript theme={null}
const server = http.createServer((req, res) => {
  // This callback fires for EVERY incoming HTTP request.
  // Node.js does not create a new thread per request--this single function
  // handles all requests via the event loop, which is why Node.js can handle
  // thousands of concurrent connections with minimal overhead.
  res.write('Hello World');
  res.end();  // Always call res.end() -- forgetting this leaves the connection hanging
});

server.listen(5000, () => {
  console.log('Server running on port 5000...');
});
```

<Warning>
  **Common pitfall -- forgetting `res.end()`:** If you never call `res.end()`, the client will hang indefinitely waiting for a response, and the connection stays open consuming resources. In a production server, this leads to connection pool exhaustion where your server appears frozen even though it is actually running.
</Warning>

Run this script and navigate to `http://localhost:5000` in your browser.

## The Request and Response Objects

The callback function receives two arguments:

1. `req` (Request): Contains information about the incoming request (URL, method, headers, etc.).
2. `res` (Response): Used to send a response back to the client.

### Inspecting the Request

```javascript theme={null}
const server = http.createServer((req, res) => {
  console.log(req.url, req.method);
  // ...
});
```

### Setting Headers and Status Code

```javascript theme={null}
const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
});
```

## Basic Routing

You can use `req.url` to handle different routes.

```javascript theme={null}
const server = http.createServer((req, res) => {
  if (req.url === '/') {
    res.write('Welcome to the Home Page');
    res.end();
  } else if (req.url === '/about') {
    res.write('Welcome to the About Page');
    res.end();
  } else {
    res.statusCode = 404;
    res.write('Page Not Found');
    res.end();
  }
});
```

## Serving JSON

To serve an API, you typically return JSON data.

```javascript theme={null}
const server = http.createServer((req, res) => {
  if (req.url === '/api/users') {
    const users = [
      { name: 'Alice', age: 25 },
      { name: 'Bob', age: 30 }
    ];
    
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(users));
  }
});
```

## Serving HTML Files

To serve actual HTML files, we combine the `http` module with the `fs` module.

```javascript theme={null}
const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    fs.readFile(path.join(__dirname, 'index.html'), (err, content) => {
      if (err) throw err;
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end(content);
    });
  }
});
```

## Handling POST Requests

Unlike GET requests, POST data comes in chunks and must be collected. This is because HTTP request bodies can be arbitrarily large (file uploads, for example), so Node.js streams the data to you piece by piece rather than buffering the entire body into memory.

```javascript theme={null}
const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/api/users') {
    let body = '';
    
    // Each 'data' event delivers a chunk of the request body.
    // For a small JSON payload, you might get it all in one chunk.
    // For a large file upload, you could get hundreds of chunks.
    req.on('data', chunk => {
      body += chunk.toString();
      
      // SECURITY: Limit body size to prevent memory exhaustion attacks.
      // Without this check, an attacker can send a 10GB payload and crash your server.
      if (body.length > 1e6) {  // ~1MB limit
        res.writeHead(413, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Payload too large' }));
        req.destroy();
      }
    });
    
    // 'end' fires when the entire body has been received
    req.on('end', () => {
      try {
        const user = JSON.parse(body);
        console.log('Received user:', user);
        
        res.writeHead(201, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ message: 'User created', user }));
      } catch (error) {
        res.writeHead(400, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Invalid JSON' }));
      }
    });
  }
});
```

## Query Parameters

```javascript theme={null}
const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
  const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
  const pathname = parsedUrl.pathname;
  const query = parsedUrl.searchParams;
  
  // GET /search?q=nodejs&limit=10
  if (pathname === '/search') {
    const searchTerm = query.get('q');
    const limit = parseInt(query.get('limit')) || 10;
    
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({
      search: searchTerm,
      limit: limit,
      results: []
    }));
  }
});
```

## Headers Deep Dive

### Request Headers

```javascript theme={null}
const server = http.createServer((req, res) => {
  console.log('User-Agent:', req.headers['user-agent']);
  console.log('Content-Type:', req.headers['content-type']);
  console.log('Authorization:', req.headers['authorization']);
  console.log('Accept-Language:', req.headers['accept-language']);
  console.log('All headers:', req.headers);
  
  // Check for JSON content
  const isJson = req.headers['content-type'] === 'application/json';
});
```

### Response Headers

```javascript theme={null}
res.setHeader('Content-Type', 'application/json');
res.setHeader('X-Powered-By', 'Node.js');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Access-Control-Allow-Origin', '*');

// Set multiple headers at once
res.writeHead(200, {
  'Content-Type': 'text/html',
  'Set-Cookie': 'session=abc123; HttpOnly',
  'X-Request-Id': '12345'
});
```

## Building a Simple Router

```javascript theme={null}
const http = require('http');

class Router {
  constructor() {
    this.routes = {
      GET: {},
      POST: {},
      PUT: {},
      DELETE: {}
    };
  }

  get(path, handler) {
    this.routes.GET[path] = handler;
  }

  post(path, handler) {
    this.routes.POST[path] = handler;
  }

  put(path, handler) {
    this.routes.PUT[path] = handler;
  }

  delete(path, handler) {
    this.routes.DELETE[path] = handler;
  }

  async handle(req, res) {
    const handler = this.routes[req.method]?.[req.url];
    
    if (handler) {
      await handler(req, res);
    } else {
      res.writeHead(404, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Not Found' }));
    }
  }
}

// Usage
const router = new Router();

router.get('/', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Home Page');
});

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

const server = http.createServer((req, res) => router.handle(req, res));
server.listen(3000);
```

## HTTPS Server

For production, you need HTTPS:

```javascript theme={null}
const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server.cert')
};

const server = https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('Secure Hello World!');
});

server.listen(443, () => {
  console.log('HTTPS server running on port 443');
});
```

## Summary

* The **`http`** module creates web servers without external dependencies
* Use `req.url` and `req.method` for routing--but this gets unwieldy fast, which is why frameworks like Express exist
* POST data arrives in chunks via `'data'` and `'end'` events--always limit body size to prevent memory exhaustion attacks
* Parse query strings with the `URL` class (not the deprecated `url.parse()`)
* Set appropriate **Content-Type** headers for responses--mismatched headers cause subtle client-side bugs
* Always call `res.end()` to avoid hanging connections
* Use **`https`** module with SSL certificates for production--in practice, most teams terminate TLS at a reverse proxy (Nginx, load balancer) rather than in Node.js directly
* Frameworks like **Express.js** abstract this complexity while still using `http` under the hood
