Executing Shell Scripts with NodeJS
Tags: javascript, node, beginners, webdev
Published: 2022-03-08 19:29:51


You might have seen **.sh** files in many popular GitHub repositories. Even some of you might have used shell file to install node's latest version in Ubuntu. What the hell are those? Let's get started

## What is Shell? 🐚 
Shell is a command line tool through which you can interact with the kernel. Through shell commands many crucial things can be done like accessing the hardware resource, spawning child processes and many more.

## What is NodeJS? ☊
Node is a javascript runtime, usually used to create a backend server. Initially JavaScript could only run on the browser. But the curiosity of one man named [Ryan Dahl](https://github.com/ry) changed it. JavaScript is slower than statically typed languages. But V8 engine developed by Google improved the javascript performance.


## Ways to execute shell scripts 🚴‍♂️
You can run shell script by creating a file with .sh extension. bash, zshrc, and more are modifications on top of shell. Unix like systems have bash shell as default. Windows has command shell and powershell.
![shell](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8qy2tm6s4i0kclc8enz.png)

## Why execute shell scripts in Node? 🤔
Say you need to use a library or tool which is only available as CLI. You don't have any npm module to do that specific task. What you would do now? Don't worry I have a solution for you.

## The first time I used it 🐣
In one of the projects I had to access a CLI tool installed on the [docker](https://www.docker.com/) container using Node server. I was using [Cura engine](https://github.com/Ultimaker/CuraEngine) a CLI tool installed in my docker container running ubuntu. The basic function of the CLI tool was to convert STL 3d object to G-code. For those of you who don't know what a G-code file is, it is a code which serves as instructions for a 3d printer to print a model. It contains instructions on how to move and how much material to drop, which material to choose etc.
So in my server I was taking a STL file as multipart data and returning a G-code file as response.


## Let's try a fun example 🚀

First install Node.js if you don't have it already. Let's create a node project.

Install [emoj](https://github.com/sindresorhus/emoj) globally so that we can call it via command line. Create a directory and then change your directory into that. Install [nodemon](https://nodemon.io/) globally as well so we don't have to reload the server manually.

```javascript
npm i -g emoj nodemon
```

Initialise the node project with all default parameters.

```javascript
npm i init -y
```

Create a **server.js** file. The main logic of our code will be in this file.

```console
touch server.js
```

Add an npm script to run the server.
```javascript
  "scripts": {
    "start": "nodemon server.js"
  }
```

Now let's create a node server. Add the code below in server.js file.

```javascript
import express from 'express'
const app = express()

const PORT = 3000;

app.get('/', (req, res) => {
  res.send('hello world!');
});

app.listen(PORT, () => {
  console.log(`server running on port: ${PORT}`);
})
```


Run the server and try **localhost:3000/** in your browser. You will see hello world! if there is no error.

```javascript
  npm start
```

Now let's code the most fun part. We will use exec function that node.js provides.

The function of exec is beautifully described in node.js documentation. I would like to quote it here.

> Spawns a shell then executes the command within that shell, buffering any generated output.

You can check out the node.js documentation to learn more about exec.


```js
const { exec } = require("child_process");
```

Add the fun request in server.js before app.listen.
```javascript
app.get('/fun', async (req, res) => {
  const { commandText } = req.query;
  console.log(req.query)
  await exec(`emoj ${commandText}`, (error, stdout, stderr) => {
    if (error) {
      console.error(`exec error: ${error}`);
      return res.send('some error happened: 😭');
    }

    return res.status(200).send(stdout);
  });
});

```

Now the final server.js file looks like below.

```js
const express = require('express');
const app = express()
const { exec } = require("child_process");

const PORT = 3000;

// app.use(express.urlencoded());

app.get('/', (req, res) => {
  res.send('hello world!');
});

app.get('/fun', async (req, res) => {
  const { commandText } = req.query;
  console.log(req.query)
  await exec(`emoj ${commandText}`, (error, stdout, stderr) => {
    if (error) {
      console.error(`exec error: ${error}`);
      return res.send('some error happened: 😭');
    }

    return res.status(200).send(stdout);
  });
});

app.listen(PORT, () => {
  console.log(`server running on port: ${PORT}`);
});
```

To test our fun route send a get request via any tool like Postman or use browser itself. For postman create a parameter named commandText and pass some cool text.

```
localhost:3000/fun?commandText=winter is coming
```

![Fun route output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/at02a9mt8yorxe5w92ox.png)

## Disclaimer
Passing input arguments taken from user directly to exec function can be dangerous. In a way it is similar to how SQL injection happens. Thanks @antongolub for the feedback.
Consider using following libraries to abstract out risks mentioned above.
[github.com/google/zx](https://github.com/google/zx)
[github.com/shelljs/shelljs](https://github.com/shelljs/shelljs)

## Outro 💚
Congratulations! You just executed shell command from node.js. I hope you found this article useful. Incase you want to connect with me and discuss about anything feel free to connect with me on [LinkedIn](https://linkedin.com/in/aabhas-sao)💕

> If you are an organisation and want a freelance tech content engineer feel free to connect with me