1. Node JS

What is Node.js?

Node.js is a Javascript runtime built on Google’s open-source v8 Javascript engine. Traditionally, Javascript’s runtime (i.e. environment) is the browser. Node.js is a container that allows Javascript to run outside of a browser. The v8 engine is responsible for actually executing that Javascript. With Node.js, we can use Javascript on the server side of web development.

Why Node :

Pasted image 20240113131604.png

Using Node.js :

To run a local external Javascript file in the REPL, we just enter node NAME-OF-FILE.js into the terminal.

Reading and writing files

One of the ways to read a file is to use the readFileSync() method in the fs module (‘fs’ standing for ‘file system’). This function takes two arguments: a file path and a character encoding (usually "utf-8" for English).

const fs = require('fs');

const textIn = fs.readFileSync('./txt/input.txt', 'utf-8');
console.log(textIn);
const textOut = `This is the text: ${textIn}.\nCreated on ${Date.now()}`;
fs.writeFileSync('./txt/output.txt', textOut); // creates an output.txt file and dumps data in it
console.log('File written');

Blocking and non-blocking

Pasted image 20240113165854.png

The above code snippets are examples of synchronous code: each line of code is executed only after the line before has finished running. We also call this “blocking,” as each line blocks the next line from running until its own code has finished. Because blocking can create performance issues, we instead write non-blocking code in which heavy operations have callback functions to be executed after the operation has finished.

What exactly are the performance issues of blocking code? Node.js is single-threaded, meaning all its processes happen in one part of the CPU. If thousands of users are using our app at once and one user makes a heavy request, blocking code will force all other users to wait for that request to finish before Node.js can process those other users’ requests. Far from ideal. With asynchronous code, we can have that heavy task running in the background while other users continue to use the app. To achieve async behavior, Node.js is heavily reliant on callback functions.

(Side note: PHP is multi-threaded and thus has an entire different paradigm for asynchronous performance).

A simple example of non-blocking code:

const fs = require("fs");

fs.readFile("./txt/start.txt", "utf-8", (err, data) => {
  console.log(data);
});
console.log("Will read file!");

// Output:
// Will read file!
// {contents of start.txt}

Pasted image 20240113171717.png

The Problem : Callback Hell :

Pasted image 20240113171917.png

const { pseudoRandomBytes } = require("crypto");
const fs = require("fs");

/* -------------- Blocking, synchronous way ------------- */
const textIn = fs.readFileSync("./txt/input.txt", "utf-8");
console.log(textIn);
const textOut = `This is what we know about the avocado: ${textIn}.\nCreated on ${Date.now()}`;
fs.writeFileSync("./txt/output.txt", textOut);
console.log("File Written !!");

/* ----------- Non-Blocking, Asynchronous way ----------- */
// node will start reading the 'start.txt' file and after it is done reading that it will start executing the calback function. It calls the callback function with 2 arguments : (error, data)
fs.readFile("./txt/start.txt", "utf-8", (err, data1) => {
    if(err) return console.log('ERROR 😔💥'); // if we encounter an error then this line will be executed
    
  fs.readFile(`./txt/${data1}.txt`, "utf-8", (err, data2) => {
    console.log(data2);
  });
});
// here since data1 = 'read-this' therefore the data2 will read data from 'read-this.txt'

console.log("Will read file!");

/* → 
    Will read file!
    'data from read-this.txt'
    ... */

A Simple Website with Node.js

This project takes product data from a JSON file and uses it to create web pages. We’ll configure Node.js to create new HTML files and elements by using the HTML templates we provide it.

  • See github repo

Node Process and Threads :

Pasted image 20240127214117.png

NodeJs Event Loop :

Pasted image 20240127214626.png
Pasted image 20240127215247.png
Pasted image 20240127215501.png
Example of Event Loop
Pasted image 20240127221421.png

Pasted image 20240127222323.png

  • here Timer 2 finished should have been printed before Immediate 2 finished according to the diagrams above. But that is not the case here, why ?
  • This is because the event loop actually waits for stuff to happen in the poll phase, so in that phase where I/O callbacks are handled.
  • So when this queue of callbacks is empty, which is the case in our fictional example here, we have no callbacks and all we have is these timers, then the event loop will wait until there is an expired timer. But if we scheduled a callback using setImmediate then that callback will actually be executed right away after the polling phase and even before expired timers, if there are some.

Pasted image 20240127225552.png

  • This happens because nextTick is a part of the microtasks queue, which gets executed after each phase and not just after one entire tick.
  • Pasted image 20240127225909.png

Pasted image 20240127232625.png

Events in Practice :

const EventEmitter = require("events");
const http = require("http");

// const myEmitter = new EventEmitter(); // we use this to create an instance of the EventEmitter class

// .on() is used to listen to an event
// .emit is used to emit an event

class Sales extends EventEmitter {
  constructor() {
    super();
  }
}
const myEmitter = new Sales();

myEmitter.on("newSale", () => {
  console.log("There was a new sale!");
});

myEmitter.on("newSale", () => {
  console.log("Consumer Name: Jonas");
});

myEmitter.on("newSale", (stock) => {
  console.log(`There are now ${stock} items left in stock.`);
});

myEmitter.emit("newSale", 9); // event name and argument

// Note : if we have multiple event listeners for the same event, then they are executed in the order in which they are defined

// but if we want to use these in a real world scenario, then we need to create a class which inherits from the EventEmitter class :

/* ------------------------------------------------------ */

const server = http.createServer();
server.on("request", (req, res) => {
  console.log("Request received!");
  res.end("Request received");
});
server.on("request", (req, res) => {
  console.log("Another req received!");
//   res.end("Another Request");
});
server.on("close", () => {
  console.log("Server Closed!");
});

server.listen(8000, "127.0.0.1", () => {
  console.log("Waiting for requests...");
});

// the request is made twice by the browser, once for the favicon.ico file and once for the actual request

Streams :

Pasted image 20240131002156.png
Pasted image 20240131002618.png

const fs = require("fs");
const server = require("http").createServer();

server.on("request", (req, res) => {
  // Solution 1
  //   fs.readFile("test-file.txt", (err, data) => {
  //     if (err) console.log(err);
  //     res.end(data);
  //   });

  // Solution 2: Streams
  //   const readable = fs.createReadStream("test-file.txt");
  //   readable.on("data", chunk => {
  //     res.write(chunk);
  //   });
  //   readable.on("end", () => {
  //     res.end();
  //   });
  //   readable.on("error", err => {
  //     console.log(err);
  //     res.statusCode = 500;
  //     res.end("File not found");
  //   });

  // Solution 3
  const readable = fs.createReadStream("test-file.txt");
  //    readableSource.pipe(writeableDestination)
  readable.pipe(res); // pipe operator allows us to pipe the output of a readable stream right into the input of a writable stream
});

server.listen(8000, "172.0.0.1", () => {
  console.log("Listening...");
});

Node Modules :

Pasted image 20240131011614.png
Pasted image 20240131012005.png
Pasted image 20240131012611.png

console.log(arguments); // if we are inside of a function then it'll log the arguments passed into that function.
  • Output :
[Arguments] {
  '0': {},
  '1': [Function: require] {
    resolve: [Function: resolve] { paths: [Function: paths] },
    main: {
      id: '.',
      path: 'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\2-how-node-works\\starter',
      exports: {},
      filename: 'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\2-how-node-works\\starter\\modules.js',    
      loaded: false,
      children: [],
      paths: [Array]
    },
    extensions: [Object: null prototype] {
      '.js': [Function (anonymous)],
      '.json': [Function (anonymous)],
      '.node': [Function (anonymous)]
    },
    cache: [Object: null prototype] {
      'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\2-how-node-works\\starter\\modules.js': [Object]     
    }
  },
  '2': {
    id: '.',
    path: 'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\2-how-node-works\\starter',
    exports: {},
    filename: 'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\2-how-node-works\\starter\\modules.js',      
    loaded: false,
    children: [],
    paths: [
      'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\2-how-node-works\\starter\\node_modules',
      'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\2-how-node-works\\node_modules',
      'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\node_modules',
      'E:\\Courses\\FILES\\NODE\\node_modules',
      'E:\\Courses\\FILES\\node_modules',
      'E:\\Courses\\node_modules',
      'E:\\node_modules'
    ]
  },
  '3': 'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\2-how-node-works\\starter\\modules.js',
  '4': 'E:\\Courses\\FILES\\NODE\\complete-node-bootcamp-master\\2-how-node-works\\starter'
}

Screenshots :

Pasted image 20240131022006.png

Pasted image 20240131022022.png
Pasted image 20240131022029.png
Pasted image 20240131022036.png