... in the previous chapters...

Block IP addresses in Node.js and Express: net.BlockList, ::ffff: addresses and X-Forwarded-For spoofing

Block single IPs, ranges and CIDR subnets in a Node.js or Express server with the built-in net.BlockList, without any package. The common snippet, x-forwarded-for || remoteAddress, fails twice: it never matches because Node reports ::ffff: addresses, and any client can bypass it with one header. Here's a version that works directly and behind nginx, tested on Node 24 and 26 with Express 5.

Running Node.js in Docker: one-off scripts, --watch for development, and shutting down properly

Run Node.js scripts and servers in Docker without installing Node, then make them stop correctly. Measured on Node 24 and 26: plain node as PID 1 ignores Ctrl+C and gets SIGKILLed after 10 seconds, npm start swallows signals, and a "graceful" server.close() can still hang on keep-alive sockets. Here are the fixes, plus a production Dockerfile with a healthcheck that needs no curl.

Detect a user's country from their IP address in Node.js (free offline database, no API calls)

Look up a visitor's country, or city, from their IP address in Node.js using a free MMDB database and the maxmind package: about a million lookups per second, with no API key and no network calls. It also covers the validation gotcha that returns a country for garbage input, which IP to look up, and when your CDN has already done the work. Tested on Node 24 and 26.

Server-Sent Events in Node.js: live updates with EventSource, reconnects and Last-Event-ID

Push live updates from Node.js to the browser over plain HTTP with Server-Sent Events: no WebSocket server and no dependencies. A ~60-line server with event ids, catch-up after a reconnect, a configurable retry delay, keep-alives and a clean way to stop, all verified in Chromium, Firefox and WebKit. Plus the six-connection limit that bites on HTTP/1.1.

5 ways to create an HTTP or HTTP/2 server in Node.js: node:http, Express 5, Fastify 5, Hono and node:http2

The same small JSON API built five ways: plain node:http, Express 5, Fastify 5, Hono, and an HTTP/2 server that browsers can actually reach. Testing them turned up a common route-validation bug, an Express 5 migration trap, and the reason the usual http2.createServer() snippet silently ignores your TLS certificate. Tested on Node 24 and 26 with curl and Chromium.

Batch convert a folder of images to WebP and AVIF with Node.js (sharp) or FFmpeg

A ~70-line Node.js script that converts every JPG and PNG in a folder tree to WebP and AVIF with sharp — with resizing, EXIF auto-rotation, skip-if-up-to-date and parallelism, and no glob package thanks to the built-in fs.glob. Plus SVG to PNG, the FFmpeg one-liners, and the transparency gotcha that FFmpeg won't warn you about. Verified on node:24-slim and node:26-slim.

fetch() with a timeout: AbortSignal.timeout(), AbortSignal.any() and the setTimeout trap

fetch() has no timeout option, but one line fixes that: AbortSignal.timeout(ms). Here's how it behaves while you wait for headers and while you read the body, how to combine it with a cancel button using AbortSignal.any(), how to tell a timeout from a cancel, and why the old setTimeout + AbortController recipe keeps Node processes alive. Tested on Node 24 and 26.

Stop CSS columns from splitting an element: break-inside: avoid

With CSS columns, a card or list item can get cut in half, with its top at the bottom of one column and the rest at the top of the next. One line fixes it: break-inside: avoid. Tested in Chromium, Firefox and WebKit, including what the legacy page-break-inside does today and the case it can't fix.

An accessible pure CSS toggle switch: one checkbox, appearance: none, role="switch"

A toggle switch needs no JavaScript and no extra markup: style a real checkbox with appearance: none and give it role="switch". Unlike the classic "hidden checkbox + styled label" trick, it stays reachable with the keyboard, announces itself to screen readers, works in forced-colors mode and respects reduced motion. Tested in Chromium, Firefox and WebKit.

Format bytes as kB, MB and GB in JavaScript: Intl.NumberFormat, 1000 vs 1024, and the edge cases

A small, tested formatBytes() that turns 2151099283 into "2.2 GB" (or "2 GiB"), with localised output from Intl.NumberFormat. Plus why the popular Math.log one-liner prints "NaN undefined" for 0 and "1024.00 KB" for 1,048,575 bytes, and which convention (1000 or 1024) to use.

Serve AVIF or WebP automatically from Node.js based on the Accept header (and why you need Vary: Accept)

An Express middleware that serves photo.avif or photo.webp when a browser asks for photo.jpg and accepts the newer format, with no HTML changes. Testing the original version of this middleware found four bugs, including the dangerous one: without Vary: Accept, a cache in front of the server hands AVIF to clients that can't display it. Verified with curl, an nginx cache, and Chromium, Firefox and WebKit.

Modal dialogs without a library: <dialog>, showModal(), :modal, commandfor and closedby

The native <dialog> element now handles almost everything a modal library did: an inert background, Esc to close, focus that moves in and back out, a ::backdrop, and, with invoker commands, opening without any JavaScript. Tested in Chromium, Firefox and WebKit, including the parts it still doesn't do for you: scroll locking, a stale returnValue, and light dismiss in Safari.

About Code with Node.js

This is a personal blog and reference point of a Node.js developer.

I write and explain how different Node and JavaScript aspects work, as well as research popular and cool packages, and of course fail time to time.