← Back
The Ultimate Guide to Node.js

The Ultimate Guide to Node.js

What is Node.js?

Node.js is an open-source JavaScript runtime environment that allows you to execute JavaScript code on the server side. Introduced around 2015, it enabled developers to run JavaScript locally, whereas previously, JavaScript could only be executed inside a web browser. Node.js is built on Google Chrome's V8 JavaScript engine.

What is a Runtime?

The environment where JavaScript code is executed. It could be executed on the server, on the browser, or even on small devices like watches.

What is the V8 engine?

The V8 engine is an open-source JavaScript engine developed by Google. It is used to execute JavaScript code in various environments, most notably in the Google Chrome web browser.

Other JavaScript engines include:

  • SpiderMonkey: Developed by Mozilla.
  • JavaScriptCore: Used in Safari.
  • V8: Used in Brave, Microsoft Edge, and Chrome.

Installing Node.js

You can install Node.js in several ways:

  • Build from source
  • Using a package manager
  • Using Node Version Manager (nvm)

How to Start a Node.js Project

To initialize a Node.js project locally, run the following command in your terminal:

npm init -y

When you run this command, a package.json file will be generated inside your folder.

What is package.json{:jsx}?

The package.json{:jsx} file is a key component of any Node.js or JavaScript-based project. It provides important metadata about the project and helps manage dependencies, scripts, and other configurations.

{
  "name": "nodejs-app", // Name of your website/app/library
  "version": "1.0.0", // Current version
  "main": "index.js", // Entry point
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
    // Dev Specified Scripts
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "chalk": "^5.3.0",
    "express": "^4.19.2",
    "nodemon": "^3.1.4",
    "url": "^0.11.4"
  }
}

Modules In Node.js

Modules are a fundamental part of how code is organized and reused. They allow you to divide your code into separate files and directories, making it easier to manage, maintain, and share.

In Node.js, modules are individual files or packages that contain reusable code. They can export functionality such as functions, objects, or variables that can be imported and used in other files.

Types of Modules

  1. Core Modules: These are built-in modules provided by Node.js itself. Examples include fs (file system), http (HTTP server and client), path (path utilities), and os (operating system information). You don’t need to install these separately.

    const fs = require('fs');
    
  2. Local Modules: These are custom modules that you create yourself within your project. You define them in your own files and export their functionality.

    Example myModule.js:

    module.exports = function() {
      console.log('Hello from my module!');
    };
    

    Example usage:

    const myModule = require('./myModule');
    myModule();
    
  3. Third-Party Modules: These are modules published on the npm registry. You can install these modules using npm and include them in your project. Examples include express (web framework) and lodash (utility functions).

    To install a module:

    npm install express
    

    To use the installed module:

    const express = require('express');
    

Exporting and Importing

Exporting

You use module.exports or exports to provide functions, objects, or variables from a module.

// myModule.js
const add = (a, b) => {
     console.log(a + b);
}

// Method 1
module.exports = {
  addFunc: add,
};

// Method 2 (Exporting an anonymous function directly)
exports.add = (a, b) => {
      console.log(a + b);
}   

Importing

You use require() to import the exported functionalities from a module into another file.

// app.js

// Importing for Method 1
const myModule = require('./myModule');
myModule.addFunc(2, 3); // output: 5

// Importing for Method 2
const myModule2 = require('./myModule');
myModule2.add(2, 3); // output: 5

File Handling In Node.js

Write File

Synchronous:

const fs = require('fs');

// This creates a test1.txt file in the same directory and writes "Hey hello there!" synchronously.
fs.writeFileSync('./test1.txt', 'Hey hello there!'); 

Asynchronous:

const fs = require('fs');

fs.writeFile('./test2.txt', 'Hello there', (err) => {
    if(err) console.log('error', err);
});

Read File

Synchronous:

const fs = require('fs');

// The second argument is the encoding type.
const result = fs.readFileSync('./test1.txt', 'utf-8');
console.log(result);

This will read the content of test1.txt and store it in result synchronously.

Asynchronous:

const fs = require('fs');

fs.readFile('./test2.txt', 'utf-8', (err, data) => {
    if(err) console.log('error', err);
    else console.log(data);
});

This will read the content of test2.txt and print it to the console asynchronously. The callback function receives err and data parameters. Note that the fs (File System) module offers many more functionalities beyond standard reading and writing.


Node.js Architecture

  1. The Client makes requests.
  2. Every request goes into the Event Queue. Since it is a queue, requests are processed in a FIFO (First-In-First-Out) order.
  3. The Event Loop continuously checks if there are requests in the Event Queue. If there are, it processes them.
  4. Requests are categorized into two types:
    • Blocking requests (Synchronous)
    • Non-blocking requests (Asynchronous)

Blocking Requests

The Event Loop sends blocking requests to the Thread Pool. The Thread Pool contains a limited number of threads/workers. By default, there are 4 threads, but this can scale up to the number of CPU cores depending on the machine.

A single thread processes one request at a time. If you have a maximum of 4 threads and 4 concurrent blocking requests, they will occupy all available threads. If a 5th request comes in, it will have to wait for one of the threads to become free. Once processed, the thread sends the response. This limitation is why blocking operations can cause performance bottlenecks.

Non-Blocking Requests

The Event Loop processes non-blocking requests immediately and sends the response back without relying heavily on the thread pool.


Internal vs External Packages and Versioning

Internal Packages: Node.js comes with built-in packages out of the box. Some common ones include:

  • fs — Filesystem
  • path — Path-related functions
  • http — Create HTTP Servers

External Packages: These packages are written and maintained by the broader community. You leverage their work in your project. For example:

  • express
  • chalk

Semantic Versioning Format

Every external package is updated incrementally. A version string typically looks like this:

"chalk": "^5.3.0" 
// 5 represents MAJOR
// 3 represents MINOR
// 0 represents PATCH

The format strictly follows MAJOR.MINOR.PATCH:

  • MAJOR: Indicates significant updates or breaking changes.
  • MINOR: Signifies the addition of new features or improvements in a backward-compatible manner.
  • PATCH: Includes backward-compatible bug fixes or minor improvements that resolve issues without adding new features.

Symbols:

  • Caret (^): Tells npm to install any version that is compatible up to the next major version. For ^5.3.0, this includes 5.3.1, 5.4.0, etc., but less than 6.0.0.
  • Tilde (~): Restricts updates to the patch level only. For ~5.3.2, it allows 5.3.4 but restricts 5.4.0.

The package-lock.json File

The package-lock.json file records the exact versions of all dependencies and sub-dependencies installed at the time npm install is executed.

Consistency: By locking down these versions, package-lock.json ensures every time dependencies are installed, the exact same package versions are pulled. This prevents discrepancies that can arise from different versions being installed in different environments.

Imagine a team of developers using different versions of the same package:

  • Developer 1: 5.3.2
  • Developer 2: 5.3.4
  • Server: 5.3.0

Without a lock file, slight variations in a dependency’s behavior could break your application in production. The package-lock.json removes this ambiguity.

Should We Push package-lock.json to GitHub?
Yes! Pushing this file to version control ensures that anyone pulling your repository replicates the exact dependency tree used in production.


What is HTTP?

HTTP (Hypertext Transfer Protocol) is an application-layer protocol designed for transmitting documents in the form of JSON, HTML, or plain text. It follows a client-server model, where the client (usually a web browser) sends requests to the server, and the server responds with the requested data.

Request-Response Model

  1. Client Request: When you type a URL into your browser or click a link, your browser sends an HTTP request to the server hosting the website.
  2. Server Processing: The server receives and processes the request.
  3. Server Response: The server sends back an HTTP response, which includes:
    • A Status Code (e.g., 200 for success, 404 for not found)
    • Headers with metadata about the response
    • The Requested Content (if available)
  4. Client Rendering: Your browser receives the response and renders the content.

Domain Names and IPs

Domain Names: A human-readable address for a website designed to be easier to remember than IP addresses. They are structured from right to left:

  • Top-Level Domain (TLD): For instance, .com, .org, .net.
  • Second-Level Domain: The name registered by the organization (e.g., google in google.com).
  • Subdomains: Extensions added before the main domain (e.g., www, blog).

IP Addresses: An IP (Internet Protocol) address is a unique numerical label assigned to a device on a network. Every domain name fundamentally resolves to an underlying IP address. You can find out an IP address by running the ping command in your terminal.


HTTP Methods

  1. GET: Retrieve data from a server.
  2. POST: Submit data to a server to create a new resource.
  3. PUT: Update or replace an existing resource on the server.
  4. PATCH: Partially update an existing resource.
  5. DELETE: Remove a resource from the server.

Response Data Types

The response represents what the server returns. It could be:

  • Plaintext Data: Not used very often for rich content.
  • HTML: If the client requested a website page.
  • JSON Data: Standard for fetching data via APIs (e.g., user details, lists).

Status Codes

HTTP status codes are three-digit numbers returned by a server indicating the outcome of a client's request.

  • 200 Series: Success
  • 300 Series: Redirection
  • 400 Series: Client Errors
  • 500 Series: Server Errors

Building an HTTP Server In Node.js

Step 1 — Import the http Module

const http = require('http');

Step 2 — Create the Server Use the http.createServer() method to spin up a new server. This method takes a callback function executed whenever a request reaches the server.

const server = http.createServer((request, response) => {
  // This function handles incoming requests
});

Step 3 — Handle Incoming Requests and Send Responses You can handle incoming requests (req) and issue responses (res).

const server = http.createServer((request, response) => {
  console.log('New request received');
  response.statusCode = 200; // HTTP status code 200 means OK
  response.end('Hello World!'); // Send the response and end the connection
});

Step 4 — Listen on a Port Specify a port for your server to listen on. The listen() method starts the server.

const PORT = 3000;
server.listen(PORT, () => {
  console.log('Server Started');
});

Full Example: Save this code in a file, say server.js, and execute it using node server.js. Then, navigate to http://localhost:3000 in your web browser.

const http = require('http');

const server = http.createServer((request, response) => {
    console.log('New request received');
    response.statusCode = 200;
    response.end('Hello World!');
});

server.listen(3000, () => console.log('Server Started'));

Advanced Example with File Logging and Routing:

const http = require('http');
const fs = require('fs');

const myServer = http.createServer((req, res) => {
    const date = new Date();
    const log = `Time: ${date.toLocaleTimeString()} New request received!\n`;
    
    fs.appendFile('log.txt', log, (err, data) => {
        switch(req.url){
            case '/': 
                res.end("HomePage");
                break;
            case '/about': 
                res.end("I'm Nisha!");
                break;
            case '/contact-us': 
                res.end("+91 9876543210");
                break;
            default: 
                res.statusCode = 404;
                res.end("Error: 404 Not Found");
        }
    });
});

myServer.listen(5173, () => console.log("Server Started!"));

Handling URLs in Node.js

A URL (Uniform Resource Locator) is a reference or address used to access resources on the internet. It consists of several components. Let's break down the following example URL:

https://www.example.com:8080/path/to/page?name=abc&age=30#section2

  1. Protocol (Scheme) - https://

    • Specifies the method to access the resource.
    • Examples: http, https, ftp, mailto, file.
  2. Hostname (Domain Name) - www.example.com

    • Identifies the server that hosts the resource.
    • Includes subdomains (www), the second-level domain (example), and the top-level domain (.com).
  3. Port - :8080

    • Specifies a particular port on the server. Most default ports (like 80 for http or 443 for https) do not need to be explicitly written. Custom ports follow a colon :.
  4. Pathname - /path/to/page

    • Indicates the exact location of the resource on the server. It always starts with a /.
  5. Query String - ?name=abc&age=30

    • Contains parameters that provide extra data to the server, starting with a ?.
    • Key-value pairs are separated by & (name=abc and age=30).
  6. Fragment (Anchor) - #section2

    • Identifies a specific section within a web page, starting with #. It focuses the browser on a specific labeled part of the document.

Summary of Components:

  • Protocol: Defines how to access the resource.
  • Hostname: Identifies the server.
  • Port: Specifies a port other than the default.
  • Pathname: Points to the resource’s location on the server.
  • Query String: Provides additional parameters/data.
  • Fragment: Directs the browser to a specific part of the resource.

These components work together to provide a complete address for accessing a resource across the internet.