Node is an open-source cross-platform runtime environment that can be used to execute JavaScript code using the same JavaScript engine in the Chrome web browser. Node has dozens of built-in modules which offer asynchronous APIs that make it easy for developers to build and maintain efficient backend services without the complexity of dealing with 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, what it is, how it works, and why it’s a very popular option for both backend and frontend development. We’ll learn how to set up a Node development environment, and execute a Node script. We’ll see examples of utilizing the built-in modules within Node, and demonstrate how to install and use a non-built-in library as well.
Note
|
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. Don’t confuse that with node (with a lower-case n), which is the command to execute a Node script. |
Ryan Dahl started Node in 2009 after he was inspired by the performance of the V8 JavaScript engine. 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 architecture to server-side applications. The event-driven model is the first and most important concept you need to understand about Node (and the V8 engine as well). I’ll explain it briefly in this section and we’ll expand on it in Chapter 3.
Tip
|
I decided to give Node a spin and learn more about it after watching 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 a completely accurate one. Node offers a lot more than executing 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’s written in C++. It parses and executes JavaScript code. V8 is used in Node, Chrome, and a few other browsers. It’s also used in Deno, the new JavaScript runtime that was created by Ryan Dahl in 2018.
Note
|
There are other JavaScript engines like SpiderMonkey which is used by Firefox, 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 the same event-driven model that V8 has. Most of Node’s built-in modules APIs are event-driven and can be used asynchronously without blocking the main thread of execution that your code runs in.
We’ll expand on this very important concept once we learn the basics of running Node code and using its modules and packages.
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
(and npm -v
).
If you don’t have these commands at all, you’ll need to download and install Node. You can download the latest version from the official Node website (https://nodejs.org/). 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
Note
|
Throughout this book, I use the |
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 an NVM-windows option as well.
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 operating system running Linux without needing to reboot. You can even edit your code in a Windows editor, and execute it on Linux!
If you’re using NVM, install the latest version of Node with the command:
$ nvm install node
Tip
|
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 status (Long Term Support). LTS release typically guarantees that critical bugs will be fixed for a total of 30 months. Production applications should only use 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, type Math.random()
and then, press Enter:
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 a CTRL+D
).
Note
|
We’ll learn more about Node’s REPL mode in Chapter 2. |
Note how the Print step happened automatically. We didn’t need to add any instructions 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.
Create a new directory for the exercises of this book, and then cd
into it:
$ mkdir efficient-node $ cd efficient-node
Open up your editor for this directory, then create a file named test.js
. Put the same Math.random()
line into it:
Math.random();
Now to execute that file, in the terminal, type the command:
$ node test.js
You’ll notice that the command will basically do nothing. That’s because we have not outputted anything from that file. To output something, you can use the global console
object, which is similar to the one available in browsers. Put the following code in test.js:
console.log(
Math.random()
);
Executing test.js
now will output a random number:
Note how in this simple example we’re using both JavaScript (Math
object), and an object from the Node API (console
). Let’s look at a more interesting example next.
Note
|
The |
You can create a simple web server in Node using its built-in node:http
module.
Create a server.js
file and write the following code in there:
// Basic Web Server Example
const { createServer } = require('node:http');
const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World');
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server is running...');
});
This is Node’s version of a Hello World example. You don’t need to install anything to run this script. This is all Node’s built-in power.
When you execute this script:
$ node server.js
Node will run a web server, and you’ll notice that the Node process does not exit in this example. It has work to do in the background. It waits for users to request data and serve them a plain "Hello World" text when they do.
While this web server example is a basic one, it has a few important concepts to understand. Let’s go over it in detail.
The require
function (on the first line) is part of Node’s original dependency management method. It allows a module (like server.js
) to load and use the API of another module (like node:http
). This web server module depends on the built-in node:http
module. There are many other libraries that you can use to create a web server, but this one is built-in. You don’t need to install anything to use it, but you do need to require it.
Tip
|
In a Node’s REPL session, built-in modules (like |
The second line creates a server object by invoking the createServer
function from the node:http
module. This function is one of many functions that are available under the node:http
module’s API. You can use it to create a web server object. It accepts an argument that is known as the Request Listener.
A listener function in Node is associated with a certain event and it gets executed when its event is triggered. In this example, Node will execute the request listener function every time there is an incoming connection request to the web server. That’s the event associated with this listener function.
The listener function receives two arguments:
-
The request object, named
req
in this example. You can use this object to read data about incoming requests. For example, what URL is being requested, or what is the IP address of the client that’s making a request. -
The response object, named
res
in this example. you can use this object to write things back to the requester. It’s exactly what this simple web server is doing. It’s setting the response status code to 200 to indicate a successful response, and theContent-Type
header totext/plain
. Then it’s writing back the Hello World text using theend
method on theres
object.
Note
|
The |
The createServer
function only creates the server object. It does not activate it. To activate this web server, you need to invoke the listen
method on the created server.
The listen
method accepts many arguments, like what OS port and host to use for this server. The last argument for it is a function that will be invoked once the server is successfully running on the specified port. This example prints a console message to indicate that the server is running successfully at that point.
While the server is running, if you go to a browser and ask for an http connection on localhost with the port that was used in the script (3000 in this case), you will see the Hello World text that this example had in its request listener function. To stop the web server, press CTRL+C
in the terminal where it’s running.
Both functions received by the createServer
and listen
methods are examples of functions associated with events related to an asynchronous operation. Node manages these event functions with an event queue and a forever ticking event loop. The event loop is a very important architectural concept to understand in Node. We’ll learn all about Node asynchrony, events, and the event loop in Chapter 3.
Tip
|
Note how I use a |
npm is Node’s Package Manager. It’s a simple CLI (Command Line Interface) that lets us install and manage external packages in a Node project. An npm package can be a single module or a collection of modules grouped together and exposed with an API. We’ll talk more about npm and its commands and packages in Chapter 5. Here, let’s just look at a simple example of how to install and use an npm package.
Let’s use the popular lodash
package which is a JavaScript utility library with many useful methods you can run on numbers, strings, arrays, objects, and more.
First, you need to download the package. You can do that using the npm install
command:
$ npm install lodash
This command will download the lodash
package from the npm registry, and then place it under a node_modules
folder (which it will create if it’s not there already). You can verify with an ls
command:
$ ls node_modules
You should have a folder named lodash
in there.
Now in your Node code, you can require
the lodash
module to use it. For example, lodash
has a random
method that can generate a random number between any two numbers you pass to it. Here’s an example of how to use it:
const _ = require('lodash');
console.log(
_.random(1, 99)
);
When you run this script, you’ll get a random number between 1 and 99.
Tip
|
The |
Since we called the require
method with a non built-in module lodash
, Node will look for it under the node_modules
folder. Thanks to npm, it’ll find it.
In a team Node project, when you make the project depend on an external package like this, you need to let other developers know of that dependency. You can do so in Node using a package.json file at the root of the project.
When you npm install
a module, the npm
command will also list the module and its current version in package.json, under a dependencies
section. Look at the package.json file that was auto created when you installed the lodash
package and see how the lodash
dependency was documented.
The package.json file can also contain information about the project, including the project’s name, version, description, and more. It can also be used to specify scripts that can be run from the command line to perform various tasks, like building or testing the project.
Here’s an example of a typical package.json file:
{
"name": "efficient-node",
"version": "1.0.0",
"description": "A guide to learning Node.js",
"license": "MIT",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
You can interactively create a package.json file for a new Node project using the npm init
command:
$ npm init
This command will ask a few questions and you can interactively supply your answers (or press Enter to keep the defaults, which often are good because the command tries to detect what it can about the project).
Try to npm install
new packages (for example, chalk
) and see how it gets listed as a dependency in your package.json file. Then npm uninstall
the package and see how it gets removed from package.json.
Your package.json file will eventually list many dependencies. When other developers pull your code, they can run the command npm install
without any arguments, and npm will read all the dependencies from package.json and install them under the node_modules
folder.
Some packages are only needed in a development environment, but not in a production environment. An example of that is eslint
and it’s one of my favorite code quality tools (we’ll learn all about it in Chapter 10). You can instruct the npm install
command to list a package as a development-only dependency by adding the --save-dev argument (or -D
for short):
$ npm install -D eslint
This will install the eslint
package in the node_modules
folder, and document it as a development dependency under a devDependencies
section in package.json. This is where you should place things like your testing framework, your formatting tools, or anything else that you use only while developing your project.
Tip
|
In addition to |
If you take a look at what’s under node_modules
after you install eslint
, you’ll notice that there are a lot more packages there.
The eslint
package depends on all these other packages. Be aware of these inderict dependencies in the future. By depending on one package, a project is indirectly depending on all that package’s dependencies, and the dependencies of all the sub dependencies, and so on. With every package you install, you add a tree of dependencies.
Some packages can also be installed (and configured) directly with the init
command. ESLint is an example of a package that needs a configuration file before you can use it. The following command will install ESLint and create a configuration file for it (after asking you a few questions about your project):
$ npm init @eslint/config@latest
Tip
|
In a production machine, development dependencies are usually ignored. The |
So far, we’ve been using Node`s require
method to declare a dependency in a module. This method is part of Node’s CommonJS module system, which is the default module system used in Node.
Node also supports ES Modules, the modern ECMAScript standard for working with modules in JavaScript. ES modules are supported in modern browsers. They use import
and export
statements to define dependencies and expose functionality between different modules.
One important difference between these two module systems in Node, is that CommonJS modules get loaded dynamically at runtime. ES Module dependencies are determined at compile time, allowing them to be statically analyzed and optimized. For example, with ES modules, we can easily find what code is not being used, and exclude it from the compiled version of the application.
Tip
|
While the two module types can be used together, you need to be careful about mixing them. CommonJS modules are fully synchronous while ES modules are asynchronous. |
To see ES modules in action, let’s expand on the basic web server example code and split it into two modules, one to create the web server, and one to run it.
The simplest way to use ES modules in Node is to save files with a .mjs
file extension instead of a .js
extension. This is because by default, Node assumes that all .js
extensions are using the CommonJS module system. This is configurable though. To make Node treat all .js
files as ES modules, you can add a type
key in package.json and give it the value of module
(the default value for it is commonjs
):
"type": "module"
Tip
|
npm has a |
With that, you can now use ES modules with the .js
extension.
Tip
|
Regardless of what default module type you use, Node will always assume a |
Let’s modify the basic web server example to use ES modules. In server.js file, write the following code:
import http from 'node:http';
export const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World');
});
Note the use of import
/export
statements. This is the syntax for ES modules. You use import
to declare a module dependency and export
to define what other modules can use when they depend on your module.
In this example, the server.js
module exports the server
object,
enabling other modules to import it and depend on it.
To use the server.js
module, just like we imported node:http
in there, we import
the server.js
module into other modules. In index.js
file, write the following code:
import { server } from './server.js';
server.listen(3000, () => {
console.log('Server is running...');
});
The ./
part in the import line signals to Node that this import is a relative one. Node expects to find the server.js
file in the same folder where index.js
is. You can also use a ../
to make Node look for the module up one level, or ../../
for two levels, and so on. Without ./
or ../
, Node assumes that the module you’re trying to import is either a built-in module, or a module that exists under the node_modules
folder.
With this code, the index.js
module depends on the server.js
module, and uses its exported server
object to run the server on port 3000. Execute the index.js
file with the node
command to start the web server and test it.
The export object
syntax is known as named exports and it’s great when you need to export multiple things in a module. You can use the export
keyword to prefix any object, including functions, classes, destructured variables, etc:
export function functionName() { ... }
export class ClassName { ... }
export const [name1, name2]
You can also use one export keyword, usually at the end of a module, with one object to export all named object together:
export {
server,
functionName,
ClassName,
name1,
name2,
// ...
};
In addition to the named export syntax, ES modules also have a default export syntax:
// To export the server object
// as the default export in server.js:
export default server;
// To import it, you need to
// specify a name:
import myServer from "./server.js";
Note how to import a default export, you have to name it, while with named exports, you don’t (although you can if you need to). Named exports are better for consistency, discoverability, and maintainability.
The export
/import
keywords support other syntax like renaming and exporting from other modules, but in general, my recommendation is to avoid using default exports, always use named exports and keep them simple and consistent. For example, I always specify my named exports as the last line of the module with a single export { … }
statement.
Note
|
ES modules are the modern standard for JavaScript modules and the preferred module system to use in Node today. CommonJS modules will continue to work for legacy and compatibility reasons. Many Node projects and libraries are built using CommonJS modules and it’s very likely that you will have to deal with them even if you’re starting a project from scratch. |
Real-life analogies can sometimes help us understand coding concepts.
One of my favorite analogies about coding in general is how it can be compared to writing cooking recipes. The recipe in this analogy is the program, and the cook is the computer.
In some recipes, you can use pre-made items like a cake mix or a special sauce. You would also need to use tools like a pan or a strainer. When compared to coding, you can think of these pre-made items and tools as the packages of code written by others which you can just download and use.
Extending on this analogy, you can think of an npm registry as the store where you get your pre-made items and tools for your coding recipes.
But what exactly is Node’s place in this analogy?
I like to think of it as the kitchen! it allows you to execute lines in your coding recipes by using built-in tools, like the stove and the sink.
Now imagine trying to cook without these built-in tools in your kitchen. That would make the task a lot harder, wouldn’t it?
While static import declarations are preferable for loading initial dependencies, there are many cases where you will need to import modules dynamically. For example:
-
When a module is slowing the loading of your code and it’s not needed until a later time.
-
When a module does not exist at load time.
-
When you need to dynamically construct the name of the module to import.
-
When you need to import a module conditionally.
For these cases, we can use the import()
function. It’s similar to the require()
function but it’s an asynchronous one.
Let’s think about an example where we need to read the content of a file before starting the basic web server. We can simulate the file reading delay using a simple setTimeout
function.
Since we don’t need the server.js module until a later point in time, we can import it with the import()
function when we are ready for it:
setTimeout(async () => {
const { server } = await import('./server.js');
server.listen(3000, () => {
console.log('Server is running...');
});
}, 5_000);
If you execute this code, the Node process will wait 5 seconds, it’ll then dynamically import the server.js module and use it to start the server. We’ll learn more timer objects in Chapter 3.
Tip
|
Dynamic import() expressions can be used in CommonJS modules to load ES modules. |
This example introduces the important Promise Object concept and how to consume it with async/await
. This is the modern JavaScript syntax to handle asynchronous operations and that’s the first important Node fundamental concept that we need to understand. Let’s do that next.
After considering programming languages like Python, Lua, and Haskell, Ryan Dahl picked the JavaScript language for Node because it was a great fit. It’s simple, flexible, and popular, but more importantly, JavaScript functions are first-class citizens that you can pass around while preserving their state. Node leveraged that to implement its simple callback pattern for asynchronous operations, which is also known as the non-blocking model.
Note
|
Despite JavaScript’s historical problems, I believe it’s a decent language today that can be made even better by using TypeScript (which we will discuss in Chapter 10). |
When you need to perform a slow operation in your code (like reading a file from the file system), you’ll need a way to handle the output of that slow operation.
const output = slowOperation();
handlerFunction(output)
// Other operations
The problem with this is that the slow operation will block the execution for all other operations that follow it, but since JavaScript functions can be passed as arguments, we can design the slowOperation
function to invoke its handler function once it’s done, using the following pattern:
slowOperation(
(output) => handlerFunction(output)
);
// Other operations
Now, we can make the slowOperation
run in a different process (or subprocess) and the other operations in the main process will not be blocked. This is known as the callback pattern and it’s the original implementation of asynchrony in Node. The handlerFunction
in this example is a callback function since it gets called at a later point in time once the slow operation is done.
A few years after the success of Node and its use of the callback pattern, promise objects were introduced in the JavaScript language and it became possible to natively wrap an asynchronous operation as a promise object to which handler functions can be attached:
const outputPromise = slowOperation();
outputPromise.then(
(output) => handlerFunction(output)
);
// Other operations
Both of these patterns can be used in Node today to build on top of the asynchronous APIs of its built-in modules. We’ll see many examples of callbacks and promises and learn what happens under the hood to make them work in Chapter 3.
Besides simplifying the implementation of asynchronous operations, the fact that JavaScript is the programming language of browsers gave Node the advantage of having 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, less APIs and tools to work with, and less mistakes over all.
-
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 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 web 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 interactive websites, and they can even develop mobile and desktop applications. Hiring JavaScript developers who can contribute to both frontend and backend applications is attractive to employers.
Armed with a simple non-blocking model, Ryan Dahl and many early contributors to Node got to work and implemented many low-level modules to offer asynchronous APIs for features like reading and writing files, sending and receiving data over network, compressing and encrypting data, and dozens of other features.
We saw simple examples of using the node:http
module. To see the list of all built-in modules you get with Node, you can use this line (in a REPL session):
require("repl").builtinModules
This is basically the list of things you need to learn to master Node.
Well, not all of it. Depending on the version of Node, this list might include deprecated (or soon to be deprecated) modules. You also might not need many of these modules depending on the scope of work and many other factors. For example, instead of using the native HTTPS capabilities of Node, you can simply put your Node HTTP server behind a reverse proxy like Nginx or a service like Cloudflare. Similarly, you would need to learn a module like wasi
only if you’re working with Web Assembly.
Note how a few of these modules are included twice, one with a /promises
suffix. These are the modules that support both the callback and the promise patterns.
Tip
|
Not all Node modules will be included in this list. Prefix-only modules and other experimental modules do not show up here. Examples include modules like |
It’s good to get familiar with this list now, and get a taste of what you can do with Node. Here are some of the important modules with a description of the main tasks you can do with them:
-
node:assert
: Verify invariants for testing -
node:buffer
: Represent and handle binary data -
node:child_process
: Run shell commands and fork processes -
node:cluster
: Scale a process by distributing its load across multiple workers -
node:console
: Output debugging information -
node:crypto
: Perform cryptographic functions -
node:dns
: Perform name resolutions like IP address lookup -
node:events
: Define custom events and handlers -
node:fs
: Interact with the file system -
node:http
: Create HTTP servers and clients -
node:net
: Create network servers and clients -
node:os
: Interact with the operation system -
node:path
: Handle paths for files and directories -
node:perf_hooks
: Measure and analyze applications performance -
node:readline
: Read data from readable streams one line at a time -
node:stream
: Handle large amounts of data efficiently -
node:test
: Create and run JavaScript tests -
node:timers
: Schedule code to be executed at a future time -
node:url
: Parse and resolve URL objects -
node:util
: Access useful utility functions -
node:zlib
: Compress and decompress data
We’ll see many examples of using these modules throughout the book. Some of these modules have entire chapters focusing on them.
Node ships with a powerful package manager named npm. We did not have a package manager in the JavaScript world before Node. npm was nothing short of revolutionary. It changed the way we work with JavaScript.
You can build many features in a Node application just by using code that’s freely available on npm. The npm registry has more than a million packages that you can just install and use in your Node servers. npm is a reliable package manager which comes with a simple CLI. The main npm
command offers simple ways to install and maintain third-party packages, share your own code, and reuse it too.
Tip
|
You can install packages for Node from other package registries as well. For example, you can install them directly from GitHub. |
Node also comes with the CommonJS module dependency manager and it supports ES modules as well. Node’s original module dependency management has been available since Node was released and it opened the door to a lot of flexibility in how we code JavaScript! It is widely used, even for JavaScript that gets executed in the browser, because npm has many tools to bridge the gap between modules written in Node and what browsers can work with today.
npm and Node’s module systems together make a big difference when you work with any JavaScript system, not just the JavaScript that you execute on backend servers or web browsers. For example, if you have a fancy fridge monitor that happens to run on JavaScript, you can use Node and npm for the tools to package, organize, and manage dependencies, and then bundle your code, and ship it to your fridge!
The packages that you can run on Node come in all shapes and forms, some are small and dedicated to specific programming tasks, some offer tools to assist in the life cycles of an application, others help developers every day to build and maintain big and complicated applications. Here are a few example of some of my favorite tools available from npm:
-
ESLint: A tool that you can include in any Node applications, and use it to find problems with your JavaScript code, and in some cases, automatically fix them. You can use ESLint to enforce best practices and consistent code style, but ESLint can help point out potential runtime bugs too. You don’t ship ESLint in your production environments, it’s just a tool that can help you increase the quality of your code as you write it.
-
Prettier: An opinionated code formatting tool. With Prettier, you don’t have to manually indent your code, break long code into multiple lines, remember to use a consistent style for the code (for example, always use single or double quotes, always use semicolons, never use semicolons, etc). Prettier automatically takes care of all that.
-
Webpack: A tool that assists with asset bundling. The Webpack Node package makes it very easy to bundle your multi-file frontend frameworks application into a single file for production and compile JavaScript extensions (like JSX for React) during that process. This is an example of a Node tool that you can use on its own. You do not need a Node web server to work with Webpack.
-
TypeScript: A tool that adds static typing and other features to the JavaScript language. It is useful because it can help developers catch errors before the code is run, making it easier to maintain and scale large codebases. TypeScript’s static typing can also improve developer productivity by providing better code auto-completion and documentation in development tools.
All of these tools (and many more) enrich the experience of creating and maintaining JavaScript applications, both on the frontend and the backend. Even if you choose not to host your frontend applications on Node, you can still use Node for its tools. For example, you can host your frontend application with another framework such as Ruby on Rails and use Node to build assets for the Rails Asset Pipeline.
We will learn more about these tools (and others) in Chapter 10.
Node’s approach to handling code in an asynchronous and non-blocking manner is a unique model of thinking and reasoning about code. If you’ve never done it before, it will feel weird at first. You need time to get your head wrapped around this model and get used to it.
Node has a relatively small standard library. This means that developers need to rely on third-party modules to perform most big tasks. There is a large amount of third-party modules available for Node. You need to do some research to pick the most appropriate and efficient ones. Many of these modules are small, which means you’ll need to use multiple modules in a single project. It’s not uncommon for a Node project to use hundreds of third-party modules. While this can enhance maintainability and scalability, it also requires more management and oversight. As modules are regularly updated or abandoned, it becomes necessary to closely monitor and update all modules used within a project, replacing deprecated options and ensuring that your code is not vulnerable to any of the security threats these modules might introduce.
Tip
|
Smaller code is actually why Node is named Node! In Node, we build simple small single-process building blocks (nodes) that can be organized with good networking protocols, to have them communicate with each other and scale up to build large, distributed programs. |
Additionally, Node is optimized for I/O and high-level programming tasks but it may not be the best choice for CPU-bound tasks, such as image and video processing, which require a lot of computational power. Because Node is single-threaded, meaning that it can only use one core of a CPU at a time, performing tasks that require a lot of CPU processing power might lead to performance bottlenecks. JavaScript itself is not the best language for high-performance computation, as it is less performant than languages like C++ or Rust.
Node also has a high rate of release and version updates, this can create the need for constant maintenance and updates of the codebase, which can be a disadvantage for long-term projects.
Finally, the language you use in Node, JavaScript, has one valid argument against it. It is a dynamically typed language, which means objects don’t have explicitly declared types at compile time and they can change during runtime. This is fine for small projects but for bigger ones, the lack of strong typing can lead to errors that are difficult to detect and debug and it generally makes the code harder to reason with and to maintain.
Node is a powerful framework for building network applications. wraps the V8 virtual machine to enable developers to execute JavaScript code in a simple way, and it is built on top of a simple event-driven, non-blocking model that makes it easy for developers to create efficient and scalable applications.
The built-in modules in Node provide a low-level framework on which developers can base their applications so that they don’t start from scratch. Node’s module system allows developers to organize their code into reusable modules. These modules can be imported and used in other parts of the application.
Node has a large and active community that has created many popular packages that can be easily integrated into Node projects. These packages can be found and downloaded from the npm registry.
In the next chapter, we’ll explore Node’s CLI and REPL mode and learn how Node loads and executes modules.