Running scripts with npm

npm-script

Most people are aware that is is possible to define scripts in package.json which can be run with npm start or npm test, but npm scripts can do a lot more than simply start servers and run tests.

Here is a typical package.json configuration.

// package.json
// Define start and test targets
{
  "name": "death-clock",
  "version": "1.0.0",
  "scripts": {
    "start": "node server.js",
    "test": "mocha --reporter spec test"
  },
  "devDependencies": {
    "mocha": "^1.17.1"
  }
}
// I am using comments in JSON files for clarity.
// Comments won't work in real JSON files.

start, actually defaults to node server.js, so the above declaration is redundant. In order for the test command to work with mocha, I also need to include it in the devDependencies section (it works in the dependencies section also, but since it is not needed in production it is better to declare it here).

The reason the above test command, mocha --reporter spec test, works is because npm looks for binaries inside node_modules/.bin and when mocha was installed it installed mocha into this directory.

The code that describes what will be installed into the bin directory is defined in mocha's package.json and it looks like this:

// Macha package.json
{
  "name": "mocha",
  ...
  "bin": {
    "mocha": "./bin/mocha",
    "_mocha": "./bin/_mocha"
  },
  ...
}

As we can see in the above declaration, mocha has two binaries, mocha and _mocha.

Many packages have a bin section, declaring scripts that can be called from npm similar to mocha. To find out what binaries we have in our project we can run ls node_modules/.bin

# Scripts availble in one of my projects
$ ls node_modules/.bin
_mocha      browserify  envify      jshint
jsx         lessc       lesswatcher mocha
nodemon     uglifyjs    watchify

Invoking Commands

Both start and test are special values and can be invoked directly.

# Run script declared by "start"
$ npm start
$ npm run start

# Run script declared by "test"
$ npm test
$ npm run test

All other values will have to be invoked by npm run. npm run is actually a shortcut of npm run-script.

{
  ...
  "scripts": {
    // watch-test starts a mocha watcher that listens for changes
    "watch-test": "mocha --watch --reporter spec test"
  },
}

The above code must be invoked with npm run watch-test, npm watch-test will fail.

Running Binaries Directly

All the above examples consists of running scripts that are declared in package.json but this is not required. Any of the commands in node_modules/.bin can be invoked with npm run. This means that I can invoke mocha by running npm run mocha directly instead of running it with mocha test.

Code Completion

With a lot of modules providing commands it can be difficult to remember what all of them are. Wouldn't it be nice if we could have some command completion to help us out? It turns out we can! npm follows the superb practice of providing its own command completion. By running the command npm completion we get a completion script that we can source to get completion for all the normal npm commands including completion for npm run. Awesome!

I usually put each of my completion script into their own file which I invoke from .bashrc.

# npm_completion.sh
. <(npm completion)

# Some output from one of my projects
$ npm run 
nodemon                  browserify               build
build-js                 build-less               start
jshint                   test                     deploy
less                     uglify-js                express
mocha                    watch                    watch-js
watch-less               watch-server

Pretty cool!

Combining Commands

The above features gets us a long way but sometimes we want to do more than one thing at a time. It turns out that npm supports this too. npm runs the scripts by passing the line to sh. This allows us to combine commands just as we can do on the command line.

Piping

Lets say that I want to use browserify to pack my Javascript files into a bundle and then I want to minify the bundle with uglifyjs. I can do this by piping (|) the output from browserify into uglifyjs. Simple as pie!

  //package.json
  // Reactify tells browserify to handle facebooks extended React syntax
  "scripts": {
    "build-js": "browserify -t reactify app/js/main.js | uglifyjs -mc > static/bundle.js"
  },
  // Added the needed dependencies
  "devDependencies": {
    "browserify": "^3.14.0",
    "reactify": "^0.5.1",
    "uglify-js": "^2.4.8"
  }

Anding

Another use case for running commands is to run a command only if the previous command is successful. This is easily done with and (&&). Or (||), naturally, also works.

  "scripts": {
    // Run build-less only if build-less succeeds
    "build": "npm run build-js && npm run build-less",
    ...
    "build-js": "browserify -t reactify app/js/main.js | uglifyjs -mc > static/bundle.js",
    "build-less": "lessc app/less/main.less static/main.css"
  }

Here I run two scripts declared in my package.json in combination with the command build. Running scripts from other scripts is different from running binaries, they have to prefixed with npm run.

Concurrent

Sometimes it is also nice to be able to run multiple commands at the concurrently. This is easily done by using &amp; to run them as background jobs.

  "scripts": {
    // Run watch-js, watch-less and watch-server concurrently
    "watch": "npm run watch-js & npm run watch-less & npm run watch-server",
    "watch-js": "watchify app/js/main.js -t reactify -o static/bundle.js -dv",
    "watch-less": "nodemon --watch app/less/*.less --ext less --exec 'npm run build-less'",
    "watch-server": "nodemon --ignore app --ignore static server.js"
  },
  // Add required dependencies
  "devDependencies": {
    "watchify": "^0.6.2",
    "nodemon": "^1.0.15"
  }

The above scripts contain a few interesting things. First of all watch uses &amp; to run three watch jobs concurrently. When the command is killed, by pressing Ctrl-C, all the jobs are killed, since they are all run with the same parent process.

watchify is a way to run browserify in watch mode. watch-server uses nodemon in the standard way and restarts the server whenever a relevant file has changed.

watch-less users nodemon in a less well-known way. It runs a script when any of the less-files changes and compiles them into CSS by running npm run build-less. Please note that the option --ext less is required for this to work. --exec is the option that allows nodemon to run external commands.

Complex Scripts

For more complex scripts I prefer to write them in Bash, but I usually include a declaration in package.json to run the command. Here, for example, is a small script that deploys the compiled assets to Heroku by adding them to a deploy branch and pushing that branch to Heroku.

#!/bin/bash

set -o errexit # Exit on error

git stash save 'Before deploy' # Stash all changes before deploy
git checkout deploy
git merge master --no-edit # Merge in the master branch without prompting
npm run build # Generate the bundled Javascript and CSS
if $(git commit -am Deploy); then # Commit the changes, if any
  echo 'Changes Committed'
fi
git push heroku deploy:master # Deploy to Heroku
git checkout master # Checkout master again
git stash pop # And restore the changes

Add the script to package.json so that it can be run with npm run deploy.

  "scripts": {
    "deploy": "./bin/deploy.sh"
  },


Conclusion

npm is a lot more than a package manager for Node. By configuring it properly I can handle most of my scripting needs.

Configuring start and test also sets me up for integration with SaaS providers such as Heroku and TravisCI. Another good reason to do it.

This Post Has 33 Comments

  1. Henrik Ravn

    Also, if you specify a “postinstall” script, this will be run automatically when you’ve run ‘npm install’.

    Useful for running bower install, for example.

    1. Anders Janmyr

      Thanks, I’m glad you liked it.
      I have updated as you suggested. Thanks!

  2. George Ivanov

    Great post with great examples. Very informative!

  3. Anders Janmyr

    @George, thanks for letting me know, I’m glad you liked it. :)

  4. shruthi

    Hello Anders,

    Is it possible to run scripts from a public url

    something like
    “start”: “node http://mysitedomain/index.js
    },

    The nodejs is installed in a different server. I need to run the indes.js from a different server

    Thanks in advance

    1. Anders Janmyr

      Hi shruthi,

      You can run an external script by curling it (assuming curl is installed) and piping it into node.
      Something like this:

      $ curl http://mysitedomain/index.js | node
      

      This should work as an npm script.

      "start": "curl http://mysitedomain/index.js | node"
      

      This will only work for a standalone script since it cannot download any dependencies.

  5. shruthi

    Hello Anders,

    I installed curl using ” npm install curl” and then tried with
    “start”: “curl http://117.240.88.103/myfolder/index.js | node”
    },

    I am getting error-“Failed to exec start script”.

    Is there anything wrong here

    Thanks
    Shruthi

    1. Anders Janmyr

      @shrutni, you need to install curl as a system command.
      Depending on your operating system it may already be installed.
      Try running curl --version at the command line.

      If it works you will see something like

      curl --version
      curl 7.37.1 (x86_64-apple-darwin14.0) libcurl/7.37.1 SecureTransport zlib/1.2.5
      Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smtp smtps telnet tftp
      Features: AsynchDNS GSS-Negotiate IPv6 Largefile NTLM NTLM_WB SSL libz
      

      Otherwise install with apt-get on Linux and brew on OSX.

      Anders

      1. Gavin

        This idea of running an external command with curl seems messy (and a bit scary). A npm package to make this easier and safer seems like it wouldn’t be a bad idea. Something like:

        “start”: “npm run-external http://1.2.3.4/myfolder/index.js

  6. Ed

    Excellent article, thank you so much for taking the time to write this up. I won’t be using Grunt, Gulp, or any of that nonsense anytime soon!

    1. Anders Janmyr

      @Ed, I’m glad you liked it.

  7. Ashwini Kumar

    Nice insight of how the script tag work with NPM and presented in an way easy to understand. Thanks.

    1. Anders Janmyr

      @Ashwini, I’m glad you liked it, thanks for letting me know.

  8. Kevin

    Awesome! I didn’t know a lot of this stuff was possible with NPM. You’ve just greatly simplified my understanding of how to deploy node apps! Great concise article. Thanks very much! :)

    1. Anders Janmyr

      Thanks! I’m glad you liked it.

  9. Greg

    Very helpful article, thank you!

  10. Jereme

    Excellent article. This is better than using gulp or grunt. I should have seen this before :)

  11. arieljake

    great article. thanks.

  12. Daniel

    Great tutorial.

    I have problems to following the example in Code Complete.
    After I run $ . <(npm completion), the only thing I see through $ npm run TAB is listed directories.

    What did I miss?

    1. Anders Janmyr

      It seems that the completion only works with the scripts in package.json and not the bin
      files anymore. Try to add a few scripts and you will get completion.

  13. Bo Chen

    Haven’t seen good instructions like this for a while!!!

  14. ky lee

    Good job !! your article is so helpful to me.

    I have soem question about “Running Binaries Directly”
    When i run “npm run mocha” directly, it just produce ” missing script: ~~~~”
    does it work??

    1. Anders Janmyr

      You’re right, it doesn’t work anymore. I usually add ./node_modules/.bin to my PATH.

  15. leroy

    Great article and very informative

  16. Sean

    Still a very useful article, many thanks for sharing!

  17. Javier

    It is because of articles like these that actually made me create a tool that works like npm-scripts (kind of), but tries to address its cons :D
    https://www.npmjs.com/package/makfy
    Still in its infancy, but I’m open to suggestions.

  18. Eric

    I had hoped that the “bin” option, defined in a submodule, would make install a local command in my main module. Apparently, bin only works for modules that are installed globally. Is that true?

    1. Anders Janmyr

      Eric, I usually add ./node_modules/.bin to my PATH, then it works for locally installed modules too.

  19. Alex

    Thanks! I’m learning how to use the “scripts” section of package.json as a new user to Node.js and now I understand why I can run “npm test” directly but not other scripts I add into package.json.

  20. Tamanna

    Thanks a Lot……. i ws searching this frm so long

  21. Daniel Metcalfe

    This was an easy read for me because you didn’t assume any knowledge. Very helpful, thank you.

  22. nick

    Very very nice article!!

  23. Roralee

    After 8 hours of banging my head against a wall trying to figure out a webpack config, this wonderfully succinct page showing “andify” solved my problem in less than 10min. Thus proving once again, experience is knowing what to find when.

Leave a Reply