Node.js has always been easy to grasp, but difficult to master. Design patterns and best practices exist for avoiding common pitfalls and building more stable applications, but these guiding principles have never been well documented. It has always been up to you, the individual developer, to uncover them on your own through painful trial and error.
Frustrated by this, I set out to discover the Node.js philosophy and what people really mean when they talk about "The Node Way." Is it just about small modules and asynchronous code, or could there be something more?
thenodeway.io is the result of that journey. It is a collection of articles and chapters on understanding the fundamentals of Node.js and leveraging that understanding to build great things.
But before we get to all that, lets go back to the original question: "What is The Node Way?"
Like atoms or DNA, Node.js modules are the basic building blocks of your application. They enforce basic modularity while remaining flexible enough to fit almost anywhere. What you choose to build with them is up to you, but the following guidelines will help get you pointed in the right direction.
Build small modules Build modules to serve a single purpose Building small, single-purpose modules is at the heart of the Node.js philosophy. Borrowing from Unix, Node.js encourages composing the complex and powerful out of smaller, simpler pieces. This idea trickles down from entire applications (using the best tool for the job vs. a full suite) to how the tools themselves are built.
The developers behind the popular Express web framework improved their focus and speed by pulling out most middleware features into separate modules. Instead of loading Express to get a full functioning framework, you load "express core" and then individually install each of the different middleware you'd like to use. Each middleware is now allowed to develop independently, while the consumer can swap at will (see "Use the best tool for the job" above). Everyone wins.
... But don't get carried away Modularity is a powerful concept, but like anything else its easy to get carried away. Just because every little helper function and object can be its own module, doesn't mean it necessarily should. Separating your code doesn't necessarily reduce complexity, but it does shard it.
Only build modules as needed. If there is a tangible benefit to splitting code off into a new module (ex: someone else might like to use it, this will make my code cleaner, etc) then by all means go ahead. Just remember: the end-goal is always simpler code, and not just a greater number of smaller files.
Complexity via composition Separating development across the ecosystem requires some careful design considerations. Successful use of inheritance, for example, requires a full understanding of a parent class implementation. This poses a problem for NPM modules, since implementation details can change across modules at any time. This reality has shifted design to favor Composition. Instead of absorbing some base class directly into your module, you *use* an object of that same class internally as a tool. By interacting with the interface and avoiding the implementation details, you can compose complex tools without worrying about the lower-level details of each piece.
Leverage I/O, Avoid Heavy Computation The V8 engine powering Node allows it to juggle every request at once, which leads to unparalleled efficiency. Instead of waiting around and doing nothing every time an API call or file read/write occurs, Node is able to keep busy handling other requests while it waits for a response. The implications here are huge, but the direct result is that I/O becomes incredibly cheap. But because one thread is responsible for everything, heavy tasks like encryption or data analysis can keep your thread busy and block other requests. To get the most out of your application, leverage I/O whenever possible and offload the heavy stuff to background workers and APIs.
Invest in Clean Async Because asynchronous code plays such a major role, its important to invest in its maintenance. Nested callbacks are easy to write, but they can quickly become a nightmare to read and maintain. Variables collide, scopes collude, and suddenly you have 100 different possible code paths inside a single block of code. Easy-to-read async can be hard to get right, but it is an essential part of a healthy Node.js codebase. Refactor callback hell and invest in readable code to save yourself the worst kind of headaches.
Return early Luckily, the Node.js community has adopted some best practices for cleaner callbacks. Returning early is a small stylistic choice that reduces nesting and code complexity. All you need to do is replace long, nested if-else chains with smaller if statements that return as soon as some condition is met. This keeps the code path simple and as linear as possible, reducing the need for complex logic paths.
Follow standard callback patterns The Error-first callback is another essential Node.js pattern, created so that separate modules could play nicely together. The unspoken reality of callbacks is that you're handing off control of your application with the callback. To make sure each module is able to safely pass that control back to your callback, you use the error-first callback pattern. Having this standard pattern also makes it easier to read and understand module behavior without needing to dig into each object's internals.
Get help with control flow Even with standard patterns, callback behavior can remain tricky. Luckily, plenty of great libraries exist to handle the nitty-gritty details of asynchronous behavior and let you focus on the important stuff. Well-tested libraries like `async` let you dictate the order and behavior of callbacks (in parallel? in serial? your choice!). Promises supplant callbacks completely to make your async code easier to read. No matter what your preference, a well tested control flow library will save you countless hours of otherwise frustrating debugging time.
It's impossible to talk about Node.js without mentioning NPM and the community its created. In the early days of Node, Isaac Schlueter introduced the Node Package Manager (NPM) to the community. The idea was to make it incredibly easy to publish and share different modules, similar to Maven and RubyGems. Since then the NPM ecosystem has exploded in both size and popularity. It now hosts over 200,000 modules with 100 million installs a day, numbers that are both constantly growing.
Leverage the Ecosystem (or: "There's a Module for That") Because modules are built small, the ecosystem is full of packages for everything under the sun. There are low-level utilities like bl, full testing and tooling suites like mocha or intern, and even an entire library solely dedicated to the number five. Before you spend time building anything, always check NPM first. What you're looking for may already exist, and sometimes with an entire community of developers around it.
Use the best tool for the job Gone are the days of downloading one monolithic library to do everything. NPM has made it just as easy to install new packages as needed, as you go. Smaller tools benefit from the singular focus and independent release cycles, and can be easily swapped out without a complete application re-write.
Write modules for others to read Build modules for others to use With so many different module, it's impossible to ignore the community. Instead of just writing code for yourself or your team, published modules will be shared across the world. Pay it forward by building with the clueless stranger in mind. Make your interface straight-forward and simple. Keep odd side effects to a minimum. and most importantly: document everything. Doing this will save everyone -- including yourself -- time and energy when working with dependencies.
Embrace the community Above all else, treat everyone with respect. Node's development and growth have been community-driven since the start. While some similar projects are driven by committees and company stewards, the level of engagement from the Node.js community makes these governance models obsolete (and more recently, unwanted). Doing any work in Node makes you a part of that community, which includes ownership over that vision. In return, each developer supports that community and each other in the code they write and the interactions they have every day. Be good, people.