Networking

1. Introduction 🌐

Node.js was built from the start with networking in mind — its non-blocking I/O model makes it especially well suited for servers handling many concurrent connections. This tutorial covers the core networking modules: http, net, dns, tls, and more.

2. Networking in Node.js 🔌

Node exposes networking at multiple layers: low-level TCP/UDP sockets via net and dgram, encrypted connections via tls, and the familiar HTTP/HTTPS protocols built on top of them.

Networking Stack
http / https (application layer)
tls (encryption layer)
net / dgram (transport layer: TCP / UDP)

3. HTTP Module 📡

The http module (node:http) provides everything needed to create HTTP servers and clients without any external dependencies.

http-import.js

const http = require('node:http');

4. HTTPS Module 🔒

https mirrors the http API, adding TLS encryption. It requires a certificate and private key.

https-server.js

const https = require('node:https');
const fs = require('node:fs');

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

https.createServer(options, (req, res) => {
  res.end('Secure response');
}).listen(443);

5. Creating an HTTP Server 🖥️

basic-server.js

const http = require('node:http');

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

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

6. Handling Requests 📥

The req object is a Readable stream carrying the request method, URL, headers, and body.

handling-requests.js

const http = require('node:http');

http.createServer((req, res) => {
  console.log(req.method, req.url);

  let body = '';
  req.on('data', (chunk) => (body += chunk));
  req.on('end', () => {
    console.log('Body:', body);
    res.end('Received');
  });
}).listen(3000);

7. Sending Responses 📤

sending-responses.js

const http = require('node:http');

http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.write(JSON.stringify({ message: 'ok' }));
  res.end();
}).listen(3000);

8. Request Headers 📋

request-headers.js

http.createServer((req, res) => {
  console.log(req.headers['content-type']);
  console.log(req.headers['user-agent']);
  res.end();
}).listen(3000);

9. Response Headers 📋

response-headers.js

http.createServer((req, res) => {
  res.setHeader('X-Powered-By', 'Node.js');
  res.setHeader('Content-Type', 'text/html');
  res.end('<h1>Hello</h1>');
}).listen(3000);

10. Status Codes 🚦

CodeMeaning
200OK
201Created
301 / 302Redirect (permanent / temporary)
400Bad Request
401 / 403Unauthorized / Forbidden
404Not Found
500Internal Server Error

11. URL Handling 🔗

11.1 URL Module

The URL class (global, backed by node:url) parses a URL string into its component parts.

url-module.js

const myUrl = new URL('https://example.com:8080/path?name=node#section');

console.log(myUrl.hostname); // 'example.com'
console.log(myUrl.pathname); // '/path'
console.log(myUrl.port);     // '8080'

11.2 URL Parsing

url-parsing.js

const http = require('node:http');

http.createServer((req, res) => {
  const myUrl = new URL(req.url, `http://${req.headers.host}`);
  console.log(myUrl.pathname);
  res.end();
}).listen(3000);

11.3 Query Strings

query-strings.js

const myUrl = new URL('https://example.com/search?q=node&limit=10');

console.log(myUrl.searchParams.get('q'));     // 'node'
console.log(myUrl.searchParams.get('limit')); // '10'

12. DNS Module 🧭

The dns module resolves domain names to IP addresses and vice versa.

dns-module.js

const dns = require('node:dns/promises');

const addresses = await dns.resolve4('example.com');
console.log(addresses);

const { address } = await dns.lookup('example.com');
console.log(address);

Information

dns.lookup() uses the OS resolver (and the thread pool); dns.resolve4() queries DNS servers directly and supports more record types.

13. Net Module (TCP) 🔌

13.1 TCP Servers

tcp-server.js

const net = require('node:net');

const server = net.createServer((socket) => {
  console.log('Client connected');

  socket.on('data', (data) => {
    console.log('Received:', data.toString());
    socket.write('Echo: ' + data);
  });

  socket.on('end', () => console.log('Client disconnected'));
});

server.listen(4000, () => console.log('TCP server on port 4000'));

13.2 TCP Clients

tcp-client.js

const net = require('node:net');

const client = net.createConnection({ port: 4000 }, () => {
  console.log('Connected to server');
  client.write('Hello from client');
});

client.on('data', (data) => {
  console.log('Server says:', data.toString());
  client.end();
});

14. UDP Module (Datagrams) 📮

dgram provides UDP sockets — connectionless, unordered, and lightweight, suited for real-time applications like video streaming or gaming where occasional packet loss is acceptable.

udp-server.js

const dgram = require('node:dgram');

const server = dgram.createSocket('udp4');

server.on('message', (msg, rinfo) => {
  console.log(`Received: ${msg} from ${rinfo.address}:${rinfo.port}`);
});

server.bind(5000);

15. TLS Module 🔐

tls provides encrypted socket communication built directly on top of net, implementing TLS/SSL. It underpins https.

tls-server.js

const tls = require('node:tls');
const fs = require('node:fs');

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

tls.createServer(options, (socket) => {
  socket.write('Encrypted hello\n');
  socket.end();
}).listen(6000);

16. HTTP Keep-Alive ♻️

Keep-Alive reuses a single TCP connection for multiple HTTP requests, avoiding the overhead of a new handshake per request.

keep-alive.js

const http = require('node:http');

const agent = new http.Agent({ keepAlive: true, maxSockets: 50 });

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

17. Compression 🗜️

Combining zlib with HTTP headers lets servers send gzip- or brotli-compressed responses, reducing bandwidth.

http-compression.js

const http = require('node:http');
const zlib = require('node:zlib');
const fs = require('node:fs');

http.createServer((req, res) => {
  res.setHeader('Content-Encoding', 'gzip');
  fs.createReadStream('./large.html').pipe(zlib.createGzip()).pipe(res);
}).listen(3000);

18. CORS Basics 🌍

CORS headers tell browsers which origins are permitted to access a resource from a different domain than the one serving the page.

cors-basics.js

http.createServer((req, res) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.end('OK');
}).listen(3000);

Caution

Setting Access-Control-Allow-Origin: * allows any site to read the response — avoid it for endpoints that return sensitive or user-specific data.

19. Proxy Servers 🔀

A proxy server sits between a client and the destination server, forwarding requests on the client's behalf — often used for caching, filtering, or anonymization.

simple-proxy.js

const http = require('node:http');

http.createServer((clientReq, clientRes) => {
  const proxyReq = http.request(
    { hostname: 'example.com', port: 80, path: clientReq.url, method: clientReq.method, headers: clientReq.headers },
    (proxyRes) => {
      clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
      proxyRes.pipe(clientRes);
    }
  );
  clientReq.pipe(proxyReq);
}).listen(8080);

20. Reverse Proxies 🔁

A reverse proxy sits in front of one or more backend servers, routing incoming requests to the appropriate service — commonly used for load balancing, TLS termination, and routing by path or subdomain.

Client
Reverse Proxy
Backend Server A
Backend Server B

Reference

Production reverse proxies are usually handled by dedicated tools like Nginx or HAProxy rather than hand-rolled Node code.

21. Performance Optimization ⚡

  • Enable Keep-Alive agents for outbound requests to reduce connection overhead.
  • Compress responses with gzip or brotli for text-heavy payloads.
  • Use streaming (.pipe()) instead of buffering entire request/response bodies.
  • Scale across CPU cores using the cluster module or a process manager.

22. Best Practices ✅

  • Always set explicit timeouts on servers and clients to avoid hanging connections.
  • Validate and sanitize all incoming URL and header values.
  • Use https/tls for anything handling sensitive data.
  • Prefer a well-tested framework (like Express or Fastify) over hand-rolled routing for production apps.

23. Common Mistakes ⚠️

  • Forgetting to call res.end(), leaving requests hanging indefinitely.
  • Not handling the 'error' event on sockets and servers.
  • Setting Access-Control-Allow-Origin: * on endpoints returning sensitive data.
  • Buffering entire large request bodies into memory instead of streaming them.
  • Not reusing connections (missing Keep-Alive) when making many outbound requests.

24. Frequently Asked Questions ❓

Question

Should I build my own HTTP server or use a framework?

Answer

For anything beyond a small prototype, a framework like Express or Fastify handles routing, middleware, and edge cases far more robustly than hand-rolled http code.

Question

What's the difference between net and http?

Answer

net works at the raw TCP level with no built-in protocol semantics; http is built on top of net and understands request/response framing, headers, and methods.

Question

When should I use UDP instead of TCP?

Answer

Use UDP when speed matters more than guaranteed delivery or ordering — real-time video, gaming, or telemetry — and TCP for everything requiring reliability.

25. Summary 📝

Summary

Node's networking stack spans from low-level net/dgram sockets to the high-level http/https APIs, all built on the same non-blocking, event-driven foundation. Understanding requests, responses, URLs, DNS, and concepts like Keep-Alive, compression, and reverse proxies equips you to build and reason about real-world networked Node applications.