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.
To run a local external Javascript file in the REPL, we just enter node NAME-OF-FILE.js
into the terminal.
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');
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}
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'
... */
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.
Example of Event Loop
Timer 2 finished
should have been printed before Immediate 2 finished
according to the diagrams above. But that is not the case here, why ?setImmediate
then that callback will actually be executed right away after the polling phase and even before expired timers, if there are some.nextTick
is a part of the microtasks queue, which gets executed after each phase and not just after one entire tick.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
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...");
});
console.log(arguments); // if we are inside of a function then it'll log the arguments passed into that function.
[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'
}