DEV Community

Chandelier Axel
Chandelier Axel

Posted on • Updated on

The NPM guide I would have loved as a beginner

As a junior developer, I struggled a lot with NPM.
Do you remember copy/pasting all the npm commands without knowing what they do ? Or the first time you freaked out while opening the package-lock.json ? What if I tell you that there's a bugs property that you can set in your package.json ?

No more fear is allowed from that point onward, we're going to learn together the basics of NPM.

Disclaimer - The article will follow my process of re-learning from scracth. Feel free to skip to specific parts if you don't want to read what NPM means, etc.

Table of contents

A little background

Let's start with the basics, what does NPM even mean ? It stand for Node Package Manager, and as the name implies, it's responsible for managing your packages within your Node application.

Now considered as a major piece of Javascript ecosystem, NPM offer an easy way to manage all the external dependencies we'll need to use in our project with a pretty simple command : npm install .

I will skip the installation of NPM, the Node website will explain it to you properly, and is not the core of this article.

I'll jump straight to the npm commands. Let's start.

Initialization

When I typed my first npm commands, I had absolutely no idea what was going on, despite being the core of NPM. Let's see it in detail.

First of all, we need to create a node-based application, and this is as easy as running the following command.

    npm init
Enter fullscreen mode Exit fullscreen mode

We'll be prompted a few questions about our project, such as the project name, the link to your Git repository, ect. But for now, let's just skip and press Enter.

Wait, use.

    npm init -y
Enter fullscreen mode Exit fullscreen mode

Amazing, we skipped all the questions.

So, we now have a package.json filled with some informations.

{
  "name": "your_directory_name",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
Enter fullscreen mode Exit fullscreen mode

This file can be considered as the ID Card of our application. It contains its name, the current version, a tiny description, some keywords to help other people finding it, and a bunch of other stuff.

(For a complete list of the properties, including the 'bugs' keyword we talked about, please refer to the official documentation).

You're also free to update it whenever you want, as long as you respect the JSON format and use the correct properties.

Next, we want to start our application, right ?

Scripts

Let's look at some examples. I'll assume you have worked at least once with either an Express-based application, or one of the popular JS front-end frameworks (such as React, Angular or Vue).

This scripts property give you the power to customize npm commands to use within your application.

Wait a second.. Is that the place were the npm start I use everyday is defined ? Exactly.

"scripts": {
    "start": "node index.js"
  }
Enter fullscreen mode Exit fullscreen mode

You can specify any valid shells commands here, and create as much entries as you want nor need. You can even combine them !

"scripts": {
    ...,
    "stuffA:" : "...",
    "stuffB": "...",
    "together" : "npm run stuffA & npm run stuffB"
  }
Enter fullscreen mode Exit fullscreen mode

(Beware, this will run your scrips in parallel. To run concurrently, either replace the "&" by "&&", or look at the Concurrently package).

Now, npm run together ! And yes, this is not a typo, you need to write the run keyword. In fact, even the npm start command is launched as npm run start behind the scenes. (This ability is shared with a couple other keywords, such as install, test..)

You still there ? Nice, let's keep digging and unleash the full power of NPM by adding some dependencies !

Manage dependencies

Nowadays, an npm package already exists for pretty much anything. It would be a shame not to use them, and to rebuild the wheel everytime.

This is probably the biggest role of the package.json, it'll keep track of all the dependencies within your project, including the versions.

    npm install <package_name>
Enter fullscreen mode Exit fullscreen mode

This command will download all the files needed and install them into a brand new node_modules folder.
This folder will become bigger and badder as you'll install more and more packages (and the packages themselves most likely depend on others packages, which will be installed too).

Please don't do the same mistake as I did, and prevent committing this folder to your repository !

//.gitignore file at your project root

node_modules
Enter fullscreen mode Exit fullscreen mode

Dependencies & DevDependencies

Meanwhile, in your package.json ..

"dependencies": {
    "express": "^4.17.1" <--- Package version
},
"devDependencies": {
    "eslint": "^7.13.0"
}
Enter fullscreen mode Exit fullscreen mode

What is this ? Actually, it's quite simple. Whenever you'll install something through the npm install command, it'll list it there. Doing so, when you'll share your amazing project with the world, the others devs will only launch npm install and all the libraries required for your project to run, will install nicely.

Now what are devDependencies ? Everything that is not vital for your application and that should be removed from your production build will go there (such as your linter, for example). Be careful, you have to manage them yourself.

By default, the npm install command will put everything inside the regular dependencies. To put something in the devDependencies, you must pass an extra argument to the command :

    npm install --save-dev <your_library>
Enter fullscreen mode Exit fullscreen mode

OR

    npm install -D <your_library>
Enter fullscreen mode Exit fullscreen mode

OR even shorter

    npm i -D <your_library>
Enter fullscreen mode Exit fullscreen mode

Organizing your dependencies will lead to better production performance. You might not need your linter rules or your Typescript types definition to run your app, right ?

Even better, npm allow us to omit the devDependencies on installation !

    npm install --only=prod
Enter fullscreen mode Exit fullscreen mode

Side (but important) notes

Uninstall a library

Made a typo and forgot the --save-dev in your command ?
Cleaning up your app from useless modules ?
You have two options, either remove the package and reinstall it again with the proper command, or do it manually in your package.json.

    npm uninstall <your_library>
Enter fullscreen mode Exit fullscreen mode

This will remove the library from the package.json and from the node modules.

In case you want to remove it from the node modules but not from the package.json (let's say the installation failed for whatever reason).

  npm uninstall --no-save <your_library>
Enter fullscreen mode Exit fullscreen mode

If you can't be bothered playing with the shell commands, you can also update manually your package.json.

Let's get back our previous example

"dependencies": {
    "express": "^4.17.1"
},
"devDependencies": {
    "eslint": "^7.13.0"
}
Enter fullscreen mode Exit fullscreen mode

To remove the eslint dependency, erase it, and simply re-run npm install.

Install a specific version

Sometimes you'll have to install a specific version of a package. It's easy :

    npm install <your_library>@<version>
Enter fullscreen mode Exit fullscreen mode

For example :

    npm install eslint@1.0.0
Enter fullscreen mode Exit fullscreen mode

The package-lock.json

Last but not least, the package-lock. It's actually here to solve a problem from the package.json we haven't talked about.

We saw earlier that when installing a new package, his version is set into the package.json. It uses the semver convention.

Basically, the first character before the actual version number will implies some slight changes whenever the npm install command is ran.

    "express": "~4.17.1"
Enter fullscreen mode Exit fullscreen mode

The ~ mean that NPM will go and look for the 4.17.1 version, but if a newer patch release is available, let's say 4.17.9, it'll use this one.

    "express": "^4.17.1"
Enter fullscreen mode Exit fullscreen mode

The ^ mean that NPM will go and look for the 4.17.1 version, but if a newer minor release is available, let's say 4.18.1, it'll use this one.

    "express": "4.17.1"
Enter fullscreen mode Exit fullscreen mode

If you omit a character, NPM will always use this exact version whatever happens.

If you always specify the exact version, the following problem I'll explain is already out of sight

Now let's say you have been working for a couple years on a project, and a new contributor clones and installs it.

Since a lot of time passed, some of our dependencies must have received some new releases. As we explained earlier, depending on your semver convention, NPM will look for potential newer versions...

And there we hit the wall, your project and the newly installed one are actually different because some dependencies do not match the version specified in the package.json.

Package-lock to the rescue. As his name implied, it'll lock the version number in stone and will guarantee that the same package version is installed on every subsequent installation.

You can find the original article on the Othrys website and you can follow my Twitter or tag me here to discuss about this article.

Discussion (14)

Collapse
josuerodriguez98 profile image
Josué Rodríguez (He/Him)

Kudos! Nice article! Let me tell you a little story about when I started using node: I didn't know about the --save flag and I just installed everything with npm install . When I tried running the project on a friend's computer everything (as expected) crashed. After this, I remember putting into the README this:
"To run this project, please run these commands

  • npm install packageA
  • npm install packageB
  • npm install packageZ" 😂😂😂
Collapse
mendoza profile image
David Mendoza (He/Him)

gif

Collapse
chandelieraxel profile image
Chandelier Axel Author

Good thing this is now the default behavior !

Collapse
jessedebruijne profile image
Jesse de Bruijne

Nice article! I was a bit confused with the phrase:

Please don't do the same mistake as I did, and prevent committing this folder to your repository !

Because it can also be read as: "The mistake I made was not committing this folder". Maybe you could rephrase it a little. Other than that, very useful article!

Collapse
ionutarhire profile image
Arhire Ionut

I think you made a mistake when giving the examples with '~' and '^'. 4.18.1 should switch places with 4.17.9

Collapse
chandelieraxel profile image
Chandelier Axel Author

Indeed ! Such a big mistake, thanks for pointing out. It has been fixed now. <3

Collapse
patarapolw profile image
Pacharapol Withayasakpunt

If you ever use npm init -y, you should also know there is npm config set.

Collapse
brombaut profile image
Ben Rombaut

Good article. I remember one of the first questions I had when dealing with npm was whether the package-lock.json file was suppose to be committed to the origin repo, or whether it should be ignored like the node_modules directory.

Collapse
paras594 profile image
Paras 🧙‍♂️

This was quite useful !! :)

Collapse
zakariaelk profile image
Zakaria Elk

Very useful! Thank you!!

Collapse
ash_bergs profile image
Ash

Wonderful read, I loved how you covered the shorthand commands. Thanks for writing!

Collapse
kullboii profile image
Aadi Manchekar

Thank you!

Collapse
swedlong profile image
Alexander Chan

Really nice article!