Matthew Swensen Principal software engineer and open source enthusiast.

Using npm query for better dependency management

4 min read 1365

NPM Over Green Background

Available on the npm CLI as of v8.16.0, query is a new sub-command that exposes a powerful new way to inspect and understand the dependencies of your Node.js project. With it comes a powerful new query syntax, based on a familiar paradigm: CSS. That’s right, you can now use special CSS selectors with npm query to better understand your project’s dependency tree.

In this article, we’ll discuss why it’s important to inspect dependencies, review npm query examples, dive into the syntax for this new command, and explore different ways to format and manipulate its output.

Jump ahead:

Why inspect dependencies?

First of all, why is any of this useful? Here are some of the reasons folks are wanting to have a clear picture of their dependencies:

  • Security audits: Staying on top of security vulnerabilities (and subsequent patches) is critical to shipping high-quality modern software. Good tooling around dependency inspection makes fixing these issues easier and faster
  • Stability: When bugs in a project’s dependencies cause problems, it’s crucial to be able to quickly identify the affected versions and roll out fixes
  • Bundle size: Powerful dependency inspection tooling can aid software teams in their efforts to ship smaller bundles to end users and improve performance
  • Clarity: Having a better grasp of which software packages are installed and their intended purpose helps engineers make better decisions and write better code

Basic examples

Before diving into the nitty-gritty of the command arguments and CSS-based syntax, here are some basic examples to demonstrate the power and precision this new npm query command provides.

License audit

Suppose you’re writing closed-source, for-profit software and need to ensure that none of your dependencies are released under the GPL license (which requires downstream users to also release their software under the GPL or compatible license). To surface potentially problematic packages, you could use the following query command:

npm query "[license=GPL]"

N.B., this is not intended as legal advice; you should consult an attorney if you have specific questions about software licenses

Post-install script inspection

Some packages run scripts after installation, and you may want to inspect these scripts to ensure that they aren’t doing anything nefarious. With npm query, you can easily find any dependencies that register a postinstall script:

npm query ":attr(scripts, [postinstall])"

See the MDN docs for a refresher on how the CSS :attr() function works.

Syntax

The primary means of selecting specific dependencies is analogous to the CSS ID selector. This command will list all copies of lodash installed:

npm query "#lodash"

And if you want to verify that a particular version, say 4.17.21, is installed, just modify the command like so:

npm query "#[email protected]"

npm query also supports semver ranges, as long as you use the more verbose version of the above command (which uses a name attribute selector and the semver pseudo-selector):

npm query "[name=lodash]:semver(^4)"

Already, we begin to see the flexibility this CSS-based syntax provides. With just a couple more core concepts (outlined below), you’ll be able to bring your prior CSS experience to construct queries that answer highly specific questions about the dependency tree.

Dependency groups

Dependency groups (prod dependencies vs. dev dependencies, for example) are expressed with the familiar CSS class syntax. So here’s how to query for all dev dependencies:

npm query ".dev"

This query returns all transitive dependencies in the dev group as well. But, suppose you’d like to limit the list to just the direct dev dependencies of your project.

You can utilize the :root pseudo-selector, which represents your project, and the CSS child combinator, >, to limit the scope of the query:

npm query ":root > .dev"

Special pseudo-selectors

In addition to the standard CSS pseudo-selectors (such as :not(), :has(), :empty, etc.), npm query adds a few special pseudo-selectors that are specific to querying a dependency tree:

  • :private – Selects dependencies marked as private in their package.json files ("private": true)
  • :deduped – Selects deduped dependencies
  • :overridden – Selects dependencies that have been overridden
  • :extraneous – Selects dependencies that may be left over from previous states as they no longer are depended on by any package in the dependency tree
  • :missing – Selects dependencies that are requested by other packages but are not found on disk
  • :semver(<spec>) – Selects dependencies matching the specified semantic version spec
  • :path(<path>) – Selects dependencies matching the specified path
  • :type(<type>) – Selects dependencies of the given type, such as git (for a git repo), directory (for a local directory), etc.; see this comprehensive list of possible types

Familiar CSS paradigms

With the special pseudo-selectors listed above, the full power of CSS can be leveraged to unlock some really powerful queries deep into the dependency tree. Here’s a refresher on some particularly helpful CSS concepts:

  • Combinators: The > combinator we used previously selects dependencies matching the selector on the right as long as they are direct descendants (children) of the selector on the left. The combinator is similar but operates on all descendants (children, grandchildren, and beyond). The ~ combinator allows for selecting siblings
  • Attribute selectors: Attribute selectors, like [key=value], select dependencies that have an attribute called key that is set to value. This only works for string values in package.json; for more powerful attribute selection, use the :attr() CSS function
  • Advanced selectors: * is a special selector that, just as in CSS, selects all dependencies. Multiple sequences of selectors can be joined together using the , selector, which produces the union of results from the selectors on either side

Formatting and manipulating the output

By default, npm query outputs large swaths of JSON, formatted with newlines and an indentation level of two spaces. This is perfect for sending the data to a JSON file for use by other programs, but it’s not great for people who’d like to read the output in the console.

Usage in the terminal with jq

jq is a popular and fast JSON processor that’s typically used directly in the command line. There are lots of great tutorials and examples online for how to use jq, including the official jq manual, but here is a quick example to demonstrate its power.

Let’s reuse our query from earlier in the article for selecting all dependencies that employ a postinstall script (npm query ":attr(scripts, [postinstall])"), this time displaying only their name, version, and path on disk:

npm query ":attr(scripts, [postinstall])" | jq '.[] | "\(.name)@\(.version) - \(.path)"'

Here’s some sample output:

"[email protected] - /workspaces/proutils.dev/node_modules/esbuild"
"svelte-preproce[email protected] - /workspaces/proutils.dev/node_modules/svelte-preprocess"

Programmatic usage

For even greater control, as well as the ability to run advanced processing and analysis on the results, npm has released a package called Arborist for use within a Node.js program. It features a promise-based API and supports the same CSS selectors as the CLI version. For more details on Arborist, take a look at the official docs.

How to improve dependency management with npm query

In this article, we’ve taken a quick look at the powerful new npm query CLI command. With a carefully crafted query, you can more easily achieve deep insights into the dependency tree.

There are a lot of use cases, including a great list in the npm query documentation. To get your mental wheels turning on the possibilities, here are just a few more use cases phrased as questions that development teams might have about their dependency stack. Also provided are the queries that will help answer those questions:

  • Do I need to run npm install because I’m missing some dependencies my teammates added?
    • npm query "*:missing"
  • Are there any packages that depend on both lodash and underscore?
    • npm query "*:has(> #lodash ~ #underscore)"
  • Which packages are leaf nodes in the dependency tree?
    • npm query "*:not(*:has(> *))"

What other useful queries can you come up with?

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Matthew Swensen Principal software engineer and open source enthusiast.

Leave a Reply