Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    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.

    Platforms & architectureread 20262 min read

    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)

    ↪ Connects to (8)

    All books