- INDEX
-
runtime
is the environment thatjavascript
runs on. for example: -
Benefits (features we get) of running Javascript in Browser:
- devtools
- console
- sockets
- network requests ->
fetch / xhr
- HTML DOM ->
document
- Timer ->
setTimeOut
,setInterval
-
Benefits of running Javascript in Node.js:
- accessing file system
- better networking capabilities
- These benefits enable us to have perfect conditions for using Node.js as a web server
Node.js
is aJavascript-Runtime
not a language or framework, which allows us to run javascript outside browsers, with additional javascript-features (e.g. accessing file system files)
Node.js Architecture can be presented based on dependencies which are libraries that Node depends on them in order to work properly, the most important ones are V8 engine
and libuv
-
If it wasn't for
V8
, Node would have no way of understanding the javascript code, asv8 engine
converts javascript code into machine code that the computer can understand -
libuv
is an open source library with a strong focus on Asynchronous I/O. This layer is what gives Node access to the underlying operating system (file-system
,networking
, ...)- Also,
libuv
implements 2 important features of Nodejs which are:- Event Loop --> for handling easy tasks like executing callBacks and network I/O
- Thread Pool --> for more heavy work like file accessing or compression
-
libuv
has binding to other languages likeruby
,python
- Also,
-
This architecture provides us with a level of Abstraction, as the dependencies are written in
C++
but they make us have access to their functions in pure javascript
-
Node uses
libuv
so that ifV8 engine
didn't find ajs
syntax likeprocess module
--> it goes tolibuv
-
libuv is a multi-platform C library that provides support for Asynchronous I/O based on event loops
-
-
libuv
abstracts away all specific way do deal withfiles
in differentOperation-Systems
, so thatnode.js
can work on any system/platform, it does so bybinding
these ways tonode
- this means that there's a binding(communication) between
V8 engine
andlibuv
, so whenever they encounter something that may need Asynchronous I/O, then we hand it over tolibuv
- these bindings (Node.js bindings) lets our javascript code call functionality that is implemented in
C
&C++
libuv
has the code for Node.js core functionalities,v8 engine
delegates responsibility of handling the code related to Node.js core functionalities tolibuv
- this means that there's a binding(communication) between
Asynchronous I/O : it's delegating tasks to the operating-system, so that our Node.js program (javascript) doesn't have to wait for the response
When we use Node on a computer, it means that there's a Node process
running on that computer (the process
is just a program in execution).
-
In that process, Node.js runs in a single thread (sequence of instructions). And this is what happen in a thread when starting a Node application
- all the top level code is executed (not inside any callback function)
- all the modules that the app needs are required
- all callbacks are registered
- the event loop starts running
- Some tasks are too heavy (expensive to be executed) in the event loop, because they would block the single thread. That's where the thread pool comes in
-
The thread pool is provided to Node.js by the
libuv
library, and it gives us 4 additional threads that are separate from the main single-thread (we can configure it up to 128 threads)-
The event loop can offload heavy tasks to the thread pool (behind the scenes)
-
More here
-
-
the more threads we have the faster we get the (tasks offloaded to the thread pool) to be executed
// change number of threads in the thread pool process.env.UV_THREADPOOL_SIZE = 2; // default is 4
-
Event Loop: is all the application code that is inside callback functions (non top level code)
-
Node.js is build around callback functions, as it uses "Event-driven Architecture" with "Observer Pattern" where:
- events are emitted
- the event-loop picks them up when they're emitted, and the more expensive tasks go to thread-pool
- then, the necessary callbacks are called.
Most Node.js core modules like (HTTP, file-system and others) implement events internally by inheriting from the
EventEmitter
Class ->server.on('request', (req, res)=> {})
-
The event-loop does the Orchestration in Node.js with specific order of execution
-
Notes:
setImmediate
is called "immediate", because it runs immediately after any I/O operations have finished executingclose callbacks
: it's the last phase because this is for when you close a (file or a network connection) and you have a callback that executes when that connection is closed
-
The event-loop is what makes Asynchronous programming possible in Node.js, making it the most important feature in Node.js design and this is what makes it different from other BE languages
- node.js -> single thread which is good for resources but can be bad if the thread is slowed or blocked
- php -> multiple threads which is more resources-intensive but no danger of blocking
-
In addition to the main 4 callback queues, there're also 2 other queues (
nextTick
queue and the microtasks queue -> for resolved promises)- if there's any callback to be processed in one of these 2 queues, they will be executed right after the current phase of the event loop finishes instead of waiting for the entire loop to finish
- So, callbacks from these 2 queues will be executed right away
- In Javascript, we have the
window
object - In Node.js, we have the
global
object
-
Node.js works best when you deal with
input/output
orservers
,(serving data / streaming)
-
Node.js doesn't work well for heavy server-side processing 7 calculation (CPU/GPU-Intensive) like
machine learning
,video processing
,3D-games
- in this case other languages are better
With libuv and the thread-pool, we won't need for additional web-server like Apache or Nginx to create thousands of threads
-
Node
runs on asingle thread
even if you have millions of users, so make sure you don't block that thread, --> here comes the rule ofLibuv
which gives us 4 additional threads (or more)- so
node
takes care of heavy things byoffloading
them to thethread pool
- it simulates
asynchronous
task by doing each task in a singlecall-stack (thread)
- so
-
Libuv Library
: A library written inC
that provides multithreading to Node.js and allows for heavy processing. -
The thread-pool(collection of threads) in
libuv
are se-up ahead of time and are ready to take on work as it comes in -
The default thread pool includes
4
threads
- There's a common misconception that "all Asynchronous functions are executed in the thread-pool". Instead Node try to save the thread-pool for only the heavy tasks, and for normal tasks, libuv uses the operation-system (kernel) directly
kernel
: is the core-part of the OS that talks to the computer's hardware and has multiple threads of its own- the kernel is really good at doing basic operations, So whenever the functionality exists in the OS, libuv will make the appropriate call saving us from using the valuable resources of the thread-pool
- Libuv takes advantage of the operating system's asynchronous interfaces before engaging the thread pool
- The thread pool is engaged for events that require more processing power including compression and encryption tasks
So, for most of the Async operations, we skip over the thread-pool and instead the event-loop uses the OS-kernel
- by using javascript with node with is single-threaded, we won't have to manage multiple threads manually because Node handles it and simplify our code
-
On the backend side, Node.js offers us the option to build a similar system like (mouse clicks, keyboard button presses, reacting to mouse movements, and so on.) using the events module.
-
EventEmitter provides multiple properties like
on
andemit
. on property is used to bind a function with the event and emit is used to fire an event.emit
is used to trigger an eventon
is used to add a callback function that's going to be executed when the event is triggered
-
it works on the idea of Observer Pattern where
emit
triggers an eventon
observes an event and wait until it occurs
const EventEmitter = require('events'); // class
const celebrity = new EventEmitter(); // instance
// This object exposes, among many others, the (on) and (emit) methods
// subscribe to celebrity for Observer-1
celebrity.on('new song released', function () {
console.log('check the new song of your favourite singer');
});
// triggering the event
eventEmitter.emit('new song released');
-
REPL stands for [
Read
,Evaluate
,Print
,Loop
] (the place we write js code in terminal after writing$node
)- To access the Node.js REPL environment, run:
$node
- To exit node =>
ctrl
+d
- To run
index.js
use:$node src/index.js
$node src/index
$node src/.
$node src
- To run other files use:
$ node src/path-to-file.js
$ node src/path-to-file
- try not to use
./
as you may run the node from another place than the one which has thejs
file
- To access the Node.js REPL environment, run:
The module system creates the ability to export
and import
JavaScript from separate files.
- Node Modules are used to:
- Reuse existing code
- Organize code
- Expose only what will be used
-
Node.js uses the
Common JS module
system to break code into smaller chunks.- TypeScript compiles to the Common JS Module System.
-
export
// Default export module.exports = myOnlyFunction; // exports as object {actual_item : desired_name} module.exports = { myFirstFunction: myFirstFunction, mySecondFunction: mySecondFunction }; // or using ES6 shorthand property names module.exports = { myFirstFunction, mySecondFunction }; // or exports.myFirstFunction = myFirstFunction; exports.mySecondFunction = mySecondFunction;
-
require()
- When using
require
, a precedingslash (/)
must come before a locally created module name; otherwise, Node.js will search thecore modules
and thennode_modules
. - also by default,
require()
doesn't need.js
extension for javascript file as it's set to look for these extensions in this order:.js
.json
.node
const logger = require('./util/logger.js'); // using ES6 object destructuring, to only get (myFirstFunction) const { myFirstFunction } = require('./util/logger.js');
- When using
-
__dirname
and__filename
// working file = /app/util/logger.js console.log(__dirname); // path of current module --> /app/util console.log(__filename); // name of current module --> /app/util/logger.js
-
Wrapping the module in a function
- The module's code is wrapped into an IIFE, which will give us access to a couple of special objects and methods like
require()
- This is why in every module, we automatically have access to stuff like
require
function and global variables that are injected into every module - All the module code is executed inside a function body
- This is also used to keep the top-level variables defined in each module private and scoped only to the current module instead of leaking everything into the global object
- The module's code is wrapped into an IIFE, which will give us access to a couple of special objects and methods like
-
Executing the module code by the Node.js runtime
-
Caching the module after first load for future requires
- this will make the code in the module run only one time -> (interview question)
Starting from Node.js 13.2.0
, Node.js
supports ECMAScript modules, known for their import
and export
statements
- The default format of modules in Node.js is the
CommonJS
. To make Node.js understandES
modules format, you have to explicitly make so.
-
Additional Steps in order to use
ECMAScript-modules
:- The module's file extension is
.mjs
, if not using a special compiler that handles this - Or the module's nearest parent folder has
{ "type": "module" }
inpackage.json
- The module's file extension is
-
NOTE: You must specify the file extension when importing
// won't work import notFoundMiddleware from './middlewares/not-found'; // will work import notFoundMiddleware from './middlewares/not-found.js'; import notFoundMiddleware from './middlewares/not-found.mjs';
-
module
: a file containing some code that could be exported from this module -
package
: collection of modules that have been packaged together
It's not found in the browser APIs, Process
relates to the global node execution process which occurs when you run a .js
file through Node.js.
-
The
process
object is a global that provides information about (and control) the current Node.js process.- As it's a
global
, it's always available to Node.js applications without usingrequire()
. It can also be explicitly accessed usingrequire()
- As it's a
-
The Process module contains the ability to perform tasks immediately before the process exits, and when it exits.
-
beforeExit
allows for asynchronous calls which can make the process continue. -
exit
only happens once all synchronous and asynchronous code is complete.// create conditions for exit code options // example: 0 typically implies no errors, 1 does. process.exitCode = 1; process.on('beforeExit', () => { console.log('beforeExit event'); }); process.on('exit', code => { console.log(`exit event with code: ${code}`); });
-
-
process.env
-> (ENVIRONMENT VARIABLES)-
Process.env
gives you access to the environment information of your Node.js application. It also allows you to add environment variables that can be used if your code is dependent on the environment it is run in. -
To add new environment-variables in our Node process, we have 2 ways:
-
manually in terminal before the start script:
NODE_ENV=production nodemon server.js
-
by using a
.env
configuration file that contains all our environment-variables and access it using dotenv packageconst dotenv = require('dotenv'); // specify the path of configuration file dotenv.config({ path: './config.env' }); // MUST BE BEFORE requiring app file // now we can access the variables in the config file like this: console.log(process.env.PORT);
- when calling the
.config()
method on thedotenv
, it populates theprocess.env
object with the values in the.env
file
- when calling the
-
-
-
process.stdout
it's likeconsole.log
but it does not force a new line break. This allows you to create helpful tools like progress bars. -
process.argv
- An array containing your console arguments information for your executed process.
-
process.nextTick()
- Allows you to run JavaScript between the different phases of the event loop. process.
- When we pass a function to
process.nextTick()
, we instruct the engine to invoke this function at the end of the current operation, before the next event loop tick starts
Using the path module allows us to normalize paths to work across platforms(Windows/Mac/Linux).
- The path module must be imported via
require('path')
. Once imported, there are three commonly used options
const path = require('path');
// Enables you to get the absolute path from a relative path.
console.log(path.resolve('index.js'));
// prints /Users/user/Desktop/app/index.js
// Normalizes any path by removing instances of . , turning double slashes into single slashes and removing a directory when .. is found.
console.log(path.normalize('./app//src//util/..'));
// prints app/src
// Used to concatenate strings to create a path that works across operating systems. It joins the strings, then normalizes the result.
console.log(path.join('/app', 'src', 'util', '..', '/index.js'));
// prints /app/src/index.js
const fs = require('fs');
- allows for reading and writing to files with many options.
npm i module-name # install module to dependencies
npm i --save-dev module-name # install to dev dependencies (only for development and not for production)
npm i --save-dev module-name@1.19 # install a specific version (1.19 here) of module
-
package-lock.json
- contains all of the information for the dependencies of the modules you have installed.
- it has the dependencies for the package you installed so that the main
package.json
is clean. - it's important to get the exact
semantic versioning
- It is best practice to add
package-lock.json
as well as./node_modules
to your.gitignore
file - It is best practice to add
package-lock.json
as well as./node_modules
to your.gitignore
file
-
npm audit
--> It shows all vulnerabilities your dependencies got (excluding peerDependencies).- You can disable the warning for single package installations with the
--no-audit
flag
- You can disable the warning for single package installations with the
*
means that you'll accept all updates^
means that you'll only accept minor releases~
means that you'll only accept patch releases>, >=, <=, <
are also valid for saying you'll accept versions greater/less/equal to the listed version||
allows you to combine instructions- ex : "prettier": "2.2.1 || >2.2.1 < 3.0.0" which says use prettier greater than 2.2.1 and less than version 3.0.0.
In general, all packages should be installed locally.
- it install the package globally on the system, so you can use it in the
terminal
& on thescripts
- it makes the package available to the current project (where it stores all of the node modules in node_modules)
- It will not be available as a command that the shell can resolve until you install it globally
dependencies | dev-dependencies |
---|---|
These are the packages that are required for the application to work properly. | These are the packages that you need while developing the project but not when deploying the project. These packages are not built when the project is deployed. |
--save saves the name and version of the package being installed in the dependency object. |
--save-dev saves the name and version of the package being installed in the dev-dependency object. |
It's a piece of software that can be used by another piece of software, in order to allow application to talk to each other
-
API has a broader meaning than building Web APIs, It can be a "web api" or other things like Node-APIs:
-
Web API is usually what's important in the context of Node.js, which is build using the REST Architecture
- REST APIs: are APIs following the REST Architecture
It stands for "Representational States Transfer", It's a way of building web APIs in a logical way making them easy to consume. This requires some principles:
- Separate API into logical resources
- Expose these resources using URLs
- Use HTTP methods to perform different actions on data
- Send data back to client with existing format-standards (usually JSON format)
- The API must be stateless and cacheable
- Stateless:
- Cacheable:
- by caching the request, we save the result of it for future use, this reduce the work from the server and improve performance
http
/ https
are languages used to communicate between server & client in a way called "Request-Response Model" or "Client-Server Architecture"
This is what happens when we access a webpage or accessing a wep API:
-
client type the URL(domain) to access, ex
https://www.google.com/maps
, (https
is for the protocol of the connection)- the domain name is not the address written by the user (which is for us to memorize the domain easily), instead it's converted to the real address of the server and that happens through a DNS Lookup in a special server ->
https://216.58.211.206:443
-
this actually happens through your internet service provider (ISP)
-
- the port number is just really to identify a specific service running on a server (sub-address)
- the domain name is not the address written by the user (which is for us to memorize the domain easily), instead it's converted to the real address of the server and that happens through a DNS Lookup in a special server ->
-
After having the web address, a TCP/IP Socket Connection is established between the browser and the server, and this connection is kept alive for the entire time it takes to transfer all the files of the website from the server to the client
TCP
: is "Transmission Control Protocol" andIP
is "Internet Protocol", and together they are communication protocols that define how data travels across the web- The job of TCP protocol is:
- to break out the requests and responses into thousands of small chunks called "packets" before they're sent
- and once they get to their destination, It will reassemble all the packets into (the original request or response) so that the message arrives at the destination as quick as possible, which wouldn't be possible if we sent the website as one big chunk
- The job of IP protocol is:
- to send and route all of these packets through the internet
- So it endures that all of them arrive at the destination
- The job of TCP protocol is:
- Communication Protocols: are system of rules that allows 2 or more parties to communicate
-
They are the ones that set the rules on how data moves on the internet
-
-
After establishing the connection, We make a HTTP Request, and the request message contains multiple sections (start line, request headers, request body)
- HTTP: is a communication protocol that allows clients and web servers to communicate by sending requests and response messages from client to server and back
-
After the request hits the server and the server has the data ready to be send back, The server will send back a HTTP Response, and the response message will look similar to the request message
-
The request/response cycle doesn't happen just once, As at first request, We get back just the initial HTML file, which then gets scanned for all the assets needed for the website (
js
,css
,img
,...) that require additional requests- for each file, the browser will make a new HTTP request to the server
- the number of request/response happening at the same time is limited, so that the connection won't slow down
- When all files have arrived in the browser and are ready, they are rendered
Node.js makes it easy to create a simple web server that processes incoming requests asynchronously
.
-
req
is a readable stream, which we can be listened to by using the.on()
function -
res
is a writable stream, which we can be listened to by using thewrite
functions to write data to it like (headers, body, etc) -
explanation of code below :
-
requestListener
function is the callback function that takes a requestobject
and a responseobject
as parameters. -
The
request
object contains things such as therequested URL
, but in this example we ignore it and always return "Hello World".- when using
request
with other servers, you mustend
it in order to be sent --->req.end()
- when using
-
The
response
object is how we send the headers and contents of the response back to the user making the request.- Here we return a
200
response code (signaling asuccessful response
) with the body "Hello World".
- Here we return a
-
http.createServer
method creates(returns) a server that callsrequestListener
whenever a request comes in. -
server.listen(8080)
: tells the server to wait (listen) for incoming requests on the specifiedport 8080
-
const http = require('http');
// callback function
const requestListener = function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.write('<h1>Hello from Node.js Server!</h1>');
res.end('This is the massage that will be shown in the browser!'); // indicate that the response is complete and ready to be sent
};
const server = http.createServer(requestListener);
server.listen(PORT, () => {
// this callback-function is optional, it display this message on the console when you start the server --> node index.js
console.log(`listening on port ${PORT}`);
});
//-----------------------or-----------------------//
// creating a new server
const server = require('http').createServer();
// request will be when i open the link http://127.0.0.1:8000/
server.on('request', (req, res) => {
fs.readFile('test-file.txt', (err, data) => {
// here the stuf to do after the file was read
if (err) console.log(err);
res.end(data);
});
});
// starting the server (will run on http://127.0.0.1:8000/)
server.listen(8000, '127.0.0.1', () => {
console.log('listening.......');
});
To shut down server we use process.exit(1)
which ends the program (immediately abort all the requests that are currently still running or pending which may not be good), So usually it's better to shut down the server gracefully(we first close the server and only then we shut down the application)
// Shut down gracefully
server.close(() => {
process.exit(1);
});
It's a piece of information about the response that we are sending back
- writing header/statusCode always need to be set before we send out the response
- headers can be used to send metadata
// Calling response.writeHead method
response.writeHead(404, {
'Content-Length': Buffer.byteLength(body),
'Content-Type': 'text/plain'
});
-
when you have a
server
that has differentURLs
that response differently on which one is being requested --> we call these differentURLs
:EndPoints
-
Routing
refers to how an application’s endpoints (URIs
) respond to clientrequests
. -
There are two ways to implement routing in node.js which are :
-
Without using Framework: based on the request-url, we will send corresponding response
if (req.url === '/message') { res.end('Here are your messages!'); } else { // fallback response for not-found route res.statusCode = 404; res.end(); }
-
to access route params, we first need to parse the URL:
const parsedUrl = url.parse(req.url, true); console.log(parsedUrl.query.id);
-
By Using Framework => Express.js
-
Streams: Used to process (read/write) data piece by piece (chunks), without completing the whole read or write operation, and therefore without keeping all the data in memory
-
Stream: is an ongoing process, as the request is read by Node.js in chunks
- This is done so that we can start working on the individual chunks without having to wait for the full request being read
- Ex: uploading a file or streaming-services like netflix, youtube
- streams are perfect for handling large volumes of data like videos
- streams are more a efficient data processing in terms of memory (no need to keep all data in memory) and time (we don't have to wait u til all data is available)
- This is how Node handles all requests because it doesn't know in advance how complex and big they request-chunks are.
- This is done so that we can start working on the individual chunks without having to wait for the full request being read
-
streams &
pipe()
- sometimes, there's a problem when reading streams, as the readable stream used to read file from the disk is much faster than sending the result with the response writable stream over the network, and this will overwhelm the response-stream which can't handle all the incoming data so fast. This problem is called "Back Pressure"
- "Back Pressure": It's when the response can't send the data nearly as fast as it's receiving it from the file
pipe
operator is available in all readable streams and allows us to pipe the output of a readable stream into the input of a writable stream- This fixes the problem of "Back Pressure", because it will automatically handle the speed of the data coming
in
andout
- This fixes the problem of "Back Pressure", because it will automatically handle the speed of the data coming
- sometimes, there's a problem when reading streams, as the readable stream used to read file from the disk is much faster than sending the result with the response writable stream over the network, and this will overwhelm the response-stream which can't handle all the incoming data so fast. This problem is called "Back Pressure"
-
Streams Examples:
// Instead of this: fs.readFile('rest.txt', (err, data) => { res.end(data); // write everything at once into a variable (data) and once that was ready, we then send that entire piece to the client }); // --------------------------------------------------------- // // Solution 1: Streams const readable = fs.createReadStream('rest.txt'); // listening to the "data" event readable.on('data', chunk => { res.write(chunk); // streaming the content of the file to the client (reading one piece and send it as soon as it's available and so on until all the file is read (streamed to the client)) }); readable.on('end', () => { res.end(); // notifying the server that no more streams will be send }); // Solution 2: Streams with pipe -> BEST SOLUTION const readable = fs.createReadStream('rest.txt'); readable.pipe(res); // - (readable) is the source // - (res) is the writable destination -> where the data goes to
The problem with streams is that we can't arbitrarily try to work with these chunks. Instead to organize the incoming chunks, we use buffer
-
Buffer
- It's like a bus-stop, as buses are always driving, but "it's for users", and so to climb on the bus and leave the bus, you need bus-stops where you can track the bus, and that is what the
buffer
is - It's a construct which allows you to hold multiple chunks and work with them before they are released once you are done
- It's like a bus-stop, as buses are always driving, but "it's for users", and so to climb on the bus and leave the bus, you need bus-stops where you can track the bus, and that is what the
-
This is done using events:
const dataArr = []; req.on('data', chunk => { dataArr.push(chunk); }); // "data" event will be fired whenever a new chunk is ready to be read req.on('end', chunk => { const parsedBody = Buffer.concat(dataArr).toString(); // this is what we can work with // ... }); // "done" event will be fired whenever it's done parsing the incoming requests-data // -> when calling "res.end()"
- when you have
async data
that is exported we want it to be done when it's exported so that we don't get empty data - AKA: when you deal with streams you include them in a promise
- to do so we use
Promise
- we export the promise and not the function
- we include the function you want to be the resolved value of the promise
- in the server-listening place we run(await) the function
const server = http.createServer(app); // using express as a listening function for the http-server
async function startServer() {
// this is done so that we have the data ready when we start the server before any requests so that it's available for any request
await loadPlanetsData(); // the function that contains the promise
server.listen(PORT, () => {
console.log(`listening on port http://localhost:${PORT}`);
});
}
startServer();
The stream/promises API
provides an alternative set of asynchronous utility functions for streams that return Promise objects
rather than using callbacks
.
- The API is accessible via
require('stream/promises')
require('stream').promises
it's a 3rd party library can be used instead of the above
- Convert any Stream instance to
thenable
- stream-promise
Morgan is an HTTP request level Middleware
.
- It is a great tool that logs the requests along with some other information depending upon its configuration and the preset used.
- It's helpful while debugging and also if you want to create
Log files
. - It's not only used in development mode, as date from it is used in production mode, so it's a regular dependency and not a dev-dependency
-
usually it should be before any security consideration or after
CORS
app.use(cors(corsOptions)); // Middleware app.use(morgan('combined')); // Middleware
Node.js makes use of the thread-pool to handle multiple requests coming at the same time and by passing input/output tasks that takes longer time to complete
- This is done so that the (javascript-code and the event-loop) don't get blocked, even though the code is running on a single thread
- When running on a single processor, each request on the server will wait for the previous request to finish, which is not good for performance
When dealing with servers that are overloaded with so much work, it's best to divide that work and spread the load
-
node.js runs on a single thread, So to do multi-threading we can:
- run node on multiple processes side-by-side to share the
work
- spreading the request between multiple node.js processes that response to the same request in the same way, So that they share the load evenly
- it means that instead of taking each request and handling it in
one node server (process)
, we insteadspread
the requests out between multiple node.js processes - they run side-by-side without effecting the performance
- run node on multiple processes side-by-side to share the
-
It's done when you want maximum performance on the server
Cluster Module allows us to create copies of the Node-process that each run the server-code side-by-side in parallel
The Node.js Cluster module enables the creation of child processes (workers) that run simultaneously and share the same server port. Each spawned child has its own event loop, memory, and V8 instance.
-
How it works:
- when running node.js application, the main node process is created which is called: "master process"
- Inside of the cluster module, we have access to a function called
fork()
, which is used to create copies of themaster
process called "worker processes - the worker-processes are responsible for doing the heavy work of (taking HTTP requests, process them and respond to them)
-
each time you use
fork()
method you create new process -
the number of process should equal number of
cors in the cpu
const NUM_WORKERS = os.cups().length;
-
this is similar to
Load Balancer
orHorizontal Scaling
orround robin
approach
const cluster = require('cluster');
const os = require('os');
// check if in the master-process (the first time the server runs)
if (cluster.isMaster) {
// any code here will be executed first time whe server is executed
console.log('master process has been started');
const NUM_WORKERS = os.cpus().length; // each process requires single core in the computer-processor (CPU)
for (let i = 0; i < NUM_WORKERS; i++) {
cluster.fork(); // create a worker
}
} else {
// the code here will run when code is running as a worker-process
console.log('worker has started');
app.listen(5000);
}
Load balancing: is the process of distributing a set of tasks over a set of resources (computing units), with the aim of making their overall processing more efficient
- The load balancer is what takes requests and distribute the responsibility of handling these requests and share them between different processes or applications
- load balancing applies when running multiple (servers or processes in parallel) each handling the same kind of request
- ex using the same route
- this is related to Horizontal Scaling
In Node.js, we can use the cluster-module to do load balancing of requests as they come-in to the Node-HTTP-server using the Round-Robin algorithm to determine which process handles the requests
It's much easier way than Node Cluster Module
for using Load balancing and to control processes
console.log('worker has started');
app.listen(5000);
# you should install pm2 globally
pm2 start server.js -i max # name of the js-file, i -> for "instance"
# same thing but sends log to a file
pm2 start server.js -l logs.txt -i max
The worker_threads
module enables the use of threads that execute javascript in parallel
-
worker-threads are useful for performing CPU-intensive javascript operations
- They don't help much with I/O operations, as the Node.js built-in asynchronous I/O operations are more efficient than worker-threads can be
-
worker-threads is a new feature coming from a new feature in the V8 engine called: "V8-isolates", they're like VMs running js-code together which enables us to mimic having multiple threads in javascript
-
work threads
vsclusters
-
work threads
make us work on one process with multiple threads