
Node.js Design Patterns
Beschreibung
- Avoid common pitfalls in applying proven patterns to create robust, maintainable Node.js applications
- Enhance your development skills through a wealth of real-world examples and case studies
Book DescriptionNode.js underpins much of modern web development, reliably powering APIs and full-stack apps across all industries. Authors Luciano Mammino and Mario Casciaro offer a practical guide that unpacks the JavaScript runtime so you can write reliable, high-performance Node.js apps. Building on the highly rated third edition, this new edition adds fresh case studies and the latest Node.js developments: newer APIs and libraries, ESM improvements, practical security and production tips, and guidance on using Node.js with TypeScript. It also introduces a new chapter on testing that gives you a full introduction to testing philosophy and practical guidance on writing unit, integration, and end-to-end tests, giving you the confidence to write functional, stable, and reliable code. Real-world, end-to-end examples throughout the book show how to build microservices and distributed systems with Node.js, integrating production-proven technologies such as Redis, RabbitMQ, LevelDB, and ZeroMQ, the same components you'll find in scalable deployments at companies of all sizes. End-of-chapter exercises consolidate your understanding. By the end of this Node.js book, you'll have the design patterns, mindset, and hands-on skills every serious Node.js professional needs to confidently architect robust, efficient, and maintainable applications.What you will learn - Understand Node.js basics and its async event-driven architecture
- Write correct async code using callbacks, promises, and async/await
- Harness Node.js streams to create data-driven processing pipelines
- Implement trusted software design patterns for production-grade applications
- Write testable code and automated tests (unit, integration, E2E)
- Use advanced recipes: caching, batching, async init, offload CPU-bound work
- Build and scale microservices and distributed systems powered by Node.js
Who this book is forThis book is for you if you're a developer or software architect with basic knowledge of JavaScript and Node.js and want to get the most out of these technologies to maximize productivity, design quality, and scalability. It'll help you level up from junior to senior roles. This book is a tried-and-tested reference guide for readers at all levels. Even those with more experience will find value in the more advanced patterns and techniques presented. You're expected to have an intermediate understanding of web application development, databases, and software design principles.
Alle Preise
Weitere Details
Weitere Ausgaben
Inhalt
- Cover
- Title Page
- Copyright and Credits
- Forewords
- Contributors
- Table of Contents
- Preface
- Chapter 1: The Node.js Platform
- The Node.js philosophy
- Small core
- Small modules
- Small surface area
- Simplicity and pragmatism
- How Node.js works
- I/O is often the bottleneck
- Blocking I/O
- Non-blocking I/O
- Event demultiplexing
- The reactor pattern
- libuv, the I/O engine of Node.js
- The complete recipe for Node.js
- JavaScript in Node.js
- Run the latest JavaScript with confidence
- The module system
- Full access to operating system services
- Running native code
- Node.js and TypeScript
- Using TypeScript with Node.js
- The @types/node package
- Summary
- Chapter 2: The Module System
- The need for modules
- Module systems in JavaScript and Node.js
- The revealing module pattern
- ES modules
- Using ES modules in Node.js
- The ES module syntax
- Named exports and imports
- Default exports and imports
- Mixed exports
- Module identifiers
- Static and dynamic imports
- The module resolution algorithm
- Module loading in depth
- Loading phases
- Read-only live bindings
- Circular dependencies
- Modules that modify other modules
- How monkey patching affects type safety in TypeScript projects
- CommonJS modules
- ES modules and CommonJS-differences and interoperability
- Strict mode
- Top-level await
- Behavior of `this`
- Missing references in ES modules
- Import interoperability
- Import CommonJS modules from ES modules
- Import ES modules from CommonJS
- Importing JSON files
- Using modules in TypeScript
- The role of the TypeScript compiler
- Configuring the module output format
- Input module syntax and output emission
- Module resolution
- Summary
- Chapter 3: Callbacks and Events
- The Callback pattern
- The continuation-passing style
- Synchronous CPS
- Asynchronous CPS
- Non-CPS callbacks
- Synchronous or asynchronous?
- Writing an inconsistent function
- Unleashing Zalgo
- Using synchronous APIs
- Guaranteeing asynchronicity with deferred execution
- Node.js callback conventions
- The callback is the last argument
- Any error always comes first
- Propagating errors
- Avoiding uncaught exceptions
- The Observer pattern
- The EventEmitter
- Creating and using the EventEmitter
- Propagating errors
- Making any object observable
- The risk of memory leaks
- Synchronous and asynchronous events
- EventEmitter versus callbacks
- Combining callbacks and events
- Summary
- Exercises
- Chapter 4: Asynchronous Control Flow Patterns with Callbacks
- The challenges of asynchronous programming
- Creating a simple web spider
- Callback hell
- Callback best practices
- Callback discipline
- Applying the callback discipline
- Control flow patterns
- Sequential execution
- Executing a known set of tasks in sequence
- Sequential iteration
- Concurrent execution
- Web spider version 3
- The pattern
- Fixing race conditions with concurrent tasks
- Limited concurrent execution
- Limiting concurrency
- Globally limiting concurrency
- Summary
- Exercises
- Chapter 5: Asynchronous Control Flow Patterns with Promises and Async/Await
- Promises
- What is a promise?
- Promises/A+ and thenables
- The promise API
- Creating a promise
- Promisification
- Sequential execution and iteration
- Concurrent execution
- Limited concurrent execution
- Implementing the TaskQueue class with promises
- Updating the web spider
- Lazy promises
- Async/await
- Async functions and the await expression
- Top-level await
- Error handling with async/await
- A unified try...catch experience
- The "return" versus "return await" trap
- Sequential execution and iteration
- Antipattern - using async/await with Array.forEach for serial execution
- Concurrent execution
- Limited concurrent execution
- The problem with infinite recursive promise resolution chains
- Summary
- Exercises
- Chapter 6: Coding with Streams
- Discovering the importance of streams
- Buffering versus streaming
- Spatial efficiency
- Gzipping using a buffered API
- Gzipping using streams
- Time efficiency
- Composability
- Adding client-side encryption
- Adding server-side decryption
- Getting started with streams
- Anatomy of streams
- Readable streams
- Reading from a stream
- Implementing Readable streams
- Writable streams
- Writing to a stream
- Backpressure
- Implementing Writable streams
- Duplex streams
- Transform streams
- Implementing Transform streams
- Filtering and aggregating data with Transform streams
- PassThrough streams
- Observability
- Late piping
- Lazy streams
- Connecting streams using pipes
- Pipes and error handling
- Better error handling with pipeline()
- Asynchronous control flow patterns with streams
- Sequential execution
- Unordered concurrent execution
- Implementing an unordered concurrent stream
- Implementing a URL status monitoring application
- Unordered limited concurrent execution
- Ordered concurrent execution
- Piping patterns
- Combining streams
- Implementing a combined stream
- Forking streams
- Implementing a multiple checksum generator
- Merging streams
- Merging text files
- Multiplexing and demultiplexing
- Building a remote logger
- Multiplexing and demultiplexing object streams
- Readable stream utilities
- Mapping and transformation
- Filtering and iteration
- Searching and evaluation
- Limiting and reducing
- Web Streams
- Converting Node.js streams to Web Streams
- Converting Web Streams to Node.js streams
- Stream consumer utilities
- Summary
- Exercises
- Chapter 7: Creational Design Patterns
- Factory
- Decoupling object creation and implementation
- A mechanism to enforce encapsulation
- Building a simple code profiler
- In the wild
- Builder
- Implementing a URL object builder
- In the wild
- Revealing Constructor
- Building an immutable buffer
- In the wild
- Singleton
- Wiring modules
- Singleton dependencies
- Dependency Injection
- Summary
- Exercises
- Chapter 8: Structural Design Patterns
- Proxy
- Techniques for implementing proxies
- Object composition
- Object augmentation
- The built-in Proxy object
- A comparison of the different proxying techniques
- Creating a logging Writable stream
- Change Observer with Proxy
- In the wild
- Decorator
- Techniques for implementing decorators
- Composition
- Object decoration
- Decorating with the Proxy object
- Decorating a Level database
- Introducing Level and LevelDB
- Implementing a Level plugin
- In the wild
- The decorator proposal for ECMAScript
- The line between Proxy and Decorator
- Adapter
- Using Level through the filesystem API
- In the wild
- Summary
- Exercises
- Chapter 9: Behavioral Design Patterns
- Strategy
- Multi-format configuration objects
- In the wild
- State
- Implementing a basic failsafe socket
- In the wild
- Template
- A configuration manager template
- In the wild
- Iterator
- The iterator protocol
- The iterable protocol
- Iterators and iterables as a native JavaScript interface
- Implementing the iterable protocol on iterators
- Iterator utilities
- Generators
- Generators in theory
- A simple generator function
- Controlling a generator iterator
- How to use generators in place of iterators
- Async iterators
- Async generators
- Async iterators and Node.js streams
- Async iterator utilities
- In the wild
- Middleware
- Middleware in Express
- Middleware as a pattern
- Creating a middleware framework for ZeroMQ
- The middleware manager
- Implementing the middleware to process messages
- Using the ZeroMQ middleware framework
- In the wild
- Command
- The Task pattern
- A more complex command
- In the wild
- Summary
- Exercises
- Chapter 10: Testing: Patterns and Best Practices
- An introduction to software testing
- Definitions
- System under test
- Arrange, Act, Assert
- Code coverage
- Test doubles: Stubs, spies, and mocks
- Test-driven development
- Behavior-driven development
- Continuous integration
- Continuous delivery and continuous deployment
- Types of tests
- Unit tests
- Integration tests
- End-to-end tests
- Other types of tests
- The testing pyramid
- Writing tests with Node.js
- Our first unit test
- The Node.js test runner
- Our first test with the Node.js test runner
- Organizing tests
- Subtests
- Subtest concurrency
- Parametrized test cases
- Test suites
- Test runner tips and tricks
- Watch mode
- Targeted test execution with custom glob patterns
- Test reporters
- Collecting code coverage
- Using the test runner with TypeScript
- Writing unit tests
- Testing asynchronous code
- Mocking
- Creating spies with mock.fn()
- Mocking HTTP requests with the built-in test mock
- Mocking HTTP requests with Undici
- Mocking Node.js core modules
- Mocking other dependencies
- Problems with mocking imports
- Mocking imports versus dependency injection
- Writing integration tests
- Testing with a local database
- Testing a web application
- Setting up the project
- Writing E2E tests
- The application structure
- Home page
- Sign-in and sign-up forms
- Event Page
- My reservations
- The user flow
- Browser automation
- Writing an E2E test with Playwright
- Setting up a new Playwright project
- Understanding the Playwright API
- Understanding timeouts
- Testing our user flow
- Summary
- Exercises
- Chapter 11: Advanced Recipes
- Dealing with asynchronously initialized components
- The issue with asynchronously initialized components
- Local initialization check
- Delayed startup
- Pre-initialization queues
- Using the State pattern
- In the wild
- Asynchronous request batching and caching
- What's asynchronous request batching?
- Optimal asynchronous request caching
- An API server without caching or batching
- Batching and caching with promises
- Batching requests
- Batching and caching requests
- Canceling asynchronous operations
- A basic recipe for creating cancelable functions
- Wrapping asynchronous invocations
- Cancelable async functions with AbortController
- Running CPU-bound tasks
- Solving the subset sum problem
- Interleaving with setImmediate
- Interleaving the steps of the subset sum algorithm
- Considerations on the interleaving approach
- Using external processes
- Delegating the subset sum task to an external process
- Considerations for the multi-process approach
- Using worker threads
- Running the subset sum task in a worker thread
- Running CPU-bound tasks in production
- Summary
- Exercises
- Chapter 12: Scalability and Architectural Patterns
- An introduction to application scaling
- Scaling Node.js applications
- The three dimensions of scalability
- X-axis: cloning
- Y-axis: decomposing by service or functionality
- Z-axis: splitting by data partition
- Combining strategies across the scale cube
- Cloning and load balancing
- The cluster module
- Notes on the behavior of the cluster module
- Building a simple HTTP server
- Scaling with the cluster module
- Resiliency and availability with the cluster module
- Zero-downtime restart
- Dealing with stateful communications
- Sharing the state across multiple instances
- Sticky load balancing
- Scaling with a reverse proxy
- Load balancing with Nginx
- Dynamic horizontal scaling
- Using a service registry
- Implementing a dynamic load balancer
- Peer-to-peer load balancing
- Implementing an HTTP client that can balance requests across multiple servers
- Scaling applications using containers
- What is a container?
- Creating and running a container with Docker
- What is Kubernetes?
- Deploying and scaling an application on Kubernetes
- Decomposing complex applications
- Monolithic architecture
- The microservice architecture
- An example of a microservice architecture
- Microservices: advantages and disadvantages
- Integration patterns in a microservice architecture
- The API proxy
- API orchestration
- Integration with a message broker
- Summary
- Exercises
- Chapter 13: Messaging and Integration Patterns
- Fundamentals of a messaging system
- One way versus request/reply patterns
- Message types
- Command messages
- Event messages
- Document messages
- Push versus pull delivery semantics
- Pull delivery (consumer-initiated)
- Push delivery (producer-initiated)
- Choosing between pull and push delivery
- Asynchronous messaging, queues, and streams
- Peer-to-peer or broker-based messaging
- Publish/Subscribe pattern
- Building a minimalist real-time chat application
- Implementing the server side
- Implementing the client side
- Running and scaling the chat application
- Using Redis as a simple message broker
- Peer-to-peer Publish/Subscribe with ZeroMQ
- Introducing ZeroMQ
- Designing a peer-to-peer architecture for the chat server
- Using the ZeroMQ PUB/SUB sockets
- Reliable message delivery with queues
- Introducing AMQP
- Durable subscribers with AMQP and RabbitMQ
- Reliable messaging with streams
- Characteristics of a streaming platform
- Streams versus message queues
- Implementing the chat application using Redis streams
- Task distribution patterns
- The ZeroMQ fan-out/fan-in pattern
- PUSH/PULL sockets
- Building a distributed hashsum cracker with ZeroMQ
- Pipelines and competing consumers in AMQP
- Point-to-point communications and competing consumers
- Implementing the hashsum cracker using AMQP
- Distributing tasks with Redis streams
- Redis consumer groups
- Implementing the hashsum cracker using Redis streams
- Request/Reply patterns
- Correlation Identifier
- Implementing a request/reply abstraction using correlation identifiers
- Return address
- Implementing the Return Address pattern in AMQP
- Summary
- Exercises
- Other Books You May Enjoy
- Index
Preface
Over the last decade, Node.js has grown from an experimental runtime into one of the most important technologies in modern web development. It is now a staple in companies of every size, powering everything from quick prototypes to some of the most ambitious, large-scale systems in production today. This rise is no accident. Node.js combines the flexibility and reach of JavaScript, a language understood by millions and supported in every browser, with an event-driven asynchronous architecture that excels at handling I/O-bound workloads. On top of this, a thriving ecosystem of modules, frameworks, and tools has emerged, providing solutions for almost any problem a developer might face.
The ability to use the same language on both the client and the server has broken down barriers between frontend and backend work. Models, validation rules, and utility functions can be shared across the stack, creating tighter collaboration between teams and reducing duplication. This has opened the door to single-language application stacks that are fast to build, easier to maintain, and more adaptable to change.
Yet the very qualities that make Node.js appealing can also make it challenging. The asynchronous nature of JavaScript, while powerful, takes time and practice to master. The Node.js core library evolves with every release, adding features that are often underused or poorly understood. Choosing and integrating third-party modules can be overwhelming, especially when quality, maintenance, and compatibility vary. And building production-grade applications almost always requires integration with databases, caches, message brokers, and other services, where architectural decisions can have lasting consequences on performance, scalability, and maintainability.
These are not abstract concerns. They appear in real projects every day. The following are examples:
- Code that works during development but slows to a crawl under load
- Asynchronous flows that are hard to follow, hard to test, and even harder to debug
- Modules assembled without a clear structure, making future changes risky and expensive
- Services that scale in one dimension but become bottlenecks in another
- Integrations that fail when the volume of data or traffic increases unexpectedly
This book was written to address these challenges directly. Node.js Design Patterns distills years of experience building large, distributed, high-performance systems into a structured, practical, and approachable guide. It covers the essential concepts that every Node.js developer should understand, such as the event loop, the module system, streams, and asynchronous programming techniques.
From this foundation, it introduces a broad set of patterns to solve recurring design problems. You will find most of the traditional patterns from the original Gang of Four book adapted to the realities of JavaScript and Node.js, alongside modern patterns that have emerged from the unique nature of the platform. These are patterns you can reuse in your own projects to tackle common challenges with clarity and consistency.
Patterns are more than recipes. They are a shared vocabulary and a way of thinking about problems. Once you know them, you can approach design decisions with a library of proven solutions in mind, instead of reinventing the wheel each time. This makes your code not only more robust and maintainable but also easier to discuss and share with other developers.
This book has been designed to be a loyal guide. You can read it from beginning to end, following the progression from fundamentals to advanced architecture, or you can keep it at your side as a long-term reference. Each chapter blends theory with practical examples and exercises, so you can put what you have learned into practice immediately and confirm that the concepts are clear before moving on. The examples are realistic and grounded in the kinds of problems you will face in production. The exercises give you the opportunity to test your understanding and refine your skills.
Over the years, Node.js Design Patterns has become one of the most trusted resources for mastering Node.js, helping tens of thousands of developers worldwide and being translated into multiple languages. This fourth edition builds on that foundation with fully updated content for Node.js 24; modern JavaScript features such as ECMAScript modules and async/await; a brand-new chapter dedicated to testing with practical strategies for unit, integration, and end-to-end tests; and expanded coverage of scalability, security, and architecture for today's production environments.
The main themes you will encounter throughout the book mirror the most common challenges in real-world Node.js development:
- Mastering asynchronous programming so that callbacks, promises, and async/await become tools you use with confidence rather than sources of bugs and confusion
- Designing modular, maintainable architectures using proven patterns adapted to JavaScript's capabilities and constraints
- Building scalable systems by understanding how to make the most of Node.js's strengths and knowing when to use clustering, containers, and distributed architectures
- Integrating effectively with other technologies, from databases to message brokers, so that your applications can grow without losing performance or reliability
This book is also about the developer's journey. While it is rooted in Node.js, it goes beyond the runtime to cover topics that are essential for any senior engineer: security, scalability, microservices, systems architecture, messaging patterns, and testing strategies. These concepts are just as relevant in other programming environments, and mastering them will prepare you to contribute confidently to complex and ambitious projects. Our goal is to help you grow from a junior or mid-level developer into a professional who can design and deliver production-grade systems with skill and clarity. In many ways, this is the book we wished we had when we began our own web development careers: a single resource that combines the theory, the practice, and the patterns we have learned over our years of experience.
If you are returning from a previous edition, thank you for trusting this book once again as your reference. If you are reading for the first time, welcome. We hope you find in these pages the same value that has helped tens of thousands of developers around the world build better systems, sharpen their skills, and take the next steps in their careers.
Also, if you are just starting to work with Node.js, you are very welcome in this vibrant community. It is a friendly one, so do not hesitate to reach out, share your progress, and even contribute by publishing your own modules or helping others with their projects. After all, that is the beauty of open-source communities: we are all here to get better together, and every small contribution makes a real difference.
Let's keep building great things together.
Who this book is for
This book is for developers who have already had initial contact with Node.js and now want to get the most out of it in terms of productivity, design quality, and scalability. You are only required to have had some prior exposure to the technology through some basic examples and some degree of familiarity with the JavaScript language, since this book will cover some basic concepts as well. Developers with intermediate experience in Node.js will also find the techniques presented in this book beneficial.
Some background in software design theory is also an advantage to understand some of the concepts presented.
This book assumes that you have a working knowledge of web application development, web services, databases, and data structures.
What this book covers
Chapter 1, The Node.js Platform, introduces the "Node way" of building applications and why its asynchronous mindset is both powerful and challenging. It covers the Node.js architecture, the event loop, the reactor pattern, and the implications of running JavaScript on the server. These foundational concepts shape everything from how you handle I/O to how you structure your applications.
Chapter 2, The Module System, explores the Node.js module system in depth, with a focus on ECMAScript modules (ESM) as the modern standard for writing modular JavaScript. It explains how ESM differs from CommonJS (Node.js's original module system), how to manage interoperability between the two, and how TypeScript fits into the picture. This chapter will help you avoid common pitfalls and work confidently with modules.
Chapter 3, Callbacks and Events, introduces Node.js's core asynchronous patterns: callbacks and the event-driven model. You will learn how callbacks work, how to avoid common mistakes such as Zalgo text and uncaught exceptions, and how to use the Observer pattern and EventEmitter to respond to asynchronous events predictably.
Chapter 4, Asynchronous Control Flow Patterns with Callbacks, presents proven techniques for managing asynchronous operations using callbacks. You will learn about sequential, concurrent, and limited concurrent execution patterns, along with best practices for avoiding callback hell...
Systemvoraussetzungen
Dateiformat: ePUB
Kopierschutz: Adobe-DRM (Digital Rights Management)
Systemvoraussetzungen:
- Computer (Windows; MacOS X; Linux): Installieren Sie bereits vor dem Download die kostenlose Software Adobe Digital Editions (siehe E-Book Hilfe).
- Tablet/Smartphone (Android; iOS): Installieren Sie bereits vor dem Download die kostenlose App Adobe Digital Editions oder die App PocketBook (siehe E-Book Hilfe).
- E-Book-Reader: Bookeen, Kobo, Pocketbook, Sony, Tolino u.v.a.m. (nicht Kindle)
Das Dateiformat ePUB ist sehr gut für Romane und Sachbücher geeignet – also für „fließenden” Text ohne komplexes Layout. Bei E-Readern oder Smartphones passt sich der Zeilen- und Seitenumbruch automatisch den kleinen Displays an.
Mit Adobe-DRM wird hier ein „harter” Kopierschutz verwendet. Wenn die notwendigen Voraussetzungen nicht vorliegen, können Sie das E-Book leider nicht öffnen. Daher müssen Sie bereits vor dem Download Ihre Lese-Hardware vorbereiten.
Bitte beachten Sie: Wir empfehlen Ihnen unbedingt nach Installation der Lese-Software diese mit Ihrer persönlichen Adobe-ID zu autorisieren!
Weitere Informationen finden Sie in unserer E-Book Hilfe.
Dateiformat: ePUB
Kopierschutz: ohne DRM (Digital Rights Management)
Systemvoraussetzungen:
- Computer (Windows; MacOS X; Linux): Verwenden Sie eine Lese-Software, die das Dateiformat ePUB verarbeiten kann: z.B. Adobe Digital Editions oder FBReader – beide kostenlos (siehe E-Book Hilfe).
- Tablet/Smartphone (Android; iOS): Installieren Sie bereits vor dem Download die kostenlose App Adobe Digital Editions oder die App PocketBook (siehe E-Book Hilfe).
- E-Book-Reader: Bookeen, Kobo, Pocketbook, Sony, Tolino u.v.a.m.
Das Dateiformat ePUB ist sehr gut für Romane und Sachbücher geeignet – also für „glatten” Text ohne komplexes Layout. Bei E-Readern oder Smartphones passt sich der Zeilen- und Seitenumbruch automatisch den kleinen Displays an.
Ein Kopierschutz bzw. Digital Rights Management wird bei diesem E-Book nicht eingesetzt.
Weitere Informationen finden Sie in unserer E-Book Hilfe.