For developers writing backend Java services, npm is the mysterious tool you’ve never used but often hear mentioned. That’s because it’s become the go-to build tool for JavaScript projects, used by over 11 million developers worldwide.

So what?

Not only is JavaScript the most popular programming language (65% of devs use it), it’s infiltrated all areas of technology. Dynamic web pages, backend services, and even desktop applications all run JavaScript now.

Since npm is the gateway into this amazing world, wouldn’t it make sense to know the basics?

In this article, you’ll learn the essentials of what npm is, how it works, and how to set it up in a Java developer-compatible way.

What is npm?

npm is the Node Package Manager.

What’s Node?

Node, short for Node.js, is simply a JavaScript runtime environment. Just like how the JRE is the Java runtime environment.

Node.js contains an engine used to run JavaScript code. You’ve probably heard of Node.js being used on the server, to run backend services powered by JavaScript. But developers are using it for much more than that now.

To run JavaScript code with Node.js, just pass the filename to the command-line tool:

node index.js

Node.js architecture

Node.js contains the same V8 JavaScript engine used by Chrome

After Node.js’s release in 2010, a clever developer, Isaac Schlueter, realised the need for a way to share JavaScript code for reuse. So he invented npm.

npm is a command line tool which downloads packages required by JavaScript projects. There are over 1.3 million packages to choose from, hosted in a registry also called npm. These packages can be imported into a JavaScript file, enabling code reuse and rapid development.

For years, as Node grew, npm became a way people published JavaScript to a shared repository.

CJ Silverio, npm CTO

npm packages are:

  • a bundle of one or more files, normally JavaScript code

  • equivalent of a Java .jar file (known as a library / dependency)

  • specified with their version in a package.json file, which defines what packages a project needs

npm can do a lot more, but managing packages is at it’s core. Later, you’ll see practically how this works.

But first, let’s see if npm has an equivalent in the Java world.

What is the Java equivalent of npm?

Since npm is both a command line tool and a registry, Maven is an obvious equivalent, comprising both of these.

But a more up-to-date toolset would be:

  • Gradle to manage packages, known as dependencies in Java

  • Maven Central repository to store and share packages

Comparing Gradle to the npm command-line tool is of course an over-simplification.

Why?

Java has some important constraints compared to JavaScript, which make building applications more complicated:

  1. source code must be compiled into bytecode

  2. multiple versions of the same dependency cannot be used during execution

The first constraint means that Gradle, unlike npm, has to compile code and handle complexities like Java versions and incremental compilation.

The second constraint means Gradle has to handle dependency version conflicts, and allow the developer to choose one over the other. In the JavaScript world, you can use multiple versions of the same package at the same time, even within the same JavaScript file.

With these differences in mind, both npm and Gradle are still the go-to tools which developers use to build, run, and test applications.

Why should Java developers care about npm?

If, like me, you’ve spent many of the recent years living in a Java bubble, you might be wondering what’s changed.

JavaScript is playing an increasing role in many areas of software, and npm is at the centre of that.

This includes:

  • web browsers and frameworks such as Angular, React, Vue.js

  • backend servers using Node.js

  • mobile apps using frameworks like React Native

  • desktop apps e.g Slack

Slack Desktop, which uses the Electron JavaScript framework

Even if you’re not developing JavaScript applications, more and more tools that backend developers might need to use are written in JavaScript and packaged with npm.

For example, recently I wanted to use the Serverless Framework to deploy some serverless functions and other resources to AWS. I used npm to install this as a global package, so I could easily deploy AWS infrastructure using the serverless deploy command. More on global packages in a moment.

If you feel like much of the recent software technology has flown right by, then getting familiar with npm could be the key to unlocking it. You might even have a chance of finally speaking the same language as frontend developers. ๐Ÿ˜ฎ

Sound good? OK, well let’s get into how it actually works.

How does npm work?

We’ll explore how to use npm with 4 practical examples:

  1. installing npm

  2. initialising a project

  3. installing a package

  4. running JavaScript

  5. installing a global package

1) Installing npm

npm is a command-line tool you install using a version manager or a standalone installer.

Popular version managers include nvm and nodist. I’m using nvm (Node Version Manager) right now, and find it easier to understand than nodist.

It has handy commands for installing, viewing, and selecting different versions.

$ nvm list

  * 18.12.1 (Currently using 64-bit executable)

Node.js and npm are both installed together using these tools. That makes sense because npm runs on Node.js.

A specific version of Node.js (e.g. 18.12.1) is bundled with a specific compatible npm version (e.g. 8.19.2). Check the version with the --version option.

$ node --version
v18.12.1

$ npm --version
8.19.2

To change versions using nvm is as simple as running nvm install <new Node.js version> followed by nvm use <new Node.js version>. Both Node.js and npm will then have the correct version.

$ nvm use 19.3.0
Now using node v19.3.0 (64-bit)

$ node --version
v19.3.0

$ npm --version
9.2.0

2) Initialising a project

To create a new project, run npm init. It asks for the details like package name, version, and description, then generates the important package.json file.

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (new-npm-init-project)
version: (1.0.0)
description:
...

Alternatively, run npm init -y and edit package.json manually.

{
  "name": "new-npm-init-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Don’t worry about the details of this file for now. What’s important is to know that it describes your project and its dependencies, and should be committed into version control.

Project vs. package: technically, in npm everything is a package. A project makes sense to Java developers though, and I’ve seen many JavaScript developers use it too.

3) Installing a package

Installing packages is what we do to add a dependency to a project. It’s like when you add a Java library’s group, name, and version to a pom.xml or build.gradle file.

Rather than editing package.json though, you do that using npm directly. It fetches the latest package version from the npm registry.

Imagine we want to use the JavaScript package chalk, which changes the color of text written to the JavaScript console. Add the package with npm install <package-name>.

$ npm install chalk

added 1 package, and audited 2 packages in 1s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Running this command made 3 changes to our project:

  1. added package to node_modules directory

  2. added dependencies entry in package.json

  3. added package-lock.json file

node_modules directory

A node_modules directory was added, where npm stores downloaded packages, ready for JavaScript code to reference them.

Ours has a single entry for chalk:

node_modules/
โ””โ”€โ”€ chalk
    โ”œโ”€โ”€ license
    โ”œโ”€โ”€ package.json
    โ”œโ”€โ”€ readme.md
    โ””โ”€โ”€ source
        โ”œโ”€โ”€ <source code here>

Chalk also has it’s own package.json. It doesn’t have any dependencies, but if it did they’d be listed here and npm would download them too.

Importantly, node_modules isn’t committed to version control. It should always be dynamically generated based on the package.json file.

Dependencies in package.json

package.json now has a dependencies section, with the latest version of chalk included:

{
  "name": "new-npm-init-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "chalk": "^5.2.0"
  }
}

Imagine we commit the project directory to version control (excluding node_modules) and someone else checks it out. All they need to do to add the correct packages to node_modules is to run npm install (without a package name).

This command downloads any dependencies listed in package.json, and any dependencies of those dependencies, and adds them all to node_modules, ready for use.

package-lock.json file

Finally, the package-lock.json file describes the current state of your node_modules directory.

Did you notice the caret ^ character before the chalk version in package.json? That means that when you run npm install it installs the latest minor update. That could be different from the version number specified e.g. 5.3.0 is a more recent minor version than 5.2.0.

That means one developer’s machine could have a different package version in node_modules to another, based on when they ran npm install.

package-lock.json fixes this problem. You commit the file to version control, and npm honours the entries contained within it whenever you run npm install.

4) Running JavaScript

With our chalk package installed, it’s time to do something with it.

We’ll add an index.js file with this JavaScript code:

import chalk from 'chalk';

console.log(chalk.blue('Hello ocean!'));
console.log(chalk.green('Hello land!'));
  • line 1 imports the chalk package
  • line 3 uses chalk to log blue text
  • line 4 uses chalk to green text

Pretty straightforward!

We have to add one entry to package.json, ย "type": "module", to use the right module system for chalk (ES Modules).

Now run index.js by passing it to the node command.

Awesome! So we successfully installed an npm package and used it in some JavaScript code.

To keep everything within npm, a lot of developers add an entry in package.json under scripts. Here we specify any scripts we want to run via the npm command line.

Add a new script entry to run index.js:

{
  // ...
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...
}

Now we can run the script with npm run start:

5) Installing a global package

We’ve covered in detail how packages work within a specific software project, but npm also has global packages.

Typically, these are standalone tools that provide some utility without being referenced by JavaScript code.

Some examples are:

  • Angular CLI

  • http-server

  • Serverless Framework

Conveniently, npm also makes a command available to run the package directly, without prefixing it with npm. To see this in action, we’ll install http-server, which serves the filesystem on a local web server.

Just run npm install <package-name>, importantly passing the --global or -g flag:

$ npm install -g http-server

added 39 packages, and audited 41 packages in 4s

found 0 vulnerabilities

From any directory, we can now run http-server to serve its contents on localhost:8080.

$ http-server
Starting up http-server, serving ./

See the power of that?

What’s more, since npm 5.2 , we don’t even have to install a package globally. Just run npx <package-name> to execute it directly.

Can I use npm with Java?

If you need to incorporate a JavaScript application into your Java application, you can do that. For example, maybe you need to serve an Angular frontend application from your Java backend server.

Add npm into your build process with the Gradle Plugin for Node.

It’s a plugin you apply in your project’s build.gradle build script like this:

plugins {
  id "com.github.node-gradle.node" version "3.5.0"
}

Now you can run the npmInstall task. The plugin automatically downloads npm and runs it against a local package.json file.

$ ./gradlew npmInstall --console=verbose
> Task :nodeSetup SKIPPED
> Task :npmSetup SKIPPED

> Task :npmInstall

added 1 package, and audited 2 packages in 973ms

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities

BUILD SUCCESSFUL in 3s
1 actionable task: 1 executed

The resulting node_modules directory can then be incorporated into your application as required.

What’s neat is that this plugin uses Gradle’s incremental build feature, so it only runs npm install when package.json has actually changed. Nice!

How can I learn more about npm?

You now know more about npm than 95% of Java developers, and probably most JavaScript developers too. Good work!

To deepen your understanding, I recommend that you:

  1. download and install npm via a Node.js version manager e.g. nvm

  2. install some interesting packages with npm install <package-name> (search at npmjs.com)

  3. write some simple JavaScript which imports those packages. Then execute it.

If you’re a Java developer whose started using npm, I’d love to hear if you’ve managed to wrap your head around it. Just leave a comment below or email me directly.