📄 Page
1
Efficient Node.js A Beyond-the-Basics Guide Samer Buna
📄 Page
2
9 7 8 1 0 9 8 1 4 5 1 9 4 5 6 5 9 9 ISBN: 978-1-098-14519-4 US $65.99 CAN $82.99 WEB DEVELOPMENT Samer Buna has over 20 years of experience in software development, API design, database management, and scalability. He’s authored several technical books and online courses on JavaScript, Node.js, React, and more. Take your web development skills from browser to server with Node.js, the popular backend framework used by more than 10 million developers at companies like Amazon, Netflix, and LinkedIn, to name just a few. If you’re comfortable working with JavaScript, this practical guide from Samer Buna will show you how to effectively build and maintain even the most complex Node.js applications. Following a hands-on, project-based approach, you’ll move from key fundamentals to advanced concepts such as modules, packages, event-driven architecture, streams, child processes, scaling, testing, deployment, and much more—all while focusing on what actually matters in practice. • Understand Node.js’s native capabilities in order to best use the powerful libraries and tools in its ecosystem • Utilize Node.js modules and packages • Explore Node.js’s event-driven architecture, streams, and child processes • Create, test, and maintain efficient and scalable Node.js applications Efficient Node.js “For anyone starting with Node.js, this book of fers a clear and ef f icient path to mastering the basics. For experienced developers, it provides insights that f ill in gaps or of fer new perspectives on familiar concepts. Highly recommended.” Sujith Neelakandan, @sujithvn “This book truly captures the essence of Node.js. The relatable analogies and clear summaries make learning feel ef fortless and engaging.” Hazem Twair, full stack software engineer
📄 Page
3
Samer Buna Efficient Node.js A Beyond-the-Basics Guide
📄 Page
4
978-1-098-14519-4 [LSI] Efficient Node.js by Samer Buna Copyright © 2025 Samer Buna. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://oreilly.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com. Acquisitions Editor: Amanda Quinn Development Editor: Jeff Bleiel Production Editor: Gregory Hyman Copyeditor: Miah Sandvik Proofreader: Stephanie English Indexer: Judith McConville Interior Designer: David Futato Cover Designer: Karen Montgomery Illustrator: Kate Dullea January 2025: First Edition Revision History for the First Edition 2025-01-08: First Release See http://oreilly.com/catalog/errata.csp?isbn=9781098145194 for release details. The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Efficient Node.js, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. The views expressed in this work are those of the author and do not represent the publisher’s views. While the publisher and the author have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the author disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights.
📄 Page
5
Table of Contents Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii 1. Node Fundamentals. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Introducing Node 1 The JavaScript Language 3 Executing Node Code 4 Using Built-In Modules 7 Using Packages 10 ES Modules 15 Asynchronous Operations 19 The Non-Blocking Model 21 Node Built-In Modules 28 Node Packages 30 Arguments Against Node 31 Summary 32 2. Scripts and Modules. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Node CLI 35 Options and Arguments 36 Environment Variables 40 REPL Mode 43 Node Modules 49 Resolving Modules 49 Loading Modules 50 Scoping Modules 51 Executing Modules 53 Caching Modules 57 Summary 58 iii
📄 Page
6
3. Asynchrony and Events. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Sync Versus Async Handling 59 Handler Functions 63 Promises 65 async/await 67 An Analogy for Promises 69 The Event Loop 72 Event Emitters 74 Asynchrony 76 Errors 78 Examples 79 Summary 81 4. Errors and Debugging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Throwing and Catching Errors 83 Types of Errors 85 Standard Errors 85 System Errors 87 Custom Errors 89 Layered Error Management 91 Debugging in Node 94 Preventive Measures 96 Code Quality Tools 96 Immutable Objects 96 Testing 97 Code Reviews 97 Summary 97 5. Package Management. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Introducing Package Management 99 The npm Command 102 Semantic Versioning 108 Updating and Removing Packages 109 Creating and Publishing Packages 113 npm Run Scripts 115 The npx Command 117 Summary 119 6. Streams. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 Introducing Streams 121 Using Streams 123 iv | Table of Contents
📄 Page
7
Fundamentals of Streams 126 The pipeline Method 127 Stream Events 129 Paused and Flowing Modes 130 Implementing Streams 131 Writable Streams 131 Readable Streams 132 Duplex/Transform Streams 135 Async Generators and Iterators 137 Streams Object Mode 139 Built-In Transform Streams 141 Summary 142 7. Child Processes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 Introducing Child Processes 143 The spawn Function 144 Shell Syntax and the exec Function 147 The execFile Function 150 The fork Function 150 Summary 153 8. Testing Node. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 Assertions and Runners 155 Types of Tests 160 Unit Tests 161 Functional Tests 161 Integration Tests 162 End-to-End Tests 162 Test Doubles 163 Organizing and Filtering Tests 169 Test-Driven Development 170 Continuous Integration 171 Summary 172 9. Scaling Node. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 Strategies of Scalability 175 The Cluster Module 176 Primary and Worker Processes 178 Broadcasting Messages 182 Increasing Availability 185 Zero-Downtime Restarts 187 Table of Contents | v
📄 Page
8
Handling State 191 Process Managers 192 Summary 193 10. Practical Node. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 Code Quality Tools 195 Prettier 197 ESLint 198 Other Tools 199 Module Bundlers 200 Task Runners 203 Frameworks 205 JavaScript Transpilers 209 TypeScript 210 Summary 216 Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 vi | Table of Contents
📄 Page
9
Preface I’ve been using Node.js since its early days, and it has never failed me. With every piece of code I’ve written for Node, my appreciation for it has only increased. With every new skill I’ve developed for Node, I’ve felt the productivity gain. Node.js is nothing short of revolutionary. It’s a great platform with impressive power. Once you get comfortable with its fundamentals and how it handles asynchrony, the rest is easy. You’ll get better with it quickly, and you’ll be able to build and scale back‐ end services faster than you’d imagine. Who Should Read This Book This book is my attempt at helping you learn Node.js efficiently. It naturally dips into a few JavaScript concepts, but in general, you need a good basic understanding of the JavaScript language to get the most value out of this book. If you’re not comfortable working with JavaScript objects, functions, operators, and iterators, reading an introductory book about JavaScript before this book would help. This is the book that I wished existed when I started learning Node.js. At that time, I was mainly focusing on the frontend. Naturally, this book is a good fit for a frontend developer wanting to expand their experience to the backend. Why I Wrote This Book When it comes to learning Node.js, many tutorials, books, and courses tend to focus on the libraries and tools available within the Node.js ecosystem, rather than the Node.js runtime environment itself. They prioritize teaching how to utilize popular Node.js libraries and frameworks, instead of starting from the native capabilities of Node.js. vii
📄 Page
10
This approach is understandable because Node.js is a low-level runtime environment. It does not offer comprehensive solutions but rather a collection of small essential modules that makes creating solutions easier and faster. For example, a full-fledged web server will have options like serving static files (like images, CSS files, etc.). With the Node.js built-in http module, you can build a web server that serves binary data, and with the Node.js built-in fs module, you can read the content of a file from the filesystem. You can combine both of these features to serve static assets by using your own JavaScript code. There’s no built-in Node.js way to serve static assets under a web server. Popular Node.js libraries that are not part of Node.js itself (such as Express.js, Next.js, and many others with .js in their names) aim to provide nearly complete solutions within specific domains. For example, Express.js specializes in creating and running a web server (and serving static assets, and many other neat features). Practically, most developers will not be using Node.js on its own, so it makes sense for educational materials to focus on the libraries offering comprehensive solutions, so learners can skip to the good parts. The common thinking here is that only developers whose job is to write these libraries need to understand the underlying base layer of Node.js. However, I would argue that a solid understanding of the built-in power of Node.js is essential before utilizing any of its external libraries and tools. Having a deep under‐ standing of Node.js allows developers to make informed decisions when choosing which libraries to use and how to use them effectively. This book is my attempt to prioritize first learning the native capabilities of Node.js and then using that knowl‐ edge to efficiently utilize the powerful libraries and tools in its ecosystem. Conventions Used in This Book The following typographical conventions are used in this book: Italic Indicates new terms, URLs, email addresses, filenames, and file extensions. Constant width Used for program listings, as well as within paragraphs to refer to program ele‐ ments such as variable or function names, databases, data types, environment variables, statements, and keywords. Constant width italic Shows text that should be replaced with user-supplied values or by values deter‐ mined by context. viii | Preface
📄 Page
11
This element signifies a tip or suggestion. This element signifies a general note. This element indicates a warning or caution. Using Code Examples Supplemental material (code examples, exercises, etc.) is available for download at https://oreil.ly/EfficientNodeCode. If you have a technical question or a problem using the code examples, please send email to support@oreilly.com. This book is here to help you get your job done. In general, if example code is offered with this book, you may use it in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. We appreciate, but generally do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Efficient Node.js by Samer Buna (O’Reilly). Copyright 2025 Samer Buna, 978-1-098-14519-4.” If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com. Preface | ix
📄 Page
12
O’Reilly Online Learning For more than 40 years, O’Reilly Media has provided technol‐ ogy and business training, knowledge, and insight to help companies succeed. Our unique network of experts and innovators share their knowledge and expertise through books, articles, and our online learning platform. O’Reilly’s online learning platform gives you on-demand access to live training courses, in-depth learning paths, interactive coding environments, and a vast collection of text and video from O’Reilly and 200+ other publishers. For more information, visit https://oreilly.com. How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-889-8969 (in the United States or Canada) 707-827-7019 (international or local) 707-829-0104 (fax) support@oreilly.com https://oreilly.com/about/contact.html We have a web page for this book, where we list errata, examples, and any additional information. You can access this page at https://oreil.ly/EfficientNodeJS. For news and information about our books and courses, visit https://oreilly.com. Find us on LinkedIn: https://linkedin.com/company/oreilly-media. Watch us on YouTube: https://youtube.com/oreillymedia. Acknowledgments I am deeply grateful to the many incredible people whose efforts helped shape and refine this book. A heartfelt thank you to the O’Reilly Media team for their patience, guidance, and unwavering support throughout the writing process. I am especially thankful to Jeff Bleiel, my development editor, whose thoughtful insights greatly improved the clarity and presentation of this work. To Amanda Quinn, my editor; Miah Sandvik, my copyeditor; and Gregory Hyman, my production editor: your con‐ tributions are greatly appreciated. x | Preface
📄 Page
13
I also want to thank the talented software developers who reviewed drafts of this book and provided invaluable feedback. Your suggestions were instrumental in improving the content. Special thanks to Hazem Twair for spotting issues I would have missed, and to Tamas Piros and Aniket Wattamwar for their meticulous technical proofread‐ ing. Your input made this book stronger and more polished. To my mentors and peers in the Node.js community: I am forever grateful for your wisdom, inspiration, and encouragement. To everyone who contributed: your dedication and expertise have elevated this book far beyond what I could have achieved on my own. Thank you for your help—it means the world to me! Preface | xi
📄 Page
14
(This page has no text content)
📄 Page
15
CHAPTER 1 Node Fundamentals Node is an open source, cross-platform runtime environment in which developers can create backend services using the JavaScript language. It’s built on top of V8, the JavaScript engine of the Chrome web browser, and it has dozens of built-in modules that are designed to be used asynchronously with an event-driven approach that’s commonly known as the non-blocking model. Node developers can use events and handler functions to efficiently perform multiple operations in parallel, without hav‐ ing to deal with the complexity of multiple processes and threads. There’s a lot to unpack here, and that’s what we will be doing in this first chapter. We’ll start with an introduction to Node, how it works, and why it’s popular. We’ll learn the basics of the Node CLI, how to use modules and packages, and how to perform syn‐ chronous and asynchronous operations. We’ll discuss the fundamentals of Node’s event-driven, non-blocking model and learn how callbacks, promises, and events can be used to handle the result of an asynchronous operation. Throughout the book, I use the term Node instead of Node.js for brevity. The official name of the runtime environment is Node.js, but referring to it as just Node is common. Introducing Node Ryan Dahl started the Node project in 2009 after he was inspired by the performance of the V8 JavaScript engine in the Google Chrome web browser. V8 uses an event- driven model, which makes it efficient at handling concurrent connections and requests. Ryan wanted to bring this same high-performance, event-driven architec‐ ture to server-side applications. The event-driven model is the first and most 1
📄 Page
16
important concept you need to understand about Node (and the V8 engine as well). I’ll explain it briefly in this chapter, and we’ll expand on it in Chapter 3. I decided to give Node a spin and learn more about it after watch‐ ing the presentation Ryan Dahl gave to introduce it. I think you’ll benefit by starting there as well. Search YouTube for “Ryan Dahl introduction to Node”. Node has changed significantly since then, so don’t focus on the examples but rather the concepts and explanations. In its core, Node enables developers to use the JavaScript language on any machine without needing a web browser. Node is usually defined as “JavaScript on backend servers.” Before Node, that was not a common or easy thing. JavaScript was mainly a frontend thing. However, this definition isn’t completely accurate. Node offers a lot more than the ability to execute JavaScript on servers. In fact, the actual execution of JavaScript is done by the V8 JavaScript engine, not Node. Node is just an interface to V8 when it comes to executing JavaScript code. V8 is Google’s open source JavaScript engine that can compile and execute JavaScript code. It’s used in Node as well as in Chrome and a few other browsers. It’s also used in Deno, the new JavaScript runtime that was created by Ryan Dahl in 2018. There are other JavaScript engines, like SpiderMonkey, which is used by Firefox, and JavaScriptCore, which is used by the Safari web browser and in Bun, an all-in-one JavaScript runtime, package manager, and bundler. Node is better defined as a server runtime environment that wraps V8 and provides modules to help developers build and run efficient software applications with JavaScript. The key word in this definition is efficient. Node adopts and expands on the same event-driven model that V8 has. Most of Node’s built-in modules are event-driven and can be used asynchronously without blocking the main thread of execution that your code runs in. A thread is basically a small process within a larger one. A process can create multiple threads of execution that are each associated with a CPU core. Threads can share memory and resources within the larger process. In multithreaded programming, slow operations are executed in separate threads. In Node, you get a single main thread for your code, and all the slow operations are exe‐ cuted outside of that main thread, asynchronously. 2 | Chapter 1: Node Fundamentals
📄 Page
17
You need to read the content of an external file? You can do that asynchronously without blocking the single main thread. You need to start a web server? Work with network sockets? Parse, compress, or encrypt data? Every low-level slow operation has an asynchronous API for you to use without blocking your other operations. You don’t need to deal with multiple threads to do things in parallel in Node. You don’t waste resources on manual threads being idle waiting on slow operations. You code in one thread and use asynchronous APIs, and Node takes care of executing the asynchronous operations efficiently outside of your main thread. Any code that needs to be executed after a slow operation can be managed with events and event handlers. An event is a signal that something has happened and a certain action needs to be performed. The action can be defined in an event handler function that gets associated with the event. Every time the event is signaled, its handler func‐ tion will be executed. That’s basically the gist of what event-driven means. We’ll expand on these important concepts once we learn the basics of running Node code and using its modules and packages. The JavaScript Language After considering programming languages like Python, Lua, and Haskell, Ryan Dahl picked the JavaScript language for Node because it was a good fit. It’s simple, flexible, and popular, but more importantly, JavaScript functions are first-class citizens that we can treat like any other objects (numbers or strings). We can store them in variables, pass them to other functions via arguments, and even return them from other func‐ tions, all while preserving their state. Node leveraged that to implement its handling of asynchronous operations. Despite JavaScript’s historical problems, I believe it’s a decent lan‐ guage today that can be made even better by using TypeScript (which we will discuss in Chapter 10). Besides simplifying the implementation of asynchronous operations, the fact that JavaScript is the programming language of browsers gave Node the advantage of hav‐ ing a single language across the full stack. There are some subtle but great benefits to that: • One language means less syntax to keep in your head, fewer APIs and tools to work with, and fewer mistakes overall. • One language means better integrations between your frontend code and your backend code. You can actually share code between these two sides. For example, you can build a frontend application with a JavaScript framework like React, then Introducing Node | 3
📄 Page
18
use Node to render the same components of that frontend application on the server and generate initial HTML views for the frontend application. This is known as server-side rendering (SSR), and it’s something that many Node front‐ end frameworks offer out of the box. • One language means teams can share responsibilities among different projects. Projects don’t need a dedicated team for the frontend and a different team for the backend. You would also eliminate some dependencies between teams. A full stack project can be assigned to a single team, The JavaScript People; they can develop APIs, they can develop web and network servers, they can develop inter‐ active websites, and they can even develop mobile and desktop applications. Hir‐ ing JavaScript developers who can contribute to both frontend and backend applications is attractive to employers. Executing Node Code If you have Node installed on your computer, you should have the commands node and npm available in a terminal. If you have these commands, make sure the Node version is a recent one (20.x or higher). You can verify that by opening a terminal and running the command node -v. If you don’t have the node command, you’ll need to download and install Node from the Node website. The installation process is straightforward and should only take a few minutes. For macOS users, Node can also be installed using the Homebrew package manager with the command: $ brew install node Throughout this book, I use the $ sign to indicate a command line to be executed in a terminal. The $ sign is not part of the com‐ mand. It’s a common prompt character in terminals. Another option to install Node is using Node Version Manager (NVM). NVM allows you to run multiple versions of Node and switch between them easily. You might need to run a certain project with an older version of Node, and use the latest Node version with another project. NVM works on Mac and Linux, and there’s a Windows option as well, called nvm-windows. 4 | Chapter 1: Node Fundamentals
📄 Page
19
Node on Windows All the examples I will be using in this book are designed for a macOS environment and should also work for a Linux-based OS. On Windows, you need to switch the commands I use with their Windows alternatives. I don’t recommend using Node on Windows natively unless it’s your only option. If you have a modern Windows machine, one option that might work a lot better for you is to install the Windows subsystem for Linux. This option will give you the best of both worlds. You’ll have your Windows OS running Linux without needing to reboot. You can even edit your code in a Windows editor and execute it in Linux! If you’re using NVM, install the latest version of Node with the command: $ nvm install node Major Node versions are released frequently. When a new version is released, it enters a Current release status for six months to give library authors time to make their libraries compatible with the new version. After six months, odd-numbered releases (19, 21, etc.) become unsupported, and even-numbered releases (18, 20, etc.) move to Active LTS (long-term support) status. LTS releases typi‐ cally guarantee that critical bugs will be fixed for a total of 30 months. Production applications should use only Active LTS releases. Once you have the node command ready, open a terminal and issue the command on its own without any arguments. This will start a Node REPL session. REPL stands for Read-Eval-Print-Loop. It’s a convenient way to quickly test simple JavaScript and Node code. You can type any JavaScript code in a REPL session. For example, try a Math.random() line, as shown in Figure 1-1. Figure 1-1. Node’s REPL mode Introducing Node | 5
📄 Page
20
Node will read the line, evaluate it, print the result, and loop over these three things for everything you type until you exit the session (which you can do with Ctrl + D). Note how the Print step happened automatically. We didn’t need to add any instruc‐ tions to print the result. Node will just print the result of each line you type. This is not the case when you execute code in a Node script. Let’s do that next. We’ll learn more about Node’s REPL mode in Chapter 2. Create a new directory for the exercises of this book, and then cd into it: $ mkdir efficient-node $ cd efficient-node Open up your code editor and create a file named test.js. Put the same Math.random() line into it: Math.random(); Now to execute that script, in the terminal type the following command: $ node test.js You’ll notice that the command will basically do nothing. That’s because we did not output anything in that script. To output something, you can use the global console object, which is similar to the one available in browsers. Here is an example: console.log( Math.random() ); Executing test.js now will output a random number, as shown in Figure 1-2. In this simple example, we’re using both JavaScript (Math object) and an object from the Node API (console). The console.log method writes the value of its arguments to the default standard output stream (stdout) of the running process. The console object is one of many top-level global scope objects that we can access in Node without needing to declare any depen‐ dencies. Similar to how the global window object in browsers can be accessed with the globalThis property, in Node, the globalThis property is the global object and the console object is part of it. All properties of globalThis can be accessed directly: for example, console.log instead of globalThis.console.log (which also works). 6 | Chapter 1: Node Fundamentals