iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

https / fetch

node:https for clients + servers. TLS certs, custom CA, mTLS, and the patterns for production HTTPS.

Node — node:https

EXAMPLE
import https from 'node:https';
import fs from 'node:fs/promises';

// ===== HTTPS client =====
const data = await new Promise((resolve, reject) => {
  https.get('https://api.github.com/repos/nodejs/node', { headers: { 'user-agent': 'demo' } }, (res) => {
    let body = '';
    res.on('data', (chunk) => body += chunk);
    res.on('end', () => resolve(JSON.parse(body)));
    res.on('error', reject);
  });
});

// Easier: fetch() (built-in since Node 18):
const r = await fetch('https://api.github.com/repos/nodejs/node', { headers: { 'user-agent': 'demo' } });
const json = await r.json();

// ===== HTTPS server =====
const server = https.createServer({
  key: await fs.readFile('./key.pem'),
  cert: await fs.readFile('./cert.pem'),
}, (req, res) => {
  res.writeHead(200, { 'content-type': 'text/plain' });
  res.end('hello tls\n');
});

server.listen(8443, () => console.log('https://localhost:8443'));

// ===== Self-signed cert for dev =====
// openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'

// In dev clients, allow self-signed:
const r2 = await fetch('https://localhost:8443', { agent: new https.Agent({ rejectUnauthorized: false }) });
// NEVER do this in production. Use Let's Encrypt / mkcert for local TLS.

// ===== Custom CA bundle (corporate proxies) =====
const ca = await fs.readFile('/etc/ssl/corp-ca.pem');
const r3 = await fetch('https://internal.corp', { agent: new https.Agent({ ca }) });

// Or set globally:
// process.env.NODE_EXTRA_CA_CERTS = '/etc/ssl/corp-ca.pem';

// ===== mTLS (client certificate auth) =====
const r4 = await fetch('https://api.internal', {
  agent: new https.Agent({
    cert: await fs.readFile('./client.crt'),
    key: await fs.readFile('./client.key'),
    ca: await fs.readFile('./ca.crt'),
  }),
});

// ===== HTTP/2 =====
import http2 from 'node:http2';
const h2 = http2.createSecureServer({
  key: await fs.readFile('./key.pem'),
  cert: await fs.readFile('./cert.pem'),
});
h2.on('stream', (stream, headers) => {
  stream.respond({ ':status': 200, 'content-type': 'text/plain' });
  stream.end('h2 hello');
});
h2.listen(8443);

// ===== In production =====
// Usually you terminate TLS at a reverse proxy (nginx, Caddy, ELB, ALB).
// Node serves plain HTTP behind it. Simpler ops, fewer cert renewals in app code.

// ===== Patterns to internalise =====
// - fetch() for clients in modern Node
// - Terminate TLS at the edge in prod; Node speaks HTTP
// - Custom CA via NODE_EXTRA_CA_CERTS, not rejectUnauthorized: false
// - HTTP/2 only when you want multiplex + push (most cases stick to /1.1)

// ===== Pitfalls =====
// - rejectUnauthorized: false in production -> MITM accepted
// - Forgetting to renew certs (Let's Encrypt + auto-renew)
// - Self-signed certs in CI tests without --insecure-ca handling
// - HTTP/2 streams not properly closed -> connection leaks

Why it matters

fetch() handles most HTTPS clients today; node:https is still there for low-level control + mTLS + custom CAs. In production terminate TLS at the edge proxy; in dev use mkcert for trusted local certs. NODE_EXTRA_CA_CERTS over rejectUnauthorized: false.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Node 18+ has fetch built in.
const r = await fetch('https://httpbin.org/json');
console.log(await r.json());
Try it Yourself »

Discussion

Loading…