Search

How Is a Node.JS Child Process Launched?

There is a high probability that you have already heard or read somewhere that Node.js is single-threaded. Although it is true, there is some nuance to this statement. In this article let’s quickly look at the threading architecture in Node.js, understand its limitations and also discuss four different techniques through which we could overcome those limitations.The Nuance with Node.js and Threads:JavaScript is a single-threaded language and since Node.js is more of an environment to run JavaScript outside of a browser, we could say that Node.js is also single-threaded.But, it is crucial to understand that while the code we write runs on a single thread with an event loop in the same thread, certain operations performed by some Node libraries that interact with the underlying operating system might use a multi-threaded approach.For instance, the crypto library in Node uses the libuv library internally, which is written in C, and libuv has access to a thread pool. Hence the operations performed by the crypto library may be executed in a multithreaded fashion.The Limitations With Node.js:Because our code runs on a single thread, CPU-intensive actions in our code logic will block the event thread; thereby increasing the server latency and reducing its throughput.An example of a CPU-intensive task could be parsing a huge Excel sheet, manipulating the data from it and writing the results to a new sheet.Typically, Node.js is not the preferred tool to do such jobs. But let’s say that we have some tech stack limitations and can only use Node. Then, we’ll have to find a way to make sure that this computationally heavy process doesn’t stall our thread.Some Workarounds to Tackle the Single-threaded Nature:The most common approach is to resort to multiprocessing using the cluster library in Node. A second common approach is a multi-threading approach using worker threads.In this article, we’ll be looking at various ways to resort to a multiprocessing approach. We won’t be discussing the cluster module but will be having a look at the fork method from the `child_process` module in Node, which the cluster module uses internally to achieve multiprocessing.The `child_process` module makes it possible to spawn subprocesses from the main process. All the spawned subprocesses, by default will have pipes established with the main process for the standard streams namely: `stdout`, `stdin` and `stderr`.The Various APIs for Multiprocessing in Node Child_process module:Node handles multiprocessing through a module named `child_process` and as the name suggests, it spawns child processes from the main process. The following are the four different ways to create child processes using the module:spawn()exec()execFile() andfork()We will briefly discuss how these four methods work and the differences between them.Spawn:Spawn is a function exported by the `child_process` module, so we could use it in our code in the following way:const {spawn} = require('child_process');   const lsCommand = spawn('ls');The spawn method, when invoked, creates a child process and runs the command which is passed as the first parameter to the spawn function in the created process. In the example above, the `ls` command which is used to list all files and directories on a Linux machine, will be run on the new child process.Optionally, spawn also accepts a second parameter which is an array of arguments to the passed command. By default, it will be an empty array.Let’s assume that we need to display the hidden files as well. In this case, we usually pass the `-a` switch to the `ls` command. We can do that through the spawn function as follows:const lsCommand = spawn('ls',['-a']);It is interesting to note that the new child process instance created by spawn implements the EventEmitter API and hence we can attach listeners for different events on various streams such as stdin , stdout and stderr.In the following code snippet, let’s look at how we could attach listeners to the data event on the stdout stream in a process created by the  spawn function:const {spawn} = require('child_process');   const lsCommand = spawn('ls', ['-a']);   console.log(`main process id: ${process.pid}`); console.log(`spawned child process id: ${lsCommand.pid}`);   lsCommand.stdout.on('data',(data)=>console.log(`Output from spawned child process with id ${lsCommand.pid}:\n ${data}`));The execution of the above snippet will be as follows:Exec:The exec function also creates a child process. However, unlike the spawn function, it creates a shell first and then runs the command provided to it as the first argument.The first argument could be a command or a location to a script file that you would like to be executed in the spawned shell in the child process.We could use a callback as a second argument, that buffers the output of the executed command from the standard output (stdout) and the stand error (stderr) streams.Let’s look at a code snippet with exec():const { exec } = require('child_process');   const execCallback = (error, stdout, stderr)=>{   if (error) {     console.error(`exec error: ${error}`);     return;   }   console.log(`standard output: ${stdout}`);   console.log(`standard error: ${stderr}`); }   const catCommand = exec('cat spawn-demo.js | wc -w', execCallback); console.log(`Main Process Id: ${process.pid}`); console.log(`child process Id: ${catCommand.pid}`);As we can see from the code snippet above, since the command we pass to exec will be run on a shell we could use shell pipes `|` to combine multiple commands. In the above code example, we can print the content of spawn-demo.js file and count the number of words in the output.ExecFile:The execFile function differs from exec function in the way that it does not create a shell on which to run the supplied command. Hence, we could use this as an option if we would like to execute some background scripts without blocking our applications thread.Let’s assume that we have a demo bash script file named `versions.sh` as follows:# versions.sh #!/bin/bash echo "printing different software versions" echo "node version:  $(node -v)" echo "npm version: $(npm -v)" echo "script run complete"Now, we can use the execFile function to run this script on a separate child process without creating a shell as follows:const {execFile} = require('child_process');   const versionsScriptRunner = execFile('./versions.sh',(err,stdout)=>{   if(err){     console.log("script file failed to be executed");     return;   }   console.log(stdout); });When we run the above snippet, the output will be as follows:Fork:The fork function is very similar to the spawn function except for the difference that when fork is used, the created child process will have an IPC (Inter-Process Communication) channel established with the parent process. Thus, we could use this method for use cases where we require a bi-directional communication channel between the parent and the child processes. Let’s have a timer file as follows://timer.js       let seconds = 0;     setInterval(()=>{    process.send({seconds:`${++seconds} form pid ${process.pid}`});   },1000);     process.on('message',msg=>{    console.log('from parent process',msg);   });Now, we’ll create a child process with the fork method and run the timer.js file on the forked process as follows:const { fork } = require('child_process');   const forkedProcess = fork('timer.js');   forkedProcess.on('message', (msg) => {   console.log('Message from child', msg); });   forkedProcess.send({ message: `This is from parent pid: ${process.pid}` })The above code snippets demonstrate the IPC channel between the parent and child processes:The Comparison:CriteriaSpawnExecExecFileForkCreation of a new shell on child-process instantiationBy default, a shell is not spawned to execute the given command.By default, a shell is spawned and the given command is executed on it.Doesn’t spawn a shell by default to execute the command.Doesn’t spawn a shell.Option to spawn a shellCan be made to create a shell by passing the option shell: trueCan be made to avoid spawning a shell by passing the option shell: falseCan be made to create a shell by passing the option shell: trueDoes not support the shell option.EfficiencySince the default behavior doesn’t involve shell spawning, it is considered to be more efficient than exec.The default behavior is less efficient as a shell needs to be created first before running the given commands.The default behavior is more efficient than the exec method.Doesn’t involve shell spawning, but spawning a large number of child processes might lead to resource-hogging, as the spawned processes will have their own v8 instance.BehaviorAsynchronous AsynchronousAsynchronousAsynchronousGenerated output handling of the executed commandOutput is streamedOutput is bufferedOutput is bufferedOutput is streamedSynchronous VersionspawnSync()execSync()execFileSync()NAConclusion:In general, if one is going to deal with a resource-intensive task that might potentially block the main thread, it will be a good approach from a performance perspective to offload that task to a separate process to make sure that our main thread is not stalled,  thereby preventing our code from blocking the event loop.If your intention of creating a new process is just to scale the number of running instances of your application to serve increased traffic, looking into the `cluster` module will be helpful. If you’d like to explore a multi-threaded approach rather than a multi-processing approach to deal with computationally intensive tasks in Node.js, you should have a look at another module named `worker_threads`.

How Is a Node.JS Child Process Launched?

4K
How Is a Node.JS Child Process Launched?

There is a high probability that you have already heard or read somewhere that Node.js is single-threaded. Although it is true, there is some nuance to this statement. In this article let’s quickly look at the threading architecture in Node.js, understand its limitations and also discuss four different techniques through which we could overcome those limitations.

The Nuance with Node.js and Threads:

JavaScript is a single-threaded language and since Node.js is more of an environment to run JavaScript outside of a browser, we could say that Node.js is also single-threaded.

But, it is crucial to understand that while the code we write runs on a single thread with an event loop in the same thread, certain operations performed by some Node libraries that interact with the underlying operating system might use a multi-threaded approach.

For instance, the crypto library in Node uses the libuv library internally, which is written in C, and libuv has access to a thread pool. Hence the operations performed by the crypto library may be executed in a multithreaded fashion.

The Limitations With Node.js:

Because our code runs on a single thread, CPU-intensive actions in our code logic will block the event thread; thereby increasing the server latency and reducing its throughput.

An example of a CPU-intensive task could be parsing a huge Excel sheet, manipulating the data from it and writing the results to a new sheet.

Typically, Node.js is not the preferred tool to do such jobs. But let’s say that we have some tech stack limitations and can only use Node. Then, we’ll have to find a way to make sure that this computationally heavy process doesn’t stall our thread.

Some Workarounds to Tackle the Single-threaded Nature:

The most common approach is to resort to multiprocessing using the cluster library in Node. A second common approach is a multi-threading approach using worker threads.

In this article, we’ll be looking at various ways to resort to a multiprocessing approach. We won’t be discussing the cluster module but will be having a look at the fork method from the `child_process` module in Node, which the cluster module uses internally to achieve multiprocessing.

The `child_process` module makes it possible to spawn subprocesses from the main process. All the spawned subprocesses, by default will have pipes established with the main process for the standard streams namely: `stdout`, `stdin` and `stderr`.

The Various APIs for Multiprocessing in Node Child_process module:

Node handles multiprocessing through a module named `child_process` and as the name suggests, it spawns child processes from the main process. The following are the four different ways to create child processes using the module:

  1. spawn()
  2. exec()
  3. execFile() and
  4. fork()

We will briefly discuss how these four methods work and the differences between them.

Spawn:

Spawn is a function exported by the `child_process` module, so we could use it in our code in the following way:

const {spawn} = require('child_process');   
const lsCommand = spawn('ls');

The spawn method, when invoked, creates a child process and runs the command which is passed as the first parameter to the spawn function in the created process. In the example above, the `ls` command which is used to list all files and directories on a Linux machine, will be run on the new child process.

Optionally, spawn also accepts a second parameter which is an array of arguments to the passed command. By default, it will be an empty array.

Let’s assume that we need to display the hidden files as well. In this case, we usually pass the `-a` switch to the `ls` command. We can do that through the spawn function as follows:

const lsCommand = spawn('ls',['-a']);

It is interesting to note that the new child process instance created by spawn implements the EventEmitter API and hence we can attach listeners for different events on various streams such as stdin , stdout and stderr.

In the following code snippet, let’s look at how we could attach listeners to the data event on the stdout stream in a process created by the  spawn function:

const {spawn} = require('child_process'); 
  
const lsCommand = spawn('ls', ['-a']); 
  
console.log(`main process id: ${process.pid}`); 
console.log(`spawned child process id: ${lsCommand.pid}`); 
  
lsCommand.stdout.on('data',(data)=>console.log(`Output from spawned child process with id ${lsCommand.pid}:\n ${data}`));

The execution of the above snippet will be as follows:

How Is a Node.JS Child Process Launched?

Exec:

The exec function also creates a child process. However, unlike the spawn function, it creates a shell first and then runs the command provided to it as the first argument.

The first argument could be a command or a location to a script file that you would like to be executed in the spawned shell in the child process.

We could use a callback as a second argument, that buffers the output of the executed command from the standard output (stdout) and the stand error (stderr) streams.

Let’s look at a code snippet with exec():

const { exec } = require('child_process'); 
  
const execCallback = (error, stdout, stderr)=>{ 
  if (error) { 
    console.error(`exec error: ${error}`); 
    return; 
  } 
  console.log(`standard output: ${stdout}`); 
  console.log(`standard error: ${stderr}`); 
} 
  
const catCommand = exec('cat spawn-demo.js | wc -w', execCallback); 
console.log(`Main Process Id: ${process.pid}`); 
console.log(`child process Id: ${catCommand.pid}`);

As we can see from the code snippet above, since the command we pass to exec will be run on a shell we could use shell pipes `|` to combine multiple commands. In the above code example, we can print the content of spawn-demo.js file and count the number of words in the output.

How Is a Node.JS Child Process Launched?

ExecFile:

The execFile function differs from exec function in the way that it does not create a shell on which to run the supplied command. Hence, we could use this as an option if we would like to execute some background scripts without blocking our applications thread.

Let’s assume that we have a demo bash script file named `versions.sh` as follows:

# versions.sh 
#!/bin/bash 

echo "printing different software versions" 
echo "node version:  $(node -v)" 
echo "npm version: $(npm -v)" 
echo "script run complete"

Now, we can use the execFile function to run this script on a separate child process without creating a shell as follows:

const {execFile} = require('child_process');
 
const versionsScriptRunner = execFile('./versions.sh',(err,stdout)=>{
  if(err){
    console.log("script file failed to be executed");
    return;
  }
  console.log(stdout);
});

When we run the above snippet, the output will be as follows:

How Is a Node.JS Child Process Launched?

Fork:

The fork function is very similar to the spawn function except for the difference that when fork is used, the created child process will have an IPC (Inter-Process Communication) channel established with the parent process. Thus, we could use this method for use cases where we require a bi-directional communication channel between the parent and the child processes. 

Let’s have a timer file as follows:

//timer.js  
  
  let seconds = 0; 
  
  setInterval(()=>{ 
   process.send({seconds:`${++seconds} form pid ${process.pid}`}); 
  },1000); 
  
  process.on('message',msg=>{ 
   console.log('from parent process',msg); 
  });

Now, we’ll create a child process with the fork method and run the timer.js file on the forked process as follows:

const { fork } = require('child_process'); 
  
const forkedProcess = fork('timer.js'); 
  
forkedProcess.on('message', (msg) => { 
  console.log('Message from child', msg); 
}); 
  
forkedProcess.send({ message: `This is from parent pid: ${process.pid}` })

The above code snippets demonstrate the IPC channel between the parent and child processes:

How Is a Node.JS Child Process Launched?The Comparison:

CriteriaSpawnExecExecFileFork
Creation of a new shell on child-process instantiationBy default, a shell is not spawned to execute the given command.By default, a shell is spawned and the given command is executed on it.Doesn’t spawn a shell by default to execute the command.Doesn’t spawn a shell.
Option to spawn a shellCan be made to create a shell by passing the option shell: trueCan be made to avoid spawning a shell by passing the option shell: falseCan be made to create a shell by passing the option shell: trueDoes not support the shell option.
EfficiencySince the default behavior doesn’t involve shell spawning, it is considered to be more efficient than exec.The default behavior is less efficient as a shell needs to be created first before running the given commands.The default behavior is more efficient than the exec method.Doesn’t involve shell spawning, but spawning a large number of child processes might lead to resource-hogging, as the spawned processes will have their own v8 instance.
BehaviorAsynchronous AsynchronousAsynchronousAsynchronous
Generated output handling of the executed commandOutput is streamedOutput is bufferedOutput is bufferedOutput is streamed
Synchronous VersionspawnSync()execSync()execFileSync()NA

Conclusion:

In general, if one is going to deal with a resource-intensive task that might potentially block the main thread, it will be a good approach from a performance perspective to offload that task to a separate process to make sure that our main thread is not stalled,  thereby preventing our code from blocking the event loop.

If your intention of creating a new process is just to scale the number of running instances of your application to serve increased traffic, looking into the `cluster` module will be helpful. 

If you’d like to explore a multi-threaded approach rather than a multi-processing approach to deal with computationally intensive tasks in Node.js, you should have a look at another module named `worker_threads`.

Parthipan

Parthipan Natkunam

Author

Parthipan is a full-stack capable, software engineer with 7 years of industry experience, specialized in front-end engineering. Certified "AWS Developer Associate", who has worked in industries such as Ed-tech, IoT and Cyber security with companies spanning across multiple countries with varied tech stacks. Language agnostic by practice but fluent in TypeScript and JavaScript. Loves open-source and enjoys creating quality software products from scratch.

Join the Discussion

Your email address will not be published. Required fields are marked *

Suggested Blogs

Using Github Actions for Automation

GitHub Actions are a relatively new tool that allows you to configure CI/CD workflows using a configuration file directly in your GitHub repo.Previously, if you wanted to automate your tests, builds, or deployments, you had to use services like Circle CI and Travis or create your own scripts. However, with Actions, you get first-rate support as well as comprehensive gear to automate your workflow.Automation for beginnersAutomation refers to application areas in which human input is minimal. This covers business process automation (BPA), IT automation, personal applications such as home automation, and other similar technologies.CI/CD WorkflowsContinuous Integration and Continuous Deployment are abbreviated as CI/CD. They are both software development approaches that enable teams to collaborate on projects in a timely, efficient, and error-free manner.Continuous Integration is the concept that when team members work on code on multiple git branches, the code is merged to a single working branch, which is subsequently built and tested using automated workflows. This helps to ensure that everyone's code is well-tested and works correctly together.Continuous Deployment takes this a step further by automating the deployment process. Whereas the CI method automates testing and building, Continuous Deployment automates project deployment to an environment. The goal is that once the code has been built and tested, it is in a deployable condition and should be able to be deployed.Introducing GitHub ActionsGitHub Actions allows you to build unique software development lifecycle routines from within your GitHub repository. These workflows are composed of many tasks known as actions, which can be executed automatically in response to particular triggers.This allows you to incorporate Continued Integration (CI) and continuous deployment (CD) capabilities, as well as a variety of other features, directly in your repository.This diagram shows how you can use GitHub Actions to run your software testing scripts automatically. An event initiates the workflow, which contains a job. The job then use steps to govern the sequence in which actions are carried out. These are the commands that will let you automate your software testing.Benefits for developers:Before we dive into the technical details, let's talk about why developers should care about GitHub Actions and what benefits they give:Multi-container testing:By adding support for Docker and docker-compose files to your workflow, GitHub actions enable you to test multi-container setups.Build into GitHub:GitHub Actions is fully integrated with GitHub and so does not require a separate website. This implies that it may be managed with your other repository-related features such as pull requests and issues.Multiple CI templates:GitHub offers a variety of templates for various CI (Continuous Integration) configurations, making it relatively simple to get started. You can also develop your own templates, which you can then post on the GitHub Marketplace as an Action.Great free plan:Actions are absolutely free for any open-source repository and include 2000 free build minutes per month for all of your private repositories, which is comparable to most free CI/CD services. If that is insufficient for your requirements, you can choose another plan or pursue the self-hosted route.Components and Workflows:The components of GitHub Actions that work together to run jobs are listed below:WorkflowsThe workflow is a procedure that you add to your repository. Workflows consist of one or more jobs that can be scheduled or triggered by an event. The workflow can be used to create, test, package, release, or deploy a GitHub project. See "Reusing workflows" for more information on referencing a workflow within another workflow.ActionsActions are individual commands that are integrated into steps to form a job. The simplest movable building piece of a workflow is an action. You can either develop your own actions or use those created by the GitHub community. You must add an action as a step in order to use it in a process.Steps:A step is a standalone task that can execute commands in a job. An action or a shell command can be considered a step. Each step of a task runs on the same runner, allowing the operations in that job to exchange data.Events:An event is a distinct activity that initiates a workflow. When someone submits a commit to a repository, for example, or creates an issue or pull request, the activity can come from GitHub. When an external event happens, you may also utilise the repository dispatch webhook to activate a process. See Events that trigger workflows for a complete list of events that can be used to trigger workflows.Jobs:A job is a sequence of steps that runs on the same runner. A workflow with multiple jobs will run those jobs in parallel by default. You may also set up a pipeline to conduct jobs in a specific order. A workflow, for example, could include two sequential tasks that build and test code, with the test job being reliant on the status of the build job. The test job will not run if the build task fails.Runners:A runner is a server that is running the GitHub Actions runner application. You can either utilise a runner hosted by GitHub or host your own. A runner searches for open jobs, runs one at a time, and sends progress, logs, and results to GitHub. GitHub-hosted runners run on Ubuntu Linux, Microsoft Windows, and macOS, with each task in a workflow running in a separate virtual environment. The runner then waits for open jobs to execute. They run the job's activities after picking it up and send the progress and findings back to GitHub.Creating an action fileThe events, jobs, and steps in GitHub Actions are defined using YAML syntax and have either a .yml or .yaml file extension. These YAML files are kept in a directory called .GitHub/workflows in your code repository.In your repository, you may create an example workflow that executes a sequence of tasks whenever code is pushed. GitHub Actions checks out the pushed code, installs the software dependencies, and runs bats -v in this workflow.Step 1: Create the .github/workflows/ directory in your repository to contain your workflow files.Step 2: Create a new file called learn-github-actions.yml in the .github/workflows/ directory and add the following code.name: learn-github-actions on: [push] jobs:   check-bats-version:     runs-on: ubuntu-latest     steps:       - uses: actions/checkout@v2       - uses: actions/setup-node@v2         with:           node-version: '14'       - run: npm install -g bats       - run: bats -vStep 3: Commit and deploy these changes to your GitHub repository.Your new GitHub Actions workflow file is now installed in your repository and will execute automatically whenever a change is pushed to the repository.This section describes each line of the introduction's example to help you understand how YAML syntax is used to create a workflow file:Name:The name of your workflow as it appears on the Github actions page. If you leave this field blank, the file name is used.On:The on keyword specifies the Github events that cause the workflow to run. A single event, an array of events, or a configuration map that schedules a workflow can be provided.Jobs:A workflow run consists of one or more jobs. Jobs describe the functionality that will be run in the workflow and by default, will run in parallel.check-bats-version:The name of the check-bats-version job that is kept in the jobs section.runs-on:The runs-on keyword allows you to specify the OS (Operating System) that your workflow should run on, such as the most recent version of Ubuntu.steps:All of the stages that run in the check-bats-version task are grouped together. Each item in this section is a distinct action or shell command.uses:The keyword uses instructs the job to fetch version 2 of the community action named actions/checkout@v2. This action checks out your repository and downloads it to the runner, allowing you to execute actions on your code (such as testing tools).Setting up GitHub Actions:Our workflow will look like this:To use GitHub Actions, we must first build a .github/workflows folder. We must develop our workflows within this folder. Let's start by making push.yml. What we desire from our workflow is as follows:Perform these tasks in the specified order on each push:git clone the reporun npm installrun npm lintrun npm testbuild the docker imagelogin to docker hubPush the image to docker hubSo, in order to run each of these tasks within a Docker container, we must create a Dockerfile for each of these actions and run the commands within those containers. This is, of course, time-consuming and error-prone. Because GitHub Actions are code, we can reuse, edit, and fork them just like any other piece of code.Our push.yml file would look like this:on: push name: npm build, lint, test and publish jobs:   build-and-publish:     name: build and publish     runs-on: ubuntu-latest     steps:       - uses: actions/checkout@master       - name: npm install         uses: actions/npm@master         with:           args: install       - name: npm test         uses: actions/npm@master         with:           args: run test       - name: npm lint         uses: actions/npm@master         with:           args: run lint       - name: docker build         uses: actions/docker/cli@master         with:           args: build -t ishanjainijs/github-action-example-node .       - name: docker login         uses: actions/docker/login@master         env:           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}       - name: docker push         uses: actions/docker/cli@master         with:           args: push abhinavdhasmana/github-action-example-nodeSummaryGitHub Actions have matured much in the last few years, but not sufficiently.  With the finest API of any git platform, plus the unique technique of developing actions in JavaScript, all backed up by the world's largest git community, there is no doubt that GitHub Actions has the ability to dominate the whole CI/CD game. But not just yet.There isn't much we can't automate with our capacity to design new custom workflows. GitHub also provides a marketplace where you may look for one.You can even make your own if you want to take it a step further! This allows you to create scripts to configure a workflow to accomplish whatever tasks your project requires.
5009
Using Github Actions for Automation

GitHub Actions are a relatively new tool that allo... Read More

Power Manage Your State Using React’s UseReducer() Hook

Keeping track of how your application data changes over time is referred to as state management in React. You will be able to create dynamic apps that adapt to user input by controlling the state of your application. In React, there are numerous techniques for handling state, including class-based state management and third-party frameworks such as Redux. In this article, you'll learn how to handle state on functional components using Hooks.Hooks are a group of tools that run custom functions when the props of a component change. Developers can use Hooks to produce shorter, more readable code that is easy to share and maintain because this approach of state management does not involve the use of classes. One of the primary distinctions between Hooks and class-based state management is that no single object holds all of the states. Instead, you can divide the state into parts that can be updated independently.If you've been working with React for a while, you're probably familiar with patterns like render props and higher-order components that attempt to tackle this. However, using these patterns requires you to restructure your components, which can be time-consuming and make code more difficult to follow.In React DevTools, you will most certainly encounter a "wrapper hell" of components surrounded by layers of providers, consumers, higher-order components, render props, and other abstractions. While we could filter these out in DevTools, this hints at a larger issue: React requires a better primitive for sharing stateful functionality.Hooks allow you to abstract stateful logic from a component so that it may be tested and reused independently. They allow you to reuse stateful code without having to change the component hierarchy. This makes it simple to distribute Hooks among several components or with the community.Managing state with HooksHooks are functions that allow function components to "hook into" React state and lifecycle features. Hooks do not work within classes; instead, they allow you to use React without them. (While we don't recommend rewriting your existing components overnight, you can begin using Hooks in the new ones if you like.Managing state in React, especially in large apps, was used to mandate the use of third-party frameworks like Redux and MobX. These third-party tools made it easier to update the state of your application in a more predictable and fine-grained manner, but they usually came with additional overhead and a high learning curve.React includes a few built-in Hooks, such as useState. You can even write your own Hooks to reuse stateful behavior across components. We'll start with the built-in Hooks.useStateuseState is a Hook that is invoked within a function component to add some local state to it. This state will be preserved between re-renders by React. useState returns a pair consisting of the current state value and a function that can be used to update it. This function can be called from an event handler or anywhere else. It's comparable to this. setState in a class, however, it does not blend the old and new states.const [state, setState] = useState(initialState);Example 1:Declaring a state variable: class Demo extends React.Component {   constructor(props) {     super(props);     this.state = { count: 0     }; }We don't have this in a function component, thus we can't assign or read this.state. Instead, we invoke the useState Hook from within our component:import React, { useState } from 'react';  function Demo() {   const [count, setCount] = useState(0);   return (           You clicked {count} times       setCount(count + 1)}>         Click me             ); }The useState function declares a "state variable." Our variable in the above example is count. This is a technique to "preserve" some values across function calls — useState is a new way to use the same capabilities in a class that this.state provides. Variables normally "disappear" when a function closes, but React preserves state variables.useState returns a pair of values: the current state and an update function. Because of this, we write const [count, setCount] = useState (). In a class, this is comparable to this. state.count and this.setState as a pair.We declare count as a state variable and set it to 0. Between re-renders, React will remember its current value and return the most recent one to our function. setCount can be used to update the current count.Example 2:function ExampleWithManyStates() {   // Declare multiple state variables!   const [age, setAge] = useState(21);   const [pet, setPet] = useState('dog');   const [todos, setTodos] = useState([{ text: 'Complete assignment' }]);   // ... }The array destructuring technique allows us to give other names to the state variables we declared by invoking useState. The useState API does not support these names. Instead, React considers that if you call useState many times, you will do it in the same sequence on each render.Using the useReducer() hookuseReducer is one of the new Hooks included with React 16.8. It is an alternative to the useState Hook that aids in the management of complex state logic in React applications. When paired with other Hooks such as useContext, useReducer can be an excellent alternative to Redux or MobX — in fact, it can be an outright better solution in some cases.Before we move further, I would like to state that this tutorial is not condemning Redux and MobX, which are frequently the best alternatives for managing the global state in large React apps. However, far too often, React developers rely on third-party state management frameworks when Hooks could have handled their state just as well.Coupled with the difficulty of getting started with a third-party library like Redux and the amount of boilerplate code required, managing state with React Hooks and the Context API is a very appealing option because there is no need to install an external package or add plenty of files and folders to manage global state in our application.Can you replace Redux?Redux is a predictable state container that allows you to create JavaScript apps that act reliably across client, server, and native environments and are simple to test.The state of your application is saved in a store with Redux, and any component can access any state that it requires from this store.One of the most common criticisms towards Redux is that it needs a lot of boilerplate code to set up some fairly simple functions. The inclusion of redux and react-redux expands the project's bundle size, while the arrangement increases the code's complexity.This is not due to the redux developers' fault. Redux is intended to be a universal state management tool that is not limited to React. As a result, adapting it to any particular framework will always require a little more setup than anything particularly intended for that framework.useReducer solves this problem as it is a react hook that provides the fundamental state management features provided by redux without all of the boilerplate code in the setup.This is the (nearly) appropriate alternative for projects that require a more advanced state management system but do not require the extra bells and whistles that come with redux.Because useReducer is specifically intended for React, it is relatively simple to incorporate into React components.Let’s work with useReducer()The useReducer, like the useState Hook, is used to save and update states. It takes as its first parameter a reducer function and as its second parameter the initial state.useReducer provides an array containing the current state value as well as a dispatch function to which you can pass an action and then invoke. This is similar to the Redux pattern, but with a few modifications.const [state, dispatch] = useReducer(reducer, initialArg, init);When you have complex state logic including several sub-values or when the next state is dependent on the prior one, using Reducer is usually preferable to using State. Because you may pass dispatch down instead of callbacks, useReducer allows you to enhance performance for components that generate deep modifications.Example: Increment a numberimport React, { useReducer } from 'react';  function Counter() {   const [sum, dispatch] = useReducer((state, action) => {     return state + action;   }, 0);   return (           {sum}        dispatch(1)}>         Add 1             ); }You can see how hitting the button causes an action with a value of 1 to be executed, which is then added to the current state, and the component to re-render with the new (bigger!) state.Specify the initial stateThere are two methods for initializing the useReducer state. Depending on the application, you can select either one. The most straightforward method is to pass the initial state as a second argument:const [state, dispatch] = useReducer(     reducer,     {count: initialCount}   );React does not employ the Redux-popularized state = initialState argument convention. Because the initial value is sometimes dependent on props, it is given via the Hook call instead. If you really want to imitate the Redux behavior, you can call useReducer(reducer, undefined, reducer) but it's not recommended.SummaryTo recap what we’ve learnt, Hooks were a significant change in React that introduced a new mechanism to share code and update components without the use of classes.  You now have the capabilities to develop complicated projects that respond to users and dynamic data since you can create components using useState and useReducer. You also have a solid base of knowledge from which to investigate more advanced Hooks or design custom Hooks.
4939
Power Manage Your State Using React’s UseRed...

Keeping track of how your application data changes... Read More

How To Use Mongodb Text Search?

MongoDB, one of the top NoSQL databases, is well renowned for its rapid speed, versatile structure, scalability, and excellent indexing capabilities. Before we get into the minutiae, let's look at the bigger picture. When it comes to locating content on the internet, full-text search is a must-have function. When we see the material utilizing the phrases or keywords, the best illustration is a Google search. In this post, we will learn about MongoDB's full-text search capabilities based on text indexes.MongoDB debuted with an experimental feature allowing Full-Text Search via Text Indexes in version 2.4. This feature is now an essential element of the product (and is no longer an experimental feature). In this post, we'll go through the fundamentals of MongoDB's full-text search capabilities.Searching Text - An essential featureConsider a normal Google search to better understand the concepts of full-text search. When we use Google to find content, we enter a string of text, strings, phrases, or keywords, and a number of results are returned. Full-text search in MongoDB enables you to run complicated queries comparable to those you'd run using a search engine. You can look for phrases and stemmed variations on a word, and you can also remove specific "negated" terms from your search results.You can create a text index on any field in the document whose value is a string or an array of strings using MongoDB full-text search. When we construct a text index on a field, MongoDB tokenizes and stems the text content of the indexed field and creates the indexes accordingly.Here are some other circumstances in which we might see a full-text search:Consider searching Wiki for your favorite topic. When you input a search term on Wiki, the search engine returns results for all articles that include the keywords/phrases you entered (even if those keywords were used deep inside the article). The relevancy of these search results is determined by their matched score. Consider a social networking site where a user may conduct a search to find all the posts that contain the term cats in them; or, to be more specific, all the posts that have comments that contain the word cats.Setting up for searchLet us now look at some practical examples to help us understand things better. I'd like you to follow along with me by running the examples in mongo shell. We'll start by creating some example data that we'll use throughout the tutorial, and then we'll go through key ideas.To begin, connect to your MongoDB server: Once you have MongoDB installed on your system, you can use mongo shell to connect with a MongoDB server.Run the mongo command from your command prompt to launch the MongoDB shell. The mongo command, by default, launches a shell linked to a locally installed MongoDB instance running on port 27017.Run the mongo command without any further parameters:>mongoThis will produce a welcome message with information about the server to which the shell is connected, as well as the version of MongoDB that is installed.Congrats, you are in mongo shell.Now run the following commands:>use messageLet's use the following statement to insert some documents.db.message.insert({"subject":"Ishan is having a dog", "content":"Dogs are most loyal pet", "likes": 60, "year":2015, "language":"english"}) db.message.insert({"subject":"Dogs eat cats", "content":"Cats are not evil", "likes": 30, "year":2015, "language":"english"}) db.message.insert({"subject":"Cats eat rats", "content":"Rats like cheese", "likes": 55, "year":2014, "language":"english"})Creating an IndexTo execute a text search, we must first establish a text index on the fields. This can be done on a single or numerous fields. The statement below will generate a text index on a single field.>db.message.createIndex({"subject":"text"})Let's use the following statement to insert some documents.db.message.insert({"subject":"Ishan is having a dog", "content":"Dogs are most loyal pet", "likes": 60, "year":2015, "language":"english"}) db.message.insert({"subject":"Dogs eat cats", "content":"Cats are not evil", "likes": 30, "year":2015, "language":"english"}) db.message.insert({"subject":"Cats eat rats", "content":"Rats like cheese", "likes": 55, "year":2014, "language":"english"})Creating an IndexTo execute a text search, we must first establish a text index on the fields. This can be done on a single or numerous fields. The statement below will generate a text index on a single field.>db.message.createIndex({"subject":"text"})We will generate a text index based on the description and subtitle attributes. In MongoDB, we can only construct one text index per collection. So, using the following line, we will establish a compound text index.db.messages.createIndex({"subject":"text","content":"text"})Using the $text operatorThe $text operator can also be used to search a text index. This operator is used to perform text search operations on a text-indexed collection. This operator tokenizes each search string with whitespace and treats most punctuation as delimiters with the exception of – and \." It performs a logical OR operation on the tokens after tokenizing the search phrase. Use the $meta query operator to sort the generated documents.Syntax:  $text:  { $search: , $language: , $caseSensitive: , $diacriticSensitive: }$search FieldWe'll now try to find documents with the keywords 'dog' in the topic and content boxes. We can use the following sentence to accomplish this.> db.message.find({$text: {$search: "dog"}})Example:> db.message.find({$text: {$search: "dog"}},{ subject: 1, content:1}) This will give the output as:   { "_id" : ObjectId("6176ce6de02fd70a168ad9c6"), "subject" : "Ishan is having a dog", "content" : "Dogs are most loyal pet" } { "_id" : ObjectId("6176ce77e02fd70a168ad9c7"), "subject" : "Dogs eat cats", "content" : "Cats are not evil" }Sorting documents based on search relevanceTextScoreEach page that has the search phrase in the indexed fields receives a score from the $text operator. The score shows a document's relevancy to a specific text search query. The score can be specified as part of the sort() method definition as well as the projection expression. The $meta: "textScore" expression offers information about the $text operation's processing. For more information on retrieving the score for projection or sort, see the $meta projection operator.We're doing a text search, thus we'd like to receive some statistics on how relevant the resulting documents are. To do this, we will use the $meta: "textScore" expression, which offers information about the $text operator's processing. Using the sort command, we will also sort the documents by textScore. A greater textScore indicates a better match.db.messages.find({$text: {$search: "dogs"}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}})This query returns the following documents:{ "_id" : ObjectId("6176b68b750fd1447889f942"), "subject" : "Joe owns a dog", "content" : "Dogs are man's best friend", "likes" : 60, "year" : 2015, "language" : "english", "score" : 1.2916666666666665 } { "_id" : ObjectId("6176b69f750fd1447889f943"), "subject" : "Dogs eat cats and dog eats pigeons too", "content" : "Cats are not evil", "likes" : 30, "year" : 2015, "language" : "english", "score" : 1 }As you can see, the first document gets a score of 1.2916666666666665 (since the keyword dog appears twice in its subject), whereas the second has a score of 1. The query also ordered the returned documents by their score in descending order.Compound Indexing:We will allow compound text indexing on the subject and content fields in our example. Proceed to run the following command in mongo shell:db.messages.createIndex({"subject":"text","content":"text"})This command will not work. Attempting to create a second text index will result in an error message stating that a full-text search index already exists. Why is this the case? The explanation is that text indexes are limited to one text index per collection. As a result, if you want to build another text index, you must delete the old one and establish a new one.db.messages.dropIndex("subject_text")   db.messages.createIndex({"subject":"text","content":"text"})After running the index creation queries listed above, try searching for all pages with the keyword cat.db.messages.find({$text: {$search: "cat"}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}})The above query will give the following output:{ "_id" : ObjectId("6176b69f750fd1447889f943"), "subject" : "Dogs eat cats and dog eats pigeons too", "content" : "Cats are not evil", "likes" : 30, "year" : 2015, "language" : "english", "score" : 1.3333333333333335 } { "_id" : ObjectId("6176b6cb750fd1447889f944"), "subject" : "Cats eat rats", "content" : "Rats do not cook food", "likes" : 55, "year" : 2014, "language" : "english", "score" : 0.6666666666666666 }Indexing the entire documentIn the last example, we created a composite index on the subject and content fields. However, there may be times when you want any text content in your papers to be searchable.Consider storing emails in MongoDB documents, for example. In the case of emails, all fields must be searchable, including Sender, Recipient, Subject, and Body. In such cases, you can use the $** wildcard specifier to index all of your document's string fields.The query would be as follows (make sure you delete the existing index before establishing a new one):db.messages.createIndex({"$**":"text"})This query would create text indexes on any string fields in our documents.Implementing text search in an aggregation pipeline:Text search is supported in the aggregate pipeline via the $text query operator in the $match stage. But the following regulations apply to text search in the aggregation pipeline:The pipeline's initial stage must be the $match stage with a $text. In the stage, a $text operator can only appear once. The $text operator expression is not permitted in $or or $not expressions. By default, the text search does not return matching documents in the order of matching scores. Use the $meta aggregation expression in the $sort stage to sort by descending score.The $text operator assigns a text score to each document that contains the search word in the index field. The score shows the importance of a document in relation to a given text search query.Examples: The following examples are based on a message collection with a text index on the field subject:>use people > db.people.insert({"name":"Ishan","pet":"dog"}) > db.people.insert({"name":"Abhresh","pet":"cat"}) > db.people.insert({"name":"Madan","pet":"cat"}) > db.people.insert({"name":"Sneha","pet":"dog"}) >db.people.find().pretty()Count the number of the document in which the pet value is dog:db.people.aggregate([{$match:{$text:{$search:"dog"}}},{$group:{_id:null,total:{$sum:1}}}])Count the number of the document in which the pet value is Cat:db.people.aggregate([{$match:{$text:{$search:"dog"}}},{$group:{_id:null,total:{$sum:1}}}])SummaryIf you handle string content in MongoDB, you should use full-text search to make your searches more effective and accurate. In this article, we demonstrated how to conduct a basic full-text search on a sample dataset. Full-text search has always been one of MongoDB's most requested capabilities. This article began with an introduction to full-text search before moving on to the fundamentals of generating text indexes. Following that, we looked into compound indexing, wildcard indexing. We also looked at some key concepts including analyzing text indexes, and text search in the aggregation pipeline. In the forthcoming MongoDB versions, we can expect some significant enhancements to this capability.
4787
How To Use Mongodb Text Search?

MongoDB, one of the top NoSQL databases, is well r... Read More