Tuesday, May 28, 2024

The Truth About Node.js: Single-Threaded or Multi-Threaded?

Node.js is often described as single-threaded, which can be somewhat misleading. This description primarily refers to the JavaScript execution model. Node.js uses an event-driven, non-blocking I/O model, which allows it to handle many concurrent connections efficiently with a single main thread. However, this doesn't mean that Node.js can't take advantage of multiple threads. Under the hood, Node.js uses a thread pool for certain operations, and with the introduction of worker threads, Node.js can execute JavaScript code in multiple threads.

NOTE: I have described it very clearly in my below Youtube video. The link of the video is https://youtu.be/8IDW3OQ_blQ?si=XCmM3x45nt1FRgj-


Understanding the Single-Threaded Nature

Node.js runs JavaScript code in a single thread. This means that all JavaScript code execution happens in a single call stack, and operations are executed one at a time. This is efficient for I/O-bound tasks, where the main thread can offload tasks like file I/O or network requests to the system's underlying non-blocking capabilities and continue executing other JavaScript code.

Below is the example code where I have created two routes:

const express = require('express');
const app = express();
const port = 3002;

app.get('/nonblocking', (req, res) => {
  res.send("This is non blocking api");
});
app.get('/blocking', (req, res) => {
    for(let i=0; i<99999999999999; i++) {    
    }
   res.send("This is blocking api");
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});


Here, the route '/nonblocking' is very simple and light route where I am just returning a string in response and it is taking just few millisecond to return the response. But, in the 2nd route '/blocking', I am traversing a very big loop that is taking several minutes. Now, if any user is calling this route, then the main thread gets blocked in processing this request and it is not available to other request and thus even "/nonblocking" routes are also waiting until the "/blocking" routes gets processed completely. To solve, this issue, I have used worker thread in my below code.

Example of Worker Threads

App.js: Here, I am creating a thread:

const express = require('express');
const app = express();
const port = 3002;

app.get('/nonblocking', (req, res) => {
  res.send("This is non blocking api");
});
app.get('/blocking', (req, res) => {
  const { Worker } = require('worker_threads');
  const worker = new Worker('./worker.js',  {workerData: 999999} );
    worker.on('message', (data) => {
      res.send("This is blocking api with data = "+data);
    });
    worker.on('error', (error) => {
      console.log(error);
    });
    worker.on('exit', (code) => {
      if (code !== 0)
        reject(new Error(`Worker stopped with exit code ${code}`));
    })  
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});


Here, the line const worker = new Worker('./worker.js',  {workerData: 999999} );

is basically creating a new thread. This thread is emitting events like message, error and exit. We should listen these event to process the data accordingly.

worker.js : Here, we are basically, defining the logic of the task that this thread must perform and once task is completed then it will publish the resultant data by parentPort.postMessage(data);

const { parentPort, workerData } = require('worker_threads');

function countTo(number) {
    let count=0;
    for(let i=0; i<number; i++) {
        count = count + i;
    }
    return count;
}

const result = countTo(workerData);

parentPort.postMessage(result);

Pros and Cons of Using Worker Threads Pros:

  • Improved Performance for CPU-bound Tasks: Worker threads can significantly improve the performance of CPU-bound tasks by offloading them to separate threads.
  • Parallel Execution: Allows for true parallel execution of JavaScript code, which can lead to more efficient use of multi-core processors.
  • Enhanced Application Responsiveness: By handling heavy computations in worker threads, the main thread remains responsive to I/O operations and user interactions.

Cons:

  • Complexity: Introducing multi-threading can add complexity to the codebase, making it harder to understand and maintain.
  • Shared Memory Concerns: While worker threads can share memory using SharedArrayBuffer, managing shared memory can be tricky and prone to errors.
  • Overhead: Creating and managing worker threads introduces some overhead, which might not be justified for small or simple tasks.
When to Use Worker Threads in Node.js

Worker threads are particularly useful in the following scenarios:
  • CPU-bound Tasks: Any task that requires significant CPU time, such as complex calculations, image processing, or data parsing, can benefit from being executed in worker threads.
  • Parallel Processing: Tasks that can be divided into smaller, independent sub-tasks and executed in parallel will see performance gains.
  • Offloading Intensive Tasks: Tasks that could block the event loop, such as large file processing, can be offloaded to worker threads to keep the main thread responsive.
  • Real-time Applications: Applications that require real-time processing and cannot afford to have the main thread blocked will benefit from using worker threads.

Conclusion

Node.js is fundamentally single-threaded for JavaScript execution, but it can utilize multiple threads for certain operations and with the introduction of worker threads. This dual capability allows developers to handle both I/O-bound and CPU-bound tasks efficiently. By understanding when and how to use worker threads, developers can optimize their Node.js applications for better performance and responsiveness.





Sunday, May 26, 2024

Deploying Your Node.js App on EC2 (Amazon Web Services) with PM2 and NGINX

In this blog, I am going to discuss about how to deploy your Node JS and Express application in AWS EC2 instance. Along with deploying the application I am also setting up the NGINX server and PM2 (Process Manager) library. I have shown all these steps clearly in this Youtube video. Please go through the video to get the complete and clear idea.

NOTEThe URL of this video: https://youtu.be/z0GkpPYwl8o


Steps:

1) Create your free trial AWS login
2) Commit your code to GitHub
3) Create EC2 instance:        
  • Create an EC2 instance. Basically t2-micro ubuntu
  • Connect to your EC2 instance
  • Trigger sudo apt update to install any update if available
4) Install node js version 18:
  • sudo apt install nodejs
  • node -v
  • npm -v
5) Verify if GIT and clone your project :
  • git --version
  • git clone YOUR_PROJECT_REPOSITORY_URL
6) Project configuration:
  • Go inside your project folder: cd youtubeCode
  • Trigger command "npm install" to create the node_module folder
  • Verify node_module folder by command "ls"
7) Install Process Manager(PM2) library at global level
  • sudo npm install -g pm2
  • Then go to your project folder
  • pm2 start app.js
  • pm2 logs
  • pm2 reload all
8) Now you can visit your api in browser: http://IP:Port/routeName
9) Install nginx and configure:
  • sudo apt install nginx
  • sudo vim etc/nginx/sites-available/default andthen add below configs in this default file
server_name 54.206.158.160;
location / {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
  • Press ctrl+c and type :wq! to write and quit
  • Check NGINX config by command "sudo nginx -t". If it is returning success that means your NGINX server is configured properly and you can run your application on your NGINX server.
10) Start your NGINX server: sudo systemctl stop nginx
11) If any changes are being done in NGINX configuration, then you have to restart your NGINX server by command like sudo systemctl start nginx
12) If all steps are being followed properly, then till here, your application will be deployed and running successfully on NGINX server using PM2 library on AWS EC2 instance and now you can access your API just by using your EC2 instance's public IP address. No need to provide the port number. By default NGINX is running on PORT 80 and security group of EC2 instance in AWS is already having inbound rule to allow the traffic from 80 port.

Create AWS RDS DB | Connect localhost with RDS DB

Here, I am trying to create a database in RDS in AWS (Amazon Web Services). Below are the steps that should be followed:

  1. Create a free account in AWS
  2. Go to RDS and create a DB as shown in below video
  3. Utilize the DB URL in API to create database connection.
NOTE: I have described all the steps in my Youtube video. You can follow it on Youtube for better understanding. The URL of this video: https://youtu.be/fj3gCZ9bTOo


Below is the code where I am creating a connection with AWS RDS DB in the file model/index.js. This index.js file is being used in schema file model/user.js. This schema file is being used in service/user.js.


model/index.js - Here, I am establishing connection with AWS RDS DB.

const { Sequelize, DataTypes } = require('sequelize');

// Initialize Sequelize connection
const sequelize = new Sequelize('testdb', 'username', 'password', {
    host: 'testdb.sdjkweurowdsj.ap-southeast-2.rds.amazonaws.com', //It is Dummy hostname
    dialect: 'mysql', // Specify the dialect (in this case, MySQL)
    operationsAliases: false,
    pool: {
      max: 5,
      min: 0,
      acquire: 30000,
      idle: 10000
    }
});

const db = {};
db.sequelize = sequelize;
db.models = {};
db.models.User = require('./user')(sequelize, Sequelize.DataTypes);
module.exports = db;

// Close Sequelize connection when the Node.js process exits
process.on('exit', () => {
    sequelize.close()
      .then(() => {
        console.log('Sequelize connection closed');
      })
      .catch((error) => {
        console.error('Error closing Sequelize connection:', error);
      });
  });
 
  // Handle Ctrl+C or SIGINT signal to gracefully close the connection
  process.on('SIGINT', () => {
    sequelize.close()
      .then(() => {
        console.log('Sequelize connection closed');
        process.exit(0);
      })
      .catch((error) => {
        console.error('Error closing Sequelize connection:', error);
        process.exit(1);
      });
  });
 
  // Handle uncaught exceptions and promise rejections
  process.on('uncaughtException', (error) => {
    console.log(error);
    sequelize.close()
      .then(() => {
        console.log('Sequelize connection closed');
        process.exit(1);
      })
      .catch((closeError) => {
        console.error('Error closing Sequelize connection:', closeError);
        process.exit(1);
      });
  });
 
  process.on('unhandledRejection', (reason, promise) => {
    sequelize.close()
      .then(() => {
        console.log('Sequelize connection closed');
        process.exit(1);
      })
      .catch((closeError) => {
        console.error('Error closing Sequelize connection:', closeError);
        process.exit(1);
      });
  });
 
 

Sequelize Schema file : model/user.js

module.exports = (sequelize, DataTypes) => {
  const UserModel = sequelize.define('User', {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    name: {
      type: DataTypes.STRING,
      allowNull: false
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false
    }
  }, {
    tableName: 'users',
    timestamps: false // If your table doesn't have timestamp fields (createdAt, updatedAt)
  });
  return UserModel;
}


Service file: service/user.js

const {models: {User}} = require('../model');
const getUsers = async function () {
  try {
    const users = await User.findAll();
    return users;
  } catch (error) {
    throw error;
  }
}
const addUsers = async function (userObj) {
  try {
    const insertResult = await User.create(userObj);
    return insertResult;
  } catch (error) {
    throw error;
  }
}
const deleteUser = async function (id) {
  try {
    const deleteResult = await User.destroy({"where": {"id": id}});
    return deleteResult;
  } catch (error) {
    throw error;
  }
}
module.exports = {
  getUsers, addUsers, deleteUser
}










Create your chat bot in 10 min by using CHATGPT



I am going to show here how easily we can develop our chatbot with in few minutes using ChatGPT API. Here, I am using followings:

  1. ReactJs for creating user front end
  2. NodeJs for server api interacting with ChatGPT API
  3. ChatGPT API
NOTE: I have described all the steps in my Youtube video also. You can follow it on Youtube for better understanding. The URL of this video: https://youtu.be/zbZfoT3eXmM


Here, we have to complete 3 tasks:

Task 1: Create your ChatGPT API secret key to be used in your code. You need to follow below steps:
  1. Open the url https://openai.com/ and login here using your Google Id or create your own login Id and password.
  2. Create your ChatGPT secret key by clicking on the "Create new Secret key" in the url given in below screenshot. Copy and paste this key in your notepad file. You will not be able to retrieve it again. 
        


Task2: Create Node Js api: Here, we should follow the below steps:
  1. Install NodeJs and NPM
  2. Create a node js project by command npm init -y
  3. Install express and axios dependencies.
       {
  "name": "chatnode",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Jitendra Kumar Singh",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.4.0",
    "express": "^4.18.2"
  }
}


4. Create a new file server.js file where you will define port, start server and create api for interacting with ChatGPT api

const express = require('express');
const axios = require('axios');

const app = express();
const port = 3001; // Set your desired port number

app.use(function (req, res, next) {
  res.header("Access-Control-Allow-origin", "http://localhost:3000")
  res.setHeader('Access-Control-Allow-Methods', "GET,POST,OPTIONS")
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
  next();
})

// Middleware to parse JSON request bodies
app.use(express.json());

// POST endpoint to handle user queries
app.post('/api/query', async (req, res) => {
  const { query } = req.body;

  try {
    // Make a POST request to the ChatGPT API
    const response = await axios.post('https://api.openai.com/v1/chat/completions', {
      model: 'gpt-3.5-turbo',
      messages: [{ role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: query }],
    }, {
      headers: {
        'Authorization': 'Bearer YOUR_CHATGPT_API_SECRET_KEY', // Replace with your ChatGPT API key
        'Content-Type': 'application/json',
      },
    });

    res.json({ reply: response.data.choices[0].message.content });
  } catch (error) {
    console.error('Error:', error.response.data);
    res.status(500).json({ error: 'An error occurred while processing the request.' });
  }
});

// Start the server
app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});


5) Start your node js server by npm start

Task 3: Create ReactJS user interface:  Here, we need to follow below steps:

  1. In another separate directory, create ReactJs project by using command npx create-react-app your_project_name
  2. Once installation is done, then create dependency of axios. Your final package.json file would be like
{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.4.0",
    "concurrently": "^8.2.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}


3) Then open App.js file inside folder named as src and add below code

import React, { useState } from 'react';
import axios from 'axios';

const App = () => {
  const [query, setQuery] = useState('');
  const [reply, setReply] = useState('');

  const handleSubmit = async (event) => {
    event.preventDefault();

    try {
      const response = await axios.post('http://localhost:3001/api/query', { query });

      setReply(response.data.reply);
    } catch (error) {
      console.error('Error:', error.response.data);
    }
  };

  return (
    <div>
      <h1>My Chatbot</h1>
      <form onSubmit={handleSubmit}>
        <input type="text" value={query} onChange={(e) => setQuery(e.target.value)} />
        <button type="submit">Send</button>
      </form>
      {reply && <p>{reply}</p>}
    </div>
  );
};

export default App;

4) Start your UI react application by using command npm start

So, finally when you start UI and Node Js server then you will get functionality as shown below: