HTTP & HTTPS Modules

5 questions found

How would you create a basic HTTP server using only Node's built-in node:http module, without a framework like Express?

Beginner
http.createServer() takes a callback (or listener function) that's invoked for every incoming request, receiving a request object (with method, url, and headers) and a response object used to write the response body and send it back to the client -- this is the low-level foundation that frameworks like Express are built on top of, adding routing, middleware, and other conveniences absent from the raw module.
const http = require('node:http');

const server = http.createServer((req, res) => {
  if (req.url === '/' && req.method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello, World!');
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
});

server.listen(3000, () => console.log('Server running on port 3000'));
Real-world example A lightweight internal health-check service that needs to respond to exactly one simple GET request is built directly on the raw node:http module rather than pulling in Express, since the added routing and middleware capabilities of a full framework aren't needed for such a minimal, single-purpose server.

Common follow-ups: What specific conveniences does Express add on top of this raw http.createServer() foundation?;How would you manually parse the request body from a POST request using only the raw http module, without a framework's body-parsing middleware?

Express & Middleware;Core Node.js Modules

How does Node's node:https module differ from node:http, and what additional configuration does it require?

Intermediate
The https module provides the same API surface as http but wraps connections in TLS encryption -- creating an HTTPS server requires providing a TLS certificate and private key (either self-signed for development, or issued by a trusted certificate authority like Let's Encrypt for production), whereas plain http requires no such configuration at all since it transmits data unencrypted.
const https = require('node:https');
const fs = require('node:fs');

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

https.createServer(options, (req, res) => {
  res.end('Secure response');
}).listen(443);
Real-world example A production Node.js application typically doesn't run its own https.createServer() at all -- instead, TLS termination is handled by a reverse proxy like Nginx or a cloud load balancer in front of it, with the Node.js process itself only ever handling plain HTTP behind that already-secured boundary.

Common follow-ups: Why is it more common in production to terminate TLS at a reverse proxy rather than directly within the Node.js process?;How would you configure automatic certificate renewal using a service like Let's Encrypt for a directly-exposed HTTPS Node.js server?

Security;Cloud & DevOps

How would you make an outgoing HTTP request from Node.js using the built-in fetch API versus the older http.request()?

Advanced
Modern Node.js (v18+) includes a global fetch() function matching the browser's Fetch API, providing a much more ergonomic, promise-based interface for making HTTP requests without any external dependency -- the older http.request()/https.request() APIs are lower-level, stream-based, and callback-oriented, requiring manually collecting response data chunks; fetch() is now generally the preferred choice for most application code making outgoing requests, with http.request() reserved for cases needing finer-grained control (like streaming a very large response without buffering it).
// Modern: fetch(), built in, promise-based
const response = await fetch('https://api.example.com/users');
const data = await response.json();

// Older: http.request(), lower-level, requires manual chunk collection
const chunks = [];
http.request(url, (res) => {
  res.on('data', (chunk) => chunks.push(chunk));
  res.on('end', () => console.log(Buffer.concat(chunks).toString()));
}).end();
Real-world example A codebase that previously depended on the popular 'node-fetch' or 'axios' packages purely for making simple outgoing API calls removes that dependency entirely after upgrading to Node 18+, switching to the now-built-in global fetch() function with no functional loss for its straightforward use cases.

Common follow-ups: In what specific scenario would you still reach for http.request() directly over fetch(), given fetch()'s simpler API?;How does fetch()'s AbortController-based cancellation compare to how http.request() handles request cancellation?

Async Patterns;Core Node.js Modules

What is HTTP keep-alive, and how does Node's http.Agent manage connection reuse for outgoing requests?

Intermediate
HTTP keep-alive lets a single TCP connection be reused for multiple sequential HTTP requests to the same host, avoiding the overhead of establishing a new TCP (and, for HTTPS, TLS) handshake for every individual request -- Node's http.Agent manages a pool of these persistent connections for outgoing requests, and reusing an existing Agent (rather than letting one be created fresh per request) is important for performance when an application makes many requests to the same downstream service.
const http = require('node:http');

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

// Reusing the same agent across many requests to the same host avoids repeated handshakes
fetch('https://api.example.com/data', { agent }); // (with a library supporting a custom agent)
Real-world example A service making thousands of outgoing requests per minute to the same downstream API saw a meaningful latency improvement after ensuring a single shared, keep-alive-enabled http.Agent was reused across all those requests, rather than the default behavior of Node.js creating a fresh connection (and TLS handshake, for HTTPS) for many of them.

Common follow-ups: What's the tradeoff of setting maxSockets very high versus keeping it constrained for a given downstream service?;How does connection reuse interact with a downstream server's own connection timeout settings?

Performance Optimization & Profiling;Microservices Architecture with Node.js

How would you implement HTTP/2 server push or basic HTTP/2 support in Node.js using the node:http2 module?

Advanced
The http2 module provides native support for the HTTP/2 protocol, which offers multiplexed requests over a single connection (avoiding the head-of-line blocking of HTTP/1.1's connection-per-request model), header compression, and server push (letting a server proactively send resources it knows a client will need, before the client explicitly requests them) -- though server push has since been deprecated by most browsers in favor of other techniques like preload hints, multiplexing alone still provides significant performance benefits for applications making many small requests.
const http2 = require('node:http2');
const fs = require('node:fs');

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

server.on('stream', (stream, headers) => {
  stream.respond({ ':status': 200, 'content-type': 'text/plain' });
  stream.end('Hello over HTTP/2');
});

server.listen(443);
Real-world example An API gateway handling many small, frequent requests from mobile clients over unreliable networks adopts HTTP/2 specifically for its connection multiplexing, letting many concurrent requests share a single TCP connection instead of requiring several separate connections, which particularly benefits clients on high-latency mobile networks.

Common follow-ups: Why has server push fallen out of favor despite initially seeming like a valuable feature?;What compatibility considerations exist when running an HTTP/2 server behind infrastructure (like some older load balancers) that might not fully support the protocol?

Performance Optimization & Profiling;Security