What the Heck is a Package Manager?

Avatar of Josh Collinsworth
Josh Collinsworth on (Updated on )

DigitalOcean provides cloud products for every stage of your journey. Get started with $200 in free credit!

If you’re keeping score, so far in this npm guide we’ve developed a general understanding of what npm is—notably, that it stands for Node Package Manager. In the process, we’ve discussed the importance of the command line and how it’s used with npm.

We also looked specifically at the “n” in npm—Node—and learned that Node is a lot like the JavaScript code we write to run on websites in a browser. In fact, Node is JavaScript; it just runs outside of the browser, and is capable of doing different things than its browser-based counterpart.

Guide chapters

  1. Who the Heck is This Guide For?
  2. What the Heck Does “npm” Mean?
  3. What the Heck is the Command Line?
  4. What the Heck is Node?
  5. What the Heck is a Package Manager? (You are here!)
  6. How the Heck Do You Install npm?
  7. How the Heck Do You Install npm Packages?
  8. What the Heck Are npm Commands?
  9. How the Heck Do You Install an Existing npm Project?

What we mean by “package”

Now let’s turn our attention to the last two letters in npm, namely the “package manager” part. In order to fully understand what npm is, we need to know what a package manager is. So it naturally follows that in order to understand that, we need to understand what the heck a “package” is.

Package” is a catch-all term for any external code files that you pull into a project and use in some way. Perhaps you’ve used jQuery, Bootstrap, or Axios on a project in the past. Those are common examples of packages.

We call these “packages” because they’re “packaged up” and ready to be used. Some languages call them by other names (Ruby calls them “gems” for example), but the concept is the same. At the risk of oversimplifying, a package is code that you didn’t write but got from some public source to use in your project. You know, third-party code.

Or, if you prefer musical parodies for your mnemonic devices:

🎵 When there’s code that you choose
🎵 That’s not yours, but you use
🎵 That’s a package
🎵 When it’s stuff you install
🎵 That you import and call,
🎵 That’s a package

Packages are also often also referred to as “dependencies,” because the code you write depends on them being present. Code written using jQuery’s $ won’t work right if jQuery itself isn’t already loaded, for instance. (For this reason, package managers are also sometimes called “dependency managers.”)

Packages can be big or small in terms of how much code they contain. A package might do something huge that changes how you write your whole project (like an entire framework), or it might do something very small and focused that you sprinkle in only where needed (like a widget, or a helper for a specific task).

Using packages without a package manager

Most likely, if you have used a package in the past, you’ve simply applied it with a script tag in the HTML that pulls from an external URL (ideally from a CDN). Here’s how you might include jQuery in the HTML of your site:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

Another approach is to download a copy of the package, add it to your project’s files, and link to it locally, like this:

<script src="/jquery.min.js"></script>

What package managers solve

These two approaches have worked well for years. It’s simple. It’s clean. It generally lets you “set it and forget it” as far as the package goes. So why would you need something else?

You can probably imagine how owning a car might seem unappealing to somebody who has ready access to convenient transit, or who has no need for long-distance travel. (This will tie back into the package manager talk, I promise. Stick with me.)

If you have easy access to convenient and efficient mass transit, then paying a large price for a massive machine that you have to store somewhere, regularly clean, maintain, and fill with costly fuel probably won’t carry much upside from your perspective. In that specific case, the benefits are negligible; the costs are comparatively overwhelming. Someone in that hypothetical position might even wonder why anybody wants a car at all!

I bring up this analogy because learning about a new technology can be very hard when it solves a problem you don’t have, in very much the same way that buying a car might fail to solve transportation you already have. It might seem like a massive, needless expenditure.

What a package manager solves, then, is more a matter of scaling and handling concerns. Simply linking to a package in a script tag works well, as long as:

  • the number of projects you have is manageable;
  • the number of people working on the projects is manageable;
  • the number of updates that need to be made to the packages are manageable; and, most crucially,
  • every package used in your projects is client-side (browser) JavaScript or CSS.

That last one is the doozy, because there’s a plethora of tooling you can’t ever use if you only run things in the browser (more on that in a moment).

If you do check all of those boxes, you might not ever outgrow this approach. Your development approach might just look like this:

A black and white line illustration showing the diagram of packages with a package manager. A cloud labelled packages is followed by three files, HTML, CSS, and JavaScript, which are followed by the browser.

But even in that case, when you have multiple <script> tags, each linking to a specific version of some script or library, the only way to get any visibility at all into what packages you’re using—and whether they’re up-to-date—is to go manually open up the HTML and look at the code.

That’s not much of an issue in and of itself, but it’s a problem that grows exponentially as the size and scope of a project ramps up. You may be able to keep track of a few packages manually, but how could you possibly do that when we’re talking about hundreds—if not thousands—of packages? And even if you could manually track those, that’s still introducing a high risk of human error.

It’s not HTML’s job to be the source of truth for all of the packages used on a project. Aside from mixing concerns, it also potentially introduces conflicts when trying to merge unrelated work between teammates.

All this is important, but it’s the smallest part of a larger problem. Understand that client-side JavaScript probably isn’t the only type of package you’ll want to include in your projects forever, even if it is at the moment—and that’s where things really start to fall apart.

Many production apps use some combination of the following tools and packages, if not all of them:

  • Sass (makes writing CSS easier)
  • PostCSS (enhances CSS for maximum efficiency and compatibility)
  • Babel (transpiles newer JavaScript to run in older browsers)
  • TypeScript (adds type checking to JavaScript)
  • Hot module reloading by a dev server that auto-refreshes the browser to show your changes
  • Additional utilities for code bundling, minification and/or concatenation
  • Automatic image compression
  • Testing libraries
  • Linters

That all sounds wonderful—and it is!—but notice that you now have multiple dependencies that are not only not present in your script tags, but are not accounted for anywhere in your project at all! There’s no way for anybody—including your future self—to have any idea what tools were used or are required to to get this project running.

And even if you could know exactly what the project needed that way, you’d still need to go locate, download, and install all of those packages yourself… manually. Depending on the project, this could easily be a day-long task, or longer.

All this means your workflow now looks a little more like this:

A black and white line illustration showing the diagram of packages without a package manager. A group that consists of templates, Sass, and TypeScript or followed by static HTML, CSS, and JavaScript files, which are followed by a group that contains PostCSS and Babel, which is followed by a build tool, which is followed by two forks, one the dev server preview and the other the production browser.
Again, this is all good. This toolchain means that what gets shipped to the browser is highly optimized—but it’s also additional overhead and complexity.

Convenient as all the tools above are, you still need to manage them. Dependencies are projects, too, and they ship updates to patch bugs and introduce new features. As such, it’s unrealistic to simply paste a script tag in the HTML with a link that points to package on a CDN then call it good. You have to make sure each thing is installed and working properly not just on your machine, but on every collaborator’s machine, too.

Package managers exist to make the packages—or dependencies—of a project manageable by knowing what is installed, what’s available to update, and whether one package might create conflicts with another. And the beauty of a package manager is that it accomplishes all of this directly from the command line, with minimal effort.

Many package managers, especially npm, also provide additional features that open up even more possibilities to make development more efficient. But managing packages is the main attraction.

There are package managers that aren’t npm

This part isn’t super relevant to npm itself, but for the sake of completeness, I should also mention that npm isn’t the only JavaScript package manager. For example, you may see Yarn referenced in code examples. Yarn and npm work much the same way, to the extent that a great deal of interoperability between the two is purposely built in.

Some folks prefer one package manager over another. Personally, I think the differences between npm and Yarn were more pronounced at first, but the two are now more similar than not.

You may see code examples (including some in CSS-Tricks articles) that reference both yarn and npm together. That’s to let the reader know that either approach is fine, rather than the need to use both of them together.

The syntax for Yarn and npm differ at times, but where only one is present, it’s generally trivial to convert a command or project from one to the other. Functionally, it rarely (if ever) matters which one you use—except, of course, that everybody working on the same project together will want to be using the same one to ensure compatibility and consistency.

While npm and Yarn make up the vast majority of package managers that developers use, there’s another package manager called PnPm that is effectively npm, but more performant and efficient. The tradeoff is that PnPm requires a bit more technical know-how in some cases, so it’s a bit more of an advanced option.

Three examples of installing Vite in terminal via command line. First is npm, then Yarn, then PNPM.
The syntax differences between different package managers are generally minimal. (Source: Vite)

What makes npm the “standard” package manager

Again, I only bring up other package managers to illustrate that npm isn’t the only package manager out there—but it is generally the standard.

What makes it the “standard” among package managers? Other languages, including Ruby and PHP, have had package managers for many years; JavaScript really didn’t have any good ones before npm.

npm started as an independent, open-source project, but was acquired by Microsoft in 2020. It technically has two parts: the actual package manager itself; and the package registry, which is an ever-growing list of nearly two million packages available to install.

You could think of npm as the app store for anything you might want to use on a front-end or Node-based project. Find what you want and install it to your system via the command line. You might update that package when a new version is released, or delete it altogether if the project no longer depends on it.

A note on npx

You may also see npx commands floating out there. npx is actually a part of npm, but by using npx in a command instead of npm , you can execute the code of a package without permanently installing it. NPX just installs what it needs to, runs it, and dumps it.

This is helpful if, for example, you want to run an installer script. Rather than downloading the installer, then running it, npx lets you simply run the installer directly, leaving nothing on your machine afterward. It’s like the house guest that cleans up after themselves.

Another cool example: you could run npx sass (along with the necessary input and output arguments) if you wanted to compile your project’s Sass files just once without going to the trouble of completely installing Sass. This probably isn’t practical in most cases, but if you just needed a quick one-off compilation here and there, npx would be a handy way to do it, as it means fewer installed packages that need to be updated and maintained.

What’s next

Alright, so that’s a deep dive into what we mean when we call something a package manager. In the case of npm, it is used specifically to install and manage Node packages, tools that help add features to a project, add handy developer conveniences… or all of the above!

Next up, we’re going to take our first step into using npm. And to do that, we need to install it to our system. That’s next up in this complete guide to npm.