Editor’s note: The English version of this article was updated on 10/03/2022 by our editorial team. It has been modified to include recent sources and to align with our current editorial standards.
With all of its advantages, Node.js plays a critical role in the technology stack of many high-profile companies who depend on its unique benefits. This Node.js tutorial addresses how to realize these advantages and why you might—or might not—use Node.js.
What Is Node.js?
Node.js provides developers a comprehensive tool for working in the non-blocking, event-driven I/O paradigm. Ryan Dahl, the creator of Node.js was “inspired by applications like Gmail” and—in creating Node.js—aimed to create real-time websites with push capability.
After over 20 years of stateless-web based on the stateless request-response paradigm, we finally have web applications with real-time, two-way connections.
Why Use Node.js?
Node.js shines in real-time web applications employing push technology over WebSocket. After over 20 years of stateless-web based on the stateless request-response paradigm, we finally have web applications with real-time, two-way connections, where both the client and server can initiate communication, allowing them to exchange data more freely. This is in stark contrast to the typical web response paradigm, where the client always initiates communication.
One might argue that we’ve had this technology for years in the form of Flash and Java Applets. In reality, however, those were just sandboxed environments that used the web as a transport protocol to be delivered to the client. Plus, Flash and Java Applets were run in isolation and often operated over nonstandard ports, which may have required extra permissions.
How Does Node.js Work?
Node really shines in building fast, scalable network applications. This is due to its capability of handling a huge number of simultaneous connections with high throughput.
Node.js uses non-blocking, event-driven I/O to remain lightweight and efficient in the face of data-intensive real-time applications that run across distributed devices.
Node.js is a platform that fills a particular need, and understanding this is absolutely essential. For example, you wouldn’t use Node.js to perform CPU-intensive operations. Nearly all of Node’s advantages are annulled if it’s used for heavy computation.
Node.js is a platform that fills a particular need. It is not a silver bullet, or a platform that will dominate the web development world.
How Node.js works under the hood is interesting. Compared to traditional web-serving techniques where each connection (request) spawns a new thread (taking up system RAM and eventually maxing out at the amount of RAM available), Node.js operates on a single thread, using nonblocking I/O calls. This allows Node to support tens of thousands of concurrent connections held in the event loop.
Per Michael Abernethy’s 2011 article “Just what is Node.js?”, take a thread with an accompanying 2 MB of memory, running on a system with 8 GB of RAM, and providing a theoretical maximum of 4,000 concurrent connections. Add to this the cost of context-switching between threads, and you get a common scenario in traditional web-serving techniques. Node.js avoids all this, achieving high scalability levels.
There is, of course, the question of sharing a single thread among all client requests, a potential pitfall of writing Node.js applications.
First, heavy computation could choke up Node’s single thread and cause problems for all clients, as incoming requests are blocked until said computation is completed.
Second, developers need to be vigilant and prevent exceptions from bubbling up to the core (top) Node.js event loop, as this would cause the Node.js instance to terminate, effectively crashing the program.
To prevent the flow of exceptions, we pass errors back to the caller as callback parameters (instead of “throwing,” as we do in some other environments). If an unhandled exception manages to surface, we can use the Forever module or external tools such as upstart and monit and just upstart to monitor the Node.js process and perform the necessary recovery of a crashed instance. Note that these tools do not address the recovery of the current state of the user session.
npm: The Node Package Manager
Built-in support for package management using npm is included in every Node.js installation. The idea behind npm modules is similar to that of Ruby Gems: It is a set of publicly available, reusable components, easily installed via an online repository, with version and dependency management.
npm Inc. shares a list of packaged modules that are also accessible via its npm CLI tool. The module ecosystem is open to all to publish their own module, which would be added to the npm repository.
Some useful npm modules include:
express, Express.js, or simply Express
A Sinatra-inspired web development framework for Node.js, and the de facto standard for the majority of Node.js applications.
A modular and simple-to-use configuration-centric framework for building web and services applications.
An extensible HTTP server framework for Node.js, providing a collection of high performance plugins known as middleware; serves as a base foundation for Express.
A server-side component of two common WebSocket components.
pug (formerly Jade)
A templating engine inspired by HAML, a default in Express.js.
MongoDB wrappers to provide the API for MongoDB object databases in Node.js.
The Redis client library.
A utility for ensuring that a given node script runs continuously; keeps your Node.js process up in production in the face of any unexpected failures.
A full-featured Promises/A+ implementation with exceptionally good performance.
Where to Use Node.js
Chat is a typical real-time, multi-user application—from IRC (back in the day)—to modern implementations in Node.js with WebSocket.
The chat application is lightweight, high traffic, and data intensive (but low processing/computation). It runs across distributed devices and is the sweet-spot example for Node.js.
Simple, yet covering most of the paradigms you’ll ever use in a typical Node.js application, chat is a great use case for learning.
Let’s depict how chat works: Say we have a single chat room where users can exchange messages in one-to-many (actually all) fashion. Let’s also say there are three users connected to our message board.
On the server side, a simple Express.js application implements:
GET /request handler which serves the webpage containing a message board and a ‘Send’ button to initialize new message input, and
- A WebSocket server that listens for new messages emitted by WebSocket clients.
On the client side, we have an HTML page with a couple of handlers set up:
- A handler for the ‘Send’ button click event, which picks up the input message and sends it down the WebSocket.
- A handler that listens for new incoming messages on the WebSocket client (i.e., user-originated messages that the server wants the client to display).
When a client posts a message, here’s what happens:
- The server-side component of the WebSocket connection receives the message and forwards it to all other connected clients, using the broadcast method.
- All clients receive the new message as a push message, via a WebSocket client-side component running within the web page. The clients then pick up the message content and update the web page in place, by appending the new message to the board.
For a more powerful solution, you might use a simple cache based on the Redis store. Or in an even more advanced solution, use a message queue to handle the routing of messages to clients, and a more robust delivery mechanism. The queue may cover for temporary connection losses or store messages for registered clients while they’re offline.
Regardless of the solution you choose, Node.js operates under the same basic principles: reacting to events, handling many concurrent connections, and maintaining fluidity in the user experience.
API on Top of an Object DB
Node.js is a natural fit for exposing the data from object DBs (e.g., MongoDB). JSON-stored data allows Node.js to function without impedance mismatch and data conversion.
For instance, if you’re using Rails, you would convert from JSON to binary models, then expose them back as JSON over the HTTP when the data is consumed by Backbone.js, Angular.js, etc.—or even plain jQuery AJAX calls. With Node.js, you can expose JSON objects with a REST API for the client to consume.
If you are using MongoDB, you needn’t worry about converting between JSON and whatever else when reading or writing from the database. Thus, you can avoid the need for multiple conversions by using a uniform data serialization format across the client, server, and database.
Node allows you the flexibility to push database write-offs to the side. But there are even more reasons to use Node.js.
If you’re receiving a large amount of concurrent data, your database can become a bottleneck. Node.js can easily handle the concurrent connections. Because—in this case—database access is a blocking operation, we run into trouble. The solution is to acknowledge the client’s behavior before the data is truly written to the database.
This approach enables the system to maintain responsiveness under a heavy load. It is particularly useful when the client doesn’t require firm confirmation of a successful data write, when logging or writing user-tracking data, processed in batches, to be used at a later time, or for operations that don’t need to be reflected instantly, like updating a Facebook “likes” count.
Data is queued through some kind of caching or message queuing infrastructure like RabbitMQ or ZeroMQ. It is then digested by a separate database batch-write process or computation-intensive processing backend service, written in a better-performing platform for such a task.
In short: With Node you can push the database writes off to the side to deal with later.
Why not use Node.js in data streaming? In more traditional web platforms, HTTP requests and responses are treated like isolated events—although they’re actually streams. We can use this observation to build some cool Node.js features.
For example, we can process files while they’re still being uploaded. As data comes in through a stream, we can process it in parallel during that upload process. This is true for real-time audio or video encoding and proxying between various data sources.
Node.js is easily employed as a server-side proxy, where it can handle a large amount of simultaneous connections in a nonblocking manner. It’s useful for proxying different services with varying response times, or collecting data from multiple source points.
As an example, let’s consider a server-side application communicating with third-party resources, pulling in data from different sources, or storing assets (like images and videos) to third-party cloud services.
Using Node in place of a dedicated proxy server might be helpful if your proxying infrastructure is non-existent, or if you need a solution for local development. By this, I mean that you could build a client-side app with a Node.js development server for assets and proxying/stubbing API requests. In production, you’d handle such interactions with a dedicated proxy service (like nginx or HAProxy).
Brokerage/Stock Trader’s Dashboard
At the application level, brokers’ trading software is another example where desktop software dominates, but could be easily replaced with a real-time web solution. Brokers’ trading software tracks stock prices, performs calculations and technical analysis, and renders graphs and charts.
Why not use Node.js to write a real-time web-based solution for brokers? Then, brokers could easily switch workstations or work locations. We may soon meet our brokers on the beach in Florida or Ibiza or Bali.
Application Monitoring Dashboard
Imagine how you could grow your business if you could see what your visitors were doing in real time. With Node’s real-time, two-way sockets, you can gain this ability.
Node with WebSocket fits perfectly for tracking website visitors and visualizing their interactions in real time.
Reasons to use Node.js for a monitoring dashboard include gathering real-time stats from users, or introducing targeted interactions with your visitors by opening a communication channel at a specific point in your funnel. CANDDi productizes this idea.
System Monitoring Dashboard
Now, let’s visit the infrastructure side of things. Imagine, for example, a SaaS provider that wants to offer users a service monitoring page, like GitHub’s status page. With Node.js’s event-loop, we can create a powerful web-based dashboard that checks services’ statuses in an asynchronous manner, pushing data to clients using WebSocket.
Both internal (intracompany) and public services’ statuses can be reported live and in real time using this technology. Push a little further and try to imagine a network operations center (NOC) that monitors the applications of a telecommunications operator, cloud/network/hosting provider, or some financial institution. The applications would run on the open web stack backed by Node.js and WebSocket.
Don’t try to build hard real-time systems in Node (i.e., systems requiring consistent response times). Erlang is probably a better choice</a> for that class of application.
Where to Use Node.js, but Cautiously
Server-side Web Applications
With Node.js with Express.js, you can create classic web applications on the server side. While possible, this request-response paradigm in which Node.js would carry rendered HTML is not an ideal use case. There are arguments to be made for and against this approach. Here are some facts to consider:
- Crawlers receive a fully rendered HTML response, which is far more SEO friendly than, say, a Single Page Application or a WebSocket app that runs on top of Node.js.
- Any CPU-intensive computation will block Node.js responsiveness, so a threaded platform is a better approach. Alternatively, you could try scaling out the computation.
- Using Node.js with a relational database can be painful. If you’re trying to perform relational operations, consider going with an environment such as Rails, Django, or ASP.Net MVC.
An alternative to CPU-intensive computations is to create a highly scalable MQ-backed environment with back-end processing to keep Node as a front-facing “clerk” to handle client requests asynchronously.
Where Not to Use Node.js
There are situations where Node may not be the best tool for the job.
Server-side Web Application With a Relational Database Application
Ruby on Rails was once the clear choice as a tool to access relational databases like PostgreSQL, MySQL, and Microsoft SQL Server. This was because relational DB tools for Node.js were still in their early stages while, in contrast, Rails automatically provided data access setup right out of the box, together with DB schema migrations support tools, and other Gems (pun intended). Rails and its peer frameworks have mature and proven Active Record or Data Mapper data access layer implementations.
It’s possible and not uncommon to use Node solely on the front end, while keeping your Rails back end with its easy access to a relational DB.
But things have changed. Sequelize, TypeORM, and Bookshelf have come a long way toward becoming mature ORM solutions. It might also be worth checking out Join Monster if you’re looking to generate SQL from GraphQL queries.
Heavy Server-side Computation and/or Processing
Node.js is not the best platform to handle heavy computation. No, you definitely don’t want to build a Fibonacci computation server in Node.js.
In general, any CPU-intensive operation annuls all the throughput benefits Node offers with its event-driven, nonblocking I/O model. This is because incoming requests are blocked while the thread is occupied with your number-crunching—assuming you’re trying to run computations in the same Node instance used to respond to requests.
Since Node.js is single-threaded and uses only a single CPU core, it would require considerable effort to develop a cluster module in order to deliver concurrency on a multicore server. Alternatively, you can run several Node.js server instances pretty easily behind a reverse proxy via nginx.
With clustering, you should still offload all heavy computation to background processes. Ensure that you use an appropriate environment for the background processes, and that they communicate via a message queue server like RabbitMQ.
While you may run background processes on the main server, this approach may not scale well once the load increases. You may distribute background processing services to separate worker servers without the need to configure the loads of front-facing web servers.
With Node.js—as opposed to most other platforms—you enjoy that high reqs/sec throughput we talked about, as each request is a small task that Node handles quickly and efficiently.
Why Choose Node.js?
We discussed Node.js from theory to practice, beginning with its purpose, and ending with its sweet spots and pitfalls.
Problems with Node almost always originate from the fact that blocking operations are the root of all evil—and 99% of Node misuses are a direct consequence.
In Node, blocking operations are the root of all evil—99% of Node misuses are a direct consequence.
If your use case does not contain CPU-intensive operations, nor accesses blocking resources, you can exploit the benefits of Node.js and enjoy fast and scalable network applications. Welcome to the real-time web.
Understanding the basics
Because Node.js is single-threaded, we use it primarily for non-blocking, event-driven servers. We can also use Node.js for traditional websites and back-end API services, as it was designed with real-time, push-based architectures in mind.
Web frameworks like Angular and React are libraries that help organize and generate the front-end code that runs in a web browser. Web frameworks reuse code for common operations, thereby reducing development time. Some web frameworks are full stack.
No, Node.js is an environment. Back-end frameworks run within Node.js. Popular, compatible frameworks include Express.js (also referred to as Express) for HTTP servers and Socket.IO for WebSocket servers.
Node.js isn't bad. Its technology is widely used for many types of servers. However, because it’s single-threaded, Node.js is not ideal for web servers that double as computational servers---such heavy computation would block the server's responsiveness.