Node.js Design Patterns
Mario Casciaro and Luciano Mammino · 2020
The event loop, streams and async patterns that make Node.js backends scale, explained from first principles.
What it’s about
The third edition of a well-loved book on how Node.js really works and how to design with it. It starts with the reactor pattern and the event loop, moves through callbacks, promises and async/await, spends a lot of time on streams, then revisits the classic design patterns (factory, proxy, decorator, observer and friends) as they look in a module-based, asynchronous world, before finishing with scaling and messaging.
What I learned
The event loop is the whole game. Node is fast because one thread never waits. Block it with heavy synchronous work and everything stalls. Most Node performance advice is a version of “don’t block the loop”: push CPU-heavy work to workers or another service.
Streams make backpressure explicit. A stream only pulls more data when the consumer is ready. That’s the honest way to move a lot of data: you never pretend the slow part is fast.
Classic patterns still matter, they just look different. An observer is an EventEmitter; a proxy might wrap a
module. Knowing the pattern helps you recognise when a library is using one, and when you’re about to reinvent it badly.
Decouple producers from consumers. Queues, streams and messaging all exist so a fast producer can’t overwhelm a slow consumer.
What I applied
Reading it in 2026 also gave names to things I’d built by instinct in 2020. My CMS migration tool streamed content in batches instead of loading everything into memory, put adapters between two platforms’ content models, and made long runs resumable (A Resumable CMS Migration Tool, Resumable Batch Migrations).
The overlap with my day job, building real-time trading interfaces at a fintech, surprised me. The front-end version of the same problem is a socket pushing updates faster than the UI can sensibly render. Piping every message through React state re-rendered far more of the tree than it needed to. Giving the socket a single owner and letting only the leaf components subscribe to the latest values (transient store subscriptions) calmed it down (Zustand Transient Updates). It’s backpressure on the client: the feed can be as bursty as it likes, but the screen only needs the latest frame.
The server-side patterns I hold as reflection, because I haven’t shipped a Node backend at scale. Where I’d use them is any service in front of slow dependencies, model calls included, where streaming, queuing and timeouts decide whether the system degrades gracefully (Regress, Don’t Grind: Graceful Degradation).
Where it connects
Message Passing · Threads and Processes · Zustand Transient Updates · WebSockets · Architecting the AI-Driven Node.js Backend
↩ Mentioned in (4)
Case study: a Node.js tool I built at Elanco in 2020 to move whole sites from Contentful to Kentico Kontent: export to disk, a work queue, batched streaming, adapter layers and resumable runs. It became open-source tooling and a guest post on Kontent's blog.
Twenty-odd books across code, platforms, teams, trading and science fiction turn out to make the same five arguments. Here's the thread that connects them.
- Resumable Batch MigrationsGrowing
Patterns for moving large amounts of data between systems without running out of memory or starting over: extract to a staging area, queue the work, stream in batches, adapt between models, and checkpoint everything.
book · Soren Valdrik
Architecting the AI-Driven Node.js BackendBook 1 of The AI Backend Architecture Series: building scalable Node.js infrastructure for deep-learning applications.