Click here to Skip to main content
15,868,016 members
Articles / Web Development / Node.js

Build Your Own nodemon in Few Lines of Code

Rate me:
Please Sign up or sign in to vote.
4.88/5 (4 votes)
11 Feb 2023CPOL5 min read 21K   25   8  
Monitor for any changes in your Node.js application and automatically restart the server
This tip shows how to build your own nodemon in just a few lines of code.

Table of Contents

  1. Introduction
  2. Setting up the Project
  3. Server
  4. Watcher
  5. Bringing the Pieces Together
  6. References
  7. History

Introduction

When working on Node.js applications, you often need to restart your server after some changes. Well, this would be annoying if you have to manually restart your server each time you do some modifications in your source code. Thankfully, there are some utilities available that let you automatically restart your server when changes are detected. And the most famous one is nodemon. nodemon is a tool that helps develop Node.js based applications by automatically restarting the node application when file changes in the directory are detected. The purpose of this tip is not to reinvent the wheel, but to show you how you can create your own utility that monitors for any changes in your Node.js application and automatically restart the server in just a few lines of code.

Setting up the Project

The first step will be to initialize our Node.js project:

Shell
npm init

Then, we will have to update package.json in order to add support of ES6 by setting module as type:

JavaScript
{
  "name": "watcher",
  "type": "module",
  "version": "1.0.0",
  "author": "Akram El Assas"
}

Then, we will install development dependencies:

Shell
npm i -D @types/node
  • @types/node: To have autocomplete in Visual Studio Code

Server

We will create a simple web server server.js as follows:

JavaScript
import { createServer } from 'http'

const PORT = 8888

createServer((_, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' })
    res.write('Hello World!')
    res.end()
}).listen(PORT)

console.log('HTTP server is running on Port', PORT)

Then, we will create a watcher in order to restart the server each time changes are detected on the parent folder of the server and in its subfolders through the following command:

Shell
node watcher.js server.js

Watcher

The watcher will restart the server each time changes are detected.

First and foremost, we'll need to retrieve the command-line arguments. In Node.js, we can have access to command-line arguments through process.argv. The process.argv property returns an array containing the command-line arguments passed when the Node.js process was launched. The first element will be execPath. The second element will be the path to the JavaScript file being executed. The remaining elements will be any additional command-line arguments.

If we run the following command:

Shell
node watcher.js server.js

process.argv will be as follows:

JavaScript
[
  'C:\\Program Files\\nodejs\\node.exe',
  'C:\\dev\\watcher\\src\\watcher.js',
  'server.js'
]

The first element is the path to Node.js executable. The second element is the path to watcher.js. And the last element is server.js. Thus, we can start our code by declaring the first and third elements as follows:

JavaScript
const [node, _, file] = process.argv

We then need to create a function that starts a child process that launches Node.js with the specified file as argument which in our case is server.js. To do so, we will use spawn method from child_process module. The child_process.spawn() method spawns a new process using the given command, with command-line arguments in args. The advantages of using spawn method is that we can redirect stdout and stderr of the child process to the parent process using pipe method. pipe method is used to attach a writable stream to a readable stream so that it consequently switches into flowing mode and then pushes all the data that it has to the attached writable stream. The source code of our function will look like follows:

JavaScript
import { spawn } from 'child_process'

const [node, _, file] = process.argv

const spawnNode = () => {
    const childProcess = spawn(node, [file])
    childProcess.stdout.pipe(process.stdout)
    childProcess.stderr.pipe(process.stderr)

    childProcess.on('close', (code) => {
        if (code !== null) {
            process.exit(code)
        }
    })

    return childProcess
}

First of all, we spawn a child Node.js process with the given file argument. Then, we redirect stdout and stderr of the child process to the parent process using pipe method. Then, when the child process is closed, we exit the parent process with the same exit code. The process.exit() method instructs Node.js to terminate the process synchronously with an exit status of code. If code is omitted, exit uses either the success code 0 or the value of process.exitCode if it has been set. Node.js will not terminate until all the exit event listeners are called. And finally, we return the child process.

Now, we need to detect changes on the parent folder of the file and its subfolders. And each time a change related to a JavaScript file is detected, we will kill the child process and spawn the child process again. To do so, we will use watch method from fs/promises module. The fs/promises.watch() method returns an async iterator that watches for changes on filename, where filename is either a file or a directory. We will create a watcher on the parent folder of the file. Then, we will iterate through watcher. We will ignore node_modules folder and each time a change is detected on a JavaScript file, we will kill the child process and spawn it again as follows:

JavaScript
let childProcess = spawnNode()
const watcher = watch(dirname(file), { recursive: true })
for await (const event of watcher) {
    if (
        !event.filename.includes('node_modules') &&
        event.filename.endsWith('.js')
    ) {
        childProcess.kill('SIGKILL')
        childProcess = spawnNode()
    }
}

The subprocess.kill() method sends a signal to the child process. If no argument is given, the process will be sent the SIGTERM signal. SIGKILL signal cannot be caught, blocked, or ignored and forces the child process to stop. See signal(7) for a list of available signals.

That's it! We have finished our own nodemon in just a few lines of code.

Last but not least, we need to add the start and dev scripts to our package.json as follows:

JavaScript
{
  "name": "watcher",
  "type": "module",
  "version": "1.0.0",
  "scripts": {
    "start": "node server.js",
    "dev": "node watcher.js server.js"
  },
  "author": "Akram El Assas",
  "devDependencies": {
    "@types/node": "^18.11.17"
  }
}

In order to start our application, just type the following command:

Shell
npm run dev

Now if we run our application and make changes to server.js, the server will be restarted automatically. We no longer need to stop and start the server manually.

Bringing the Pieces Together

We set up our own nodemon in just a few lines of code. Now, The whole source code of our watcher.js looks like follows:

JavaScript
import { spawn } from 'child_process'
import { watch } from 'fs/promises'
import { dirname } from 'path'

const [node, _, file] = process.argv

const spawnNode = () => {
    const childProcess = spawn(node, [file])
    childProcess.stdout.pipe(process.stdout)
    childProcess.stderr.pipe(process.stderr)

    childProcess.on('close', (code) => {
        if (code !== null) {
            process.exit(code)
        }
    })

    return childProcess
}

let childProcess = spawnNode()
const watcher = watch(dirname(file), { recursive: true })
for await (const event of watcher) {
    if (
        !event.filename.includes('node_modules') &&
        event.filename.endsWith('.js')
    ) {
        childProcess.kill('SIGKILL')
        childProcess = spawnNode()
    }
}

This is just a simple example but you can imagine other situations where you monitor changes on video files and each time a change is detected, a conversion child process (ffmpeg) is launched.

You can also implement other options such as:

  • Ignoring specific files or directories
  • Watching specific directories
  • Monitoring multiple directories
  • Specifying extension watch list
  • Delaying restarting
  • Running executables other than Node.js such as Python, Ruby, make, etc.
  • and so on...

References

History

  • 26th December, 2022 - Initial release
  • 11th February, 2023 - Updates in content

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
Morocco Morocco
I build innovative and scalable solutions for digital media. With several years of software engineering experience, I have a strong background in web, mobile and desktop development, as well as media asset management and digital asset management systems.

My strength lies in the development of innovative solutions and the ability to adapt them to different industries looking to streamline or automate their work process or data management.

I am passionate about learning new technologies and frameworks and applying them to solve complex and challenging problems. I am proficient in working with Node.js, React, React Native, TypeScript, C# and .NET among other languages and tools. My ultimate aim is to deliver high-quality software products that meet the requirements and expectations of our customers.

Open-source projects:

- Wexflow: .NET Workflow Engine and Automation Platform
- BookCars: Car Rental Platform with Mobile App
- Movin' In: Rental Property Management Platform with Mobile App
- Wexstream: Video Conferencing Platform
- wexCommerce: eCommerce Platform on Next.js

If you'd like to discuss any sort of opportunity, feel free to contact me through GitHub or LinkedIn.

Comments and Discussions

 
-- There are no messages in this forum --