Node.js Tutorial: An Introduction To This JavaScript Runtime Environment

Node.js tutorial

Node.js is a must if you want to get into the world of back-end development. This Node.js tutorial should help you get the hang of this runtime environment and create your first server-side JavaScript application.

JavaScript And Server-Side Scripting

Let’s recall a bit of JavaScript history here. This programming language was originally created by Brendan Eich to provide web developers with scripting capabilities. Thanks to it, they could make web pages more dynamic instead of just reading HTML documents and rendering text and images.

For a very long time, JavaScript only existed in web browsers. These applications come with an engine whose job is to execute JavaScript and render web pages on the client side. Web browsers, therefore, were the standard runtime environment for JavaScript.

Then came the era of server-side environments. They aimed to enable developers to create complex web applications and execute JavaScript code on the server side. But it was not until the introduction of Node.js in 2019 that this functionality really took off.

Since then, JavaScript is no longer limited to making web pages interactive. While web applications still make up the vast majority of its use, you can use JavaScript to create command-line, desktop, and even mobile applications on more platforms.

What Is Node.js?

Node.js logo

In short, Node.js is a cross-platform runtime environment for JavaScript. Based on the V8 engine, it can run JavaScript code outside your web browser.

It is neither a programming language nor a web framework. Node.js only runs scripts written in a language (JavaScript), including those generated by frameworks such as Express and Angular.

Getting Started With A Simple Node.js Tutorial

Node.js is available on all major platforms. Most JavaScript features and Web APIs are supported by Node.js. For example, you can use the console.log() method in a simple program:

console.log("Welcome to LearnshareIT")

Here is how you can execute the script named index.js above with Node.js from a terminal interface:

node index.js

Output:

Welcome to LearnshareIT

How Node.js Works

To achieve high throughput and low latency while serving requests, this runtime environment uses the non-blocking approach. In other words, Node.js aims to spend the least time waiting for responses, such as from I/O operations.

Traditional web servers like Apache create a new thread for each connection or incoming request. Some even fork a whole new process to handle and produce a response to such requests. This approach makes perfect sense when your web page only has to serve a small number of concurrent connections. But it creates a lot of overhead when you have to scale your web application.

Spawning new threads is better than forking processes. But when it comes to CPU and memory usage, it is still inefficient. When your operating system hosts too many threads of execution, a lot of resources go to thread management, such as context switching and thread scheduling.

The more connections you serve, the more threads the web server has to create and the more latency it has to suffer from. This creates a severe limitation on throughput and scalability. In fact, this was one of the main criticisms Ryan Dahl, creator of Node.js, had for the web server Apache.

To address this problem, Ryan Dahl took a different approach. Node.js runs on a single-threaded event loop to handle concurrent connections.

Whenever Node.js receives a new connection, it fires a callback function. This function uses non-sequential I/O to handle requests.

When necessary (such as when there is a need for load-balancing between CPU cores or CPU-intensive operations), it can spawn new threads from a pool too. By using callback functions instead of scaling with threads, Node.js takes up a small amount of memory to handle the same number of connections.

Why You Should Learn Node.js

Same Language As Front-End Development

With Node.js, you can leverage your JavaScript knowledge and experience to develop scalable back-end applications.

You don’t need to learn another language for this task, especially when you have a front-end background. The development process of full-stack applications can become more consistent and seamless.

Scalability

Node.js comes with improvements in performance compared to other runtime environments too.

Keep in mind that JavaScript is a single-threaded language – a design choice by its creator. There is only one thread for code instructions responsible for everything on a web page.

Your application may need to wait for a task to complete before it can continue on another task. This can take a while and severely hinders complex operations on the client side.

Node.js can’t change this nature of JavaScript but can make it more dynamic to boost improvements and expand its capabilities. By using the event loop, it prevents operations like I/O and network requests from blocking the main thread.

The event-driven approach makes those operations more efficient and lightweight. This is a big deal when a typical application nowadays can require a lot of them. Node.js also allows you to make scalable software that can handle a huge number of connections at the same time.

The V8 Engine

Node.js uses the V8 JavaScript engine under the hood. Developed by Google, it powers Chrome and Chromium-based browsers, as well as Electron-based applications.

This engine is feature-rich and well-supported. Many optimization efforts have been put into it, making V8 one of the fastest and most efficient engines for running JavaScript code out there.

Useful For Other Applications

Using JavaScript to write desktop applications used to be impractical. Frameworks for this purpose were non-existent, and the whole idea was even considered insane by many. But thanks to solutions like Node.js, you can now quickly write cross-platform applications in JavaScript with ease.

Electron, the most popular framework for creating desktop applications with web technologies, uses Node.js as its JavaScript runtime environment. This built-in Node.js integration allows developers of Visual Studio Code, Atom, and other applications to build and ship them to more platforms faster.

You don’t even need to use vanilla JavaScript to write Node.js applications. TypeScript or any language that you can compile to JavaScript code is compatible with Node.js too.

Rich Ecosystem And Active Community

Another huge advantage of this runtime environment is its thriving community and ecosystem, which revolve around Node.js modules. Developers can integrate other libraries into their applications in a seamless manner.

Applications You Can Write Develop For Node.js

Chat

Real-time chat services need to transmit types of messages (text, audio, and video) from the sender to the receiver. They can take various forms and degrees of complexity, all of which can be developed with Node.js.

For starters, the Event API can help you create objects that periodically raise events certain handlers are listening to. The event-based architecture of Node.js can also facilitate WebSockets, enabling fast communication between the server and the client.

Single-Page Applications

Single-page applications (SPAs) are on the rise because they can provide a similar user experience to that of desktop applications, even though they still operate on a web browser. They can create background requests to receive updates from the servers without having to reload the full page.

Node.js is a great solution for SPAs thanks to its ability to handle I/O workloads and asynchronous calls. Using libraries and frameworks like React or Express, your applications can smoothly transition between views and seamlessly update data. Data-driven SPAs should work well with Node.js, which provides them with data while the client does the HTML and CSS rendering.

Streaming

Streaming applications are among the most resource-intensive software you may have to develop. Your servers will need to respond to data requests on demand from many clients at once without overwhelming themselves.

Node.js comes with a built-in API called Stream. It enables the development of efficient data streaming applications in JavaScript on the server side.

Collaboration Applications

Common real-time collaboration tools include online document editors, video conferencing, and project management. These applications produce many requests at the same time. The heavy demand occurs when many users edit and comment on the same document.

Node.js is built to handle such scenarios in mind. In fact, many collaboration tools like Trello use this runtime environment in their back-end stack.

Where You Shouldn’t Use Node.js

As useful as Node.js is, it does have some limitations you need to be aware of. For instance, heavy computation isn’t its strongest suit. CPU-intensive operations don’t benefit much from the advantages of Node.js and its architecture.

By nature, a Node.js process runs just on a single thread. There would be no easy way for you to run your computation parallel on multiple threads and cores. Those long-running tasks might even block the whole process and freeze your server.

Node.js Modules

Also known as packages or libraries, Node.js modules allow you to reuse JavaScript code between applications. It works in a similar way as modules and packages in languages like Python.

Typically, it works like this. Someone writes a module containing specific classes and functions and pushes it to the npm Registry – the most popular JavaScript package repository. There are also built-in modules and APIs that come with Node.js to provide common functionalities.

If you want to use any functionality provided by a specific module, you can add it as a dependency of your application. This ranges from a few lines of code to a huge library like React. There are millions of packages you can install from the npm registry alone, making it the world’s biggest software repository.

The default Node.js installation comes with plenty of basic modules designed for frequently used functionality.

Here are some popular built-in modules you should pay attention to:

  • HTTP: offer support for data transfer over the HTTP protocol.
  • File system: allow you to interact with local file systems on your system.
  • URL: provides simple solutions for parsing and resolving URLs.
  • Events: Node.js uses the event-based paradigm. This module allows you to create, listen to, and handle custom events.

The Package Manager npm

npm is one of the driving forces behind the success of Node.js. As the default package manager, npm assumes the responsibility for integrating dependencies into Node.js applications.

Without package managers like npm, using modules in your code would be a huge challenge. You may need to clone every module’s source repository and build them from scratch. When there are conflicts, you have to resolve them or end up with broken modules. You will need to check for new versions yourself and install them again.

npm comes with a command-line tool of the same name. Installing a module into your development environment is as simple as follows:

npm install axios

This command will find the latest version of the axios library and install it into your project. You can then import in JavaScript like this:

const axios = require('axios').default;

Node.js uses the package.json file in the root directory as the official metadata for each project. This is where you can define various attributes of your project, including its dependencies.

npm can run through this file and automatically necessary modules for the project’s operation. There is no need to install each module manually from the command line. Since version 5.0, npm even automatically uses the –save option to add the module to the package.json file after installing it.

In addition to local dependency management for individual projects, npm can also manage modules globally installed in your system.

There are also alternatives to npm, providing different client-side experiences. Yarn is a prime example. You can still access packages in the npm registry while enjoying benefits such as parallel package installation

Node.js HTTP Module

You can use the built-in HTTP module to leverage Node.js’ networking capabilities to process or create HTTP requests. It can help you build either a server or a client in JavaScript. The interfaces it provides are designed to be able to handle large messages – probably the most difficult features to use in the HTTP protocol.

This is how you can use this core module to create a simple web server on your local computer:

const http = require("http");
const host = 'localhost';
const port = 8080;
const requestListener = function (req, res) {
    res.writeHead(200);
    res.end("Welcome to LearnShareIT!");
};

const server = http.createServer(requestListener);
server.listen(port, host, () => {
    console.log(`Your server is running on http://${host}:${port}`);
});

Save this script in a file (http.js, for example) and then execute with Node.js;

node http.js

The terminal app will display this message:

Your server is running on http://localhost:8080

Open http://localhost:8080/ in your browser, and you will see a bare web page like this:

If you have curl installed, you can also send a GET request to the server and get the same response:

curl http://localhost:8080/

Output:

Welcome to LearnShareIT!

In this example, we use the request listener function requestListener, which in Node.js has two arguments (usually named req and res. It receives the Request object req and interacts with the Response object res.

The createServer() function of the http module creates a server object. By passing requestListener to it, Node.js will send all HTTP requests to that listener function.

Node.js File System Module

The fs module allows you to work with local file systems based on POSIX functions. You can read, write, and delete files, among other operations.

This example demonstrates how the fs module can help you read the content of the file message.txt:

const fs = require('fs').promises;
async function readFile(filePath) {
    const content = await fs.readFile(filePath);
    console.log(content.toString());
}

readFile('message.txt');

Here we use promise-based operations, which fulfill a promise and they complete an asynchronous operation.

The readFile() function accepts a single argument – the path to the file you want to read its content. We use the async and await keywords to work with promises in JavaScript.

Node.js URL Module

This module can pass URLs and separate them into smaller pieces of information.

The url module is quite simple to use. Its URL function returns a URL object containing those components, such as the host, serialized URL, origin, port, or pathname.

Example:

const url = require('url');
const myUrl = process.argv[2];

if (myUrl) {
  const { href, host, pathname, protocol } = new URL(myUrl);
  console.log('HREF:', href);
  console.log('Protocol:', protocol);
  console.log('Host:', host);
  console.log('Pathname:', pathname);
}

To use this script, run it with a URL you want to parse:

node url.js https://learnshareit.com/about-us

Output:

https://learnshareit.com/about-us
HREF: https://learnshareit.com/about-us
Protocol: https:
Host: learnshareit.com
Pathname: /about-us

The URL you provide is stored in process.argv[2] (meaning the second argument of the node command). We use the destructuring assignment to unpack values from the returned URL object.

Node.js Events

Node.js is designed around events, which many of its core APIs fire or react to. The event-based design pattern of Node.js forces developers to produce and consume events, mostly on the client side. When the user presses a key or clicks a button, it can trigger an event your application will have to respond to.

This approach mainly involves two component types: event emitters and event handlers. Node.js features an event loop that waits for events and invokes handlers associated with them.

Example:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter ();
eventEmitter.on('welcome', () => {
  console.log('Welcome to LearnShareIT!');
});

eventEmitter.emit('welcome');

Output:

Welcome to LearnShareIT!

The EventEmitter class (part of the built-in module events) is responsible for emitting and handling events inNode.js.

The on() method defines the event’s name and a listener function associated with it. If the event has already been registered with other listeners, the new function will be added to the end of the listeners array.

The emit() method calls all listeners registered for an event. If there is no listener, the method returns false. In the example, we define the event ‘welcome’ and define a function as a listener for it. When we trigger the event, the function registered to it will be executed.

Node.js Upload Files

Unlike the above operations, you will need third-party modules to create a simple upload function for your server-side application.

You can use the Express framework and its library Multer for this task. Install them with npm:

npm install express multer

Create the front-end HTML document for your project first:

<body>
    <div class="container">
        <h1>LearnShareIT</h1>
        <h2>Node.js Upload Files</h2>
        <form id='upload-form'>
            <div class="input-group">
                <input id='files' type="file" multiple>
            </div>
            <button class="submit-btn" type='submit'>Upload</button>
        </form>
    </div>
    <script src='index.js'></script>
</body>

This file has a file picker and an upload button. This is the content of the JavaScript of this document:

const form = document.getElementById("upload-form");
form.addEventListener("submit", submitForm);
function submitForm(e) {
    e.preventDefault();
    const files = document.getElementById("files");
    const formData = new FormData();
    for(let i =0; i < files.files.length; i++) {
            formData.append("files", files.files[i]);
    }
    fetch("http://localhost:5000/upload", {
        method: 'POST',
        body: formData
    })
        .then((res) => console.log(res))
        .catch((err) => ("Error occurred", err));
}

What it does is when there is a submit event, it appends your files to the formData and sends it to the server through a POST request.

On the server side, we have this script (which will be executed by Node.js later):

const express = require("express");
const multer = require("multer");
const upload = multer({ dest: "uploads/" });
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post("/upload", upload.array("files"), uploadFiles);
function uploadFiles(req, res) {
    console.log(req.body);
    console.log(req.files);
}
app.listen(5000, () => {
    console.log(`Your server has started`);
});

We create an app in Express and define a POST API at /upload. We set up the upload directory with Multer and defined a function to intercept requests to our API. This module will add the files you send to the req.files array.

Run this JavaScript file with node:

node upload.js

Open the front-end web page:

Select a file and click Upload. You will see something like this in the terminal app where you run the node command:

The file you have selected now should also appear in the folder “uploads”.

Tutorials

Summary

Node.js is a JavaScript runtime environment. It allows you to create and run applications written in this language outside a web browser. There is an extensive ecosystem of modules you can use to quickly develop feature-rich projects in JavaScript. You can check out our Node.js tutorials to learn how to write applications with this runtime environment.

Leave a Reply

Your email address will not be published. Required fields are marked *