Implement Multi Threading in Nodejs with a real world use-case

In this article, we will see a problem statement and how you can solve that using nodejs multi threading concept.Implement Multi Threading in Nodejs with a real world use-case.

As we all know, Nodejs is a single threaded run time environment. If you are completely new to Node single thread concept. read this article to get a better understanding of it.

Since, Nodejs is single thread. Main constraint that Nodejs developers faced is running CPU intensive tasks in the Node applications.

But Node.js v11 came up with one the important features. that is, worker threads in Nodejs.

What are Worker Threads

worker threads in the Nodejs solves the problem of multi threading. there are some scenarios where you might need to run the task in different thread rather than running it in main thread.

In that case, worker threads takes the task without blocking the main thread of the application.

If you want to know more about the basic implementation of worker threads, refer this article to get better understanding of basics.

Implementation - Problem Statement

Let's see where we might need worker threads in Nodejs with an use-case. Let's say that you are building an web application which has lot of stock videos.

When user uploads the video, you have to add the watermark of your application logo for copyright issues.

let's see how you can implement this scenario using worker threads.

Adding a watermark to video can be implemented using ffmpeg. but the problem in using that in nodejs application is, it is cpu intensive.

If you directly implement that in your application. it will block the main thread and affects the performance.

Solution

So, let's implement that using worker threads.

As always, create a directory and initialize the project using npm init --yes

Install the dependencies for the project.

1 npm i express body-parser ffmpeg hbs multer

express - Nodejs Framework to handle the request and response

body-parser - it handles the request post body.

ffmpeg - This library is used to handle adding watermark to the uploaded video.

hbs - handlebars is a template engine to render the views in express applications.

multer - it is used to handle the file upload in nodejs applications.

Here, we are going to use babel to use ES6 in node application. babel basically compiles the ES6 to javascript.

1 npm i --save-dev @babel/cli @babel/core @babel/node @babel/preset-env dotenv nodemon

create a file called .babelrc and add the following code

1 { 2 "presets" : [ 3 [ 4 "@babel/preset-env" , 5 { 6 "targets" : { 7 "node" : true 8 } 9 } 10 ] 11 ] 12 }

After that, create a directory views and inside that directory, create a file index.hbs and add the following code,

1 <!DOCTYPE html> 2 < html lang = " en " > 3 < head > 4 < meta charset = " UTF-8 " /> 5 < meta name = " viewport " content = " width=device-width, initial-scale=1.0 " /> 6 < meta http-equiv = " X-UA-Compatible " content = " ie=edge " /> 7 < title > Video Watermark </ title > 8 < link 9 rel = " stylesheet " 10 href = " https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css " 11 /> 12 </ head > 13 14 < body > 15 < h1 > Watermarkify </ h1 > 16 17 {{# if error }} 18 < div class = " ui negative message " > 19 < i class = " close icon " > </ i > 20 < div class = " header " > 21 {{error}} 22 </ div > 23 </ div > 24 {{/if}} 25 26 < form 27 method = " POST " 28 class = " ui form " 29 enctype = " multipart/form-data " 30 action = " /upload-video " 31 > 32 < div class = " field " > 33 < label > Upload Video </ label > 34 < input type = " file " name = " ssvideo " accept = " video/* " /> 35 </ div > 36 < button class = " ui button " > upload </ button > 37 </ form > 38 </ body > 39 </ html >

basically, we have an html form which takes video file as an input.

Once, form is submitted, we need to handle the route to get the data in express.

1 import express from "express" 2 import * as bodyParser from "body-parser" 3 import * as path from "path" 4 import multer from "multer" 5 6 require ( "dotenv" ) . config ( ) 7 8 const storage = multer . diskStorage ( { 9 destination : "./uploads/" , 10 filename : function ( req , file , cb ) { 11 cb ( 12 null , 13 file . fieldname + "-" + Date . now ( ) + path . extname ( file . originalname ) 14 ) 15 } , 16 } ) 17 18 const upload = multer ( { dest : "uploads" , storage : storage } ) 19 20 const app = express ( ) 21 22 app . use ( bodyParser . json ( ) ) 23 app . use ( bodyParser . urlencoded ( { extended : false } ) ) 24 25 app . set ( "views" , path . join ( __dirname , "views" ) ) 26 app . set ( "view engine" , "hbs" ) 27 28 app . get ( "/" , ( req , res ) => { 29 res . render ( "index" ) 30 } ) 31 32 app . post ( "/upload-video" , upload . single ( "ssvideo" ) , ( req , res ) => { 33 console . log ( req . file ) 34 } ) 35 36 const PORT = process . env . PORT 37 38 app . listen ( PORT , ( ) => { 39 console . log ( ` Server is running on PORT ${ PORT } ` ) 40 } )

Here, we setup the handlebar template engine along with multer in express application.

After that, we have two routes which handles the default index page and upload video url.

we will get the uploaded file data in /upload-video url, you can process the video in the route.

Now, it is time to implement the worker threads.

Worker Threads - Concepts

There are three main concepts in worker threads. they are Worker, isMainThread and workerData.

Mainly, Worker is used to create a worker thread and isMainThread check if the application is running on main thread and worker data is used to pass the data from main thread to worker thread.

In /upload-video route, check if it is running in main thread, if it is, create a worker thread and pass the uploaded file data to worker thread.

1 app . post ( "/upload-video" , upload . single ( "ssvideo" ) , ( req , res ) => { 2 if ( isMainThread ) { 3 let thread = new Worker ( "./threads/threaderone.js" , { 4 workerData : { 5 file : req . file . path , 6 filename : req . file . filename , 7 watermark_image_url : image_url , 8 } , 9 } ) 10 11 thread . on ( "message" , data => { 12 res . download ( data . file , req . file . filename ) 13 } ) 14 15 thread . on ( "error" , err => { 16 console . error ( "thread" , err ) 17 } ) 18 19 thread . on ( "exit" , code => { 20 if ( code != 0 ) console . error ( ` Worker stopped with exit code ${ code } ` ) 21 } ) 22 } 23 } )

Here, we have three callback functions, they are message,error and exit. to get values from different events.

Creating Worker Threads

create a file called threaderone.js inside directory thread and add the following code.

1 let ffmpeg = require ( "ffmpeg" ) 2 const fs = require ( "fs" ) 3 const { workerData , parentPort } = require ( "worker_threads" ) 4 5 let dest = "/dest/video.mp4" 6 7 try { 8 let process = new ffmpeg ( workerData . file ) 9 10 process . then ( video => { 11 video . fnAddWatermark ( 12 __dirname + "/watermark.png" , 13 __dirname + "/" + workerData . filename , 14 { 15 position : "C" , 16 } , 17 function ( err , file ) { 18 if ( ! err ) { 19 console . log ( "New video file is" + file ) 20 21 parentPort . postMessage ( { status : "Done" , file : file } ) 22 } 23 } 24 ) 25 } ) 26 } catch ( e ) { 27 console . log ( e . code ) 28 console . log ( e . msg ) 29 }

Basically, it gets the workerData from the main thread and do all the process of embedding watermark to the video and post the message to main thread.

Main thread gets the modified video and send it as response to the user.

In this process, main thread doesn't get blocked by the CPU intensive task.

Summary

Complete Source code for the application can be found in the repo

Implement Multi Threading in Nodejs with a real world use-case.

To Read More