jsguides

node:http

require('http')

The node:http module provides HTTP server and client functionality in Node.js. It’s the foundational module for building web servers and making HTTP requests. This module is built on top of Node.js’s event-driven architecture and uses streams for efficient data handling.

Common usage

Creating a simple HTTP server

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, World!');
});

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

The createServer callback receives two stream objects: an IncomingMessage (the request) and a ServerResponse (the response). The server stays alive because the event loop keeps it listening — calling listen() registers the socket, and the callback fires once per incoming connection. This single-callback model scales to thousands of concurrent connections without threads.

Making HTTP GET requests

const http = require('http');

const req = http.get('http://example.com', (res) => {
  console.log(`STATUS: ${res.statusCode}`);
  
  res.on('data', (chunk) => {
    console.log(`DATA: ${chunk}`);
  });
  
  res.on('end', () => {
    console.log('Response complete');
  });
});

req.on('error', (err) => {
  console.error(`Error: ${err.message}`);
});

http.get() is a convenience wrapper around http.request() that sets the method to GET and calls .end() automatically. The response arrives in chunks because Node streams data as it comes off the network — you never buffer the entire response unless you explicitly concatenate the chunks. This streaming design is what makes the module memory-efficient for large payloads.

Making HTTP POST requests

const http = require('http');

const data = JSON.stringify({ name: 'John', age: 30 });

const options = {
  hostname: 'example.com',
  port: 80,
  path: '/api/users',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': Buffer.byteLength(data)
  }
};

const req = http.request(options, (res) => {
  let body = '';
  
  res.on('data', (chunk) => {
    body += chunk;
  });
  
  res.on('end', () => {
    console.log('Response:', body);
  });
});

req.write(data);
req.end();

Unlike http.get(), a POST request with http.request() requires you to call req.write() to send the body and req.end() to signal completion. The Content-Length header is set using Buffer.byteLength() rather than data.length because the byte length may differ from the character count for multi-byte UTF-8 strings. Forgetting to call .end() is a common pitfall — the request silently hangs, and no response ever arrives.

Key classes and methods

http.createServer()

Creates an HTTP server instance. The request listener receives http.IncomingMessage (req) and http.ServerResponse (res) objects.

const server = http.createServer((req, res) => {
  // Handle request
});

The server object returned by createServer is an EventEmitter. You can attach listeners for 'request', 'connection', 'close', and 'error' events to handle lifecycle concerns beyond the basic request-response cycle. This event-driven design is consistent across the entire Node.js core.

http.request()

Makes an outgoing HTTP request. Returns a http.ClientRequest instance.

const req = http.request(options, (res) => {
  // Handle response
});
req.end();

The options object accepts hostname, port, path, method, headers, and auth among others. The callback fires once the response status line and headers arrive — the body may still be streaming. Always attach an 'error' listener to the returned ClientRequest to catch DNS failures, connection refusals, and timeouts.

http.get()

A convenience method for making GET requests. Shorthand for http.request() with method: 'GET'.

http.get('http://example.com', (res) => {
  // Handle response
});

http.get() accepts a URL string as its first argument and internally parses it into hostname and path. This is simpler than constructing a full options object for quick GET requests, but you lose control over custom headers unless you pass an options object instead of a string.

Request and response objects

IncomingMessage (req)

The IncomingMessage object represents the HTTP request. It inherits from stream.Readable.

Key properties:

  • req.method — HTTP method (GET, POST, etc.)
  • req.url — Request URL
  • req.headers — Request headers object
  • req.httpVersion — HTTP version

Key events:

  • data — Emitted when a chunk of data is received
  • end — Emitted when the entire message is received
  • error — Emitted on errors

ServerResponse (res)

The ServerResponse object represents the HTTP response. It inherits from stream.Writable.

Key methods:

  • res.writeHead(statusCode, headers) — Write response headers
  • res.write(chunk) — Write response body
  • res.end() — Signal response complete
  • res.setHeader(name, value) — Set a response header
  • res.getHeader(name) — Get a response header
  • res.removeHeader(name) — Remove a response header

Examples

Routing based on URL

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>Home Page</h1>');
  } else if (req.url === '/about') {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>About Page</h1>');
  } else {
    res.writeHead(404, { 'Content-Type': 'text/html' });
    res.end('<h1>404 Not Found</h1>');
  }
});

server.listen(3000);

This manual routing works for small prototypes but grows unwieldy quickly. Each if branch checks the URL string, which means no parameterized routes (/users/:id), no middleware, and no default headers. For anything beyond a handful of endpoints, most teams reach for a router library.

Handling POST data

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.method === 'POST') {
    let body = '';
    
    req.on('data', (chunk) => {
      body += chunk.toString();
    });
    
    req.on('end', () => {
      console.log('Received:', body);
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ received: body }));
    });
  } else {
    res.writeHead(405, { 'Content-Type': 'text/plain' });
    res.end('Method not allowed');
  }
});

server.listen(3000);

POST body parsing requires listening for data events and assembling chunks into a complete string. There is no built-in JSON parsing — you get raw bytes and must handle encoding, content-type detection, and size limits yourself. For production use, stream the body through a parser rather than buffering the entire payload in memory.

JSON REST API

const http = require('http');

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
];

const server = http.createServer((req, res) => {
  // Set CORS headers
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.setHeader('Content-Type', 'application/json');
  
  if (req.url === '/api/users' && req.method === 'GET') {
    res.end(JSON.stringify(users));
  } else {
    res.writeHead(404);
    res.end(JSON.stringify({ error: 'Not found' }));
  }
});

server.listen(3000);

Setting CORS headers manually on every response is a pattern you see in minimal prototypes. In production, a middleware layer handles these concerns so individual route handlers stay focused on business logic. The key takeaway is that the core module gives you full control over every header that leaves the server.

Important notes

  • The http module is a low-level module. For production applications, consider using frameworks like Express.js or Fastify.
  • The http module does not support HTTPS. Use the https module for secure connections.
  • Always handle errors on both server and client requests to prevent unhandled exceptions.

Why the core module still matters

The built-in http module is still worth learning even if a framework sits on top of it later. It shows the request and response model directly, which makes it easier to debug header handling, streaming, and status codes. It also gives you a small, dependency-free option for prototypes or utility servers where you want full control over the wire format. Understanding the core API makes framework behavior much easier to read.

Request and response flow

An HTTP server in Node.js is a stream-oriented event loop: the request arrives, you inspect it, and the response is written back in chunks or as a finished payload. That flow is what makes the module flexible enough for both tiny demos and real APIs. Once you see the request as a readable stream and the response as a writable stream, the rest of the API becomes much easier to reason about.

See also