#100DaysOfMERN - Day 5

Subscribe to my newsletter and never miss my upcoming articles

✏ npm vs. npx

The difference between those two is (in short) that npm is a package manager that you can use to install/deinstall packages locally or globally, but you can't execute them easily from the command line, unless you install them globally. For a locally installed package, you'd have to edit your package.json to include a script:

"scripts": {
    "some-package": "some-package"
  }

Only then can you execute it with:

npm run some-package

With npx however, you can use a package without installing it. npx will get the right package and will temporarily install and invoke it for you to use it from the command line.

npx some-package


Please note that the above information might not be 100% correct. I'm trying to get my head around it, but I think it requires some more experience before I can talk with confidence about what's going on.


In my last post, I started learning more about npx on nodejs.dev/learn, and got a bit distracted by their cowsay package example.

It's used as an example to demonstrate that you don't need to install the package locally to use it, where npx comes in handy, but I still went ahead and installed it. After completion, I got the message "found 1 low severity vulnerability", and that I should run npm audit for more information.

✏ npm audit

Trying that out...

npm audit

... will give you an informative list like this:

  Low             Prototype Pollution

  Package         minimist

  Patched in      >=0.2.1 <1.0.0 || >=1.2.3

  Dependency of   cowsay

  Path            cowsay > optimist > minimist

  More info       https://npmjs.com/advisories/1179

Apparently, the culprit is a package called minimist. The "patched in" part informs you about which versions of the package do not contain the vulnerability (can be read like a comparison expression with an implicit AND):

(>=0.2.1 && <1.0.0) || >=1.2.3

The provided link to npmjs.com gives even more information.

Looking into the relevant parts of the package-lock.json in my cow folder, where the exact versions of the packages are listed:

{
  "name": "cow",
  "dependencies": {
    "cowsay": {
      "version": "1.4.0",
      "requires": {
        "get-stdin": "^5.0.1",
        "optimist": "~0.6.1",
        "string-width": "~2.1.1",
        "strip-eof": "^1.0.0"
      }
    },
    "minimist": {
      "version": "0.0.10"
    },
    "optimist": {
      "version": "0.6.1",
      "requires": {
        "minimist": "~0.0.1",
        "wordwrap": "~0.0.2"
      }
    }
  }
}

I can see that the versions of minimist are indeed outside the range of patched versions. There's also some cryptic symbols like ^ and ~ preceding some version numbers.


✏ Semantic versioning

All Node.js packages (rather, their authors) follow the rules of "semantic versioning". This means that when you've written a package for node and update it at some point, you don't just randomly increase your version number.

Generally, a version number always consists of three digits x.y.z. From the article on semantic versioning:

  • 1st digit: major version
  • 2nd digit: minor version
  • 3rd digit: patch version

You only increase the 1st digit when you make an update that includes incompatible API changes. If the update is backward-compatible, you increase the 2nd digit. The 3rd digit is reserved for backward-compatible bug fixes.

This allows developers to set certain rules in the package-lock.json, to make sure their code doesn't break when they run npm update, and there's an incompatible change in one of their dependencies.

Update rules

If there's no additional symbol in front of the version number, it means that only that exact version should be used.

A ~ means that npm will only update to patch releases (changes in the 3rd digit).

A ^ means that all patches and minor updates are accepted (changes in the 3rd and 2nd digit).

Looking at my package-lock.json from above, the version of the minimist package is 0.0.10. The optimist package requires a minimist version of ~0.0.1.

With that configuration, the minimist packages are outside the range of versions that are free from vulnerabilities. So I went ahead and changed both to ^0.0.10 and ^0.0.1, which allows updates up to the highest 2nd and 3rd digit, but no major updates. For cowsay, this means that the highest acceptable version is 0.2.1 (full list of versions), which doesn't contain the vulnerability.

Running npm update with that new package-lock.json will give npm permission to update the minimist package, and running npm audit again after that shows that there's indeed 0 vulnerabilities now. And the cow still speaks without errors.

Reading the version history

The version history of the minimist package tells me a bit more about version "policy", so I'd like to spend some words on this:

minimist-versions-history.jpg

You can see that the first unaffected version is 1.2.3 from 10 months ago. That version got two patches, and the latest version is 1.2.5.

You can also see that they added a version 0.2.1. Version 0.2.0 is from 7 years ago, and was the last version before the major update to 1.0.0. This tells me that after becoming aware of the vulnerability, the developers of the minimist package took the time and wrote a patched version for the long outdated 0.2.0 version, to make sure that everyone who had set an update rule that excludes major updates to 1.x.x would still have access to a vulnerability-free version of their package.

I find that quite reassuring, the whole system of node.js packages depends on developers being responsible and following the rules of semantic versioning. It's one of those tiny matches that the node skyscraper is built upon.


✏ Recap

I've learned

  • npm is a tool for managing packages
  • npx is a tool for executing packages
  • how to use npm audit to check if any of my packages has vulnerabilities or is outdated
  • the benefits of "semantic versioning" (and how important it is that everyone agrees to follow its rules)
  • how to edit the specified versions (with optional symbols) in the package-lock.json, to indicate which rules I want to apply when I run npm update

✏ Next:

  • diving deeper into Node.js: event loop and call stack

✏ Thanks for reading!

I do my best to thoroughly research the things I learn, but if you find any errors or have additions, please leave a comment below, or @ me on Twitter. If you liked this post, I invite you to subsribe to my newsletter. Until next time 👋

Comments (2)

Anand Baraik's photo

Loving this series. Very informative.

jsdisco's photo

Hey there, happy to hear that, it's always great if someone else finds value too in whatever you produce.