Beginner20 min read

npm & Package Management

Master npm to install, manage, and use packages from the world's largest software registry.

What is npm?

npm stands for Node Package Manager, and it is two things in one: a massive online registry of open-source JavaScript packages, and a command-line tool that lets you install and manage those packages in your projects.

The npm registry is the world's largest software registry, hosting over 2 million packages. To put that in perspective, it is larger than every other programming language's package registry combined. Every day, developers download billions of packages from npm. When you need to solve a problem in JavaScript — whether it is building a web server, sending emails, connecting to a database, or parsing dates — there is almost certainly an npm package that already does it.

npm comes bundled with Node.js. When you install Node.js, you automatically get npm. You can verify this by running npm --version in your terminal. This means you already have it if you have Node.js installed.

Here are some of the most popular npm packages you will encounter as a backend developer:

  • express — The most popular web framework for Node.js, used to build APIs and web servers
  • lodash — A utility library with helpful functions for working with arrays, objects, and strings
  • mongoose — An elegant MongoDB object modeling library for Node.js
  • dotenv — Loads environment variables from a .env file into process.env
  • axios — A promise-based HTTP client for making API requests
  • bcrypt — A library for hashing passwords securely
  • jsonwebtoken — For creating and verifying JSON Web Tokens (JWTs) for authentication

Think of npm as an app store for code. Instead of writing everything from scratch, you search for a package that solves your problem, install it with a single command, and import it into your project. This is one of the biggest reasons Node.js became so popular — the ecosystem of ready-made packages is enormous.

package.json — Your Project's ID Card

Every Node.js project has a file called package.json at its root. This file is the identity card of your project — it contains metadata about the project, lists its dependencies, and defines scripts for common tasks. Without a package.json, npm does not know how to manage your project.

You create a package.json in two ways:

  • npm init — Walks you through an interactive wizard that asks for the project name, version, description, entry point, and more.
  • npm init -y — Creates a package.json instantly with default values. The -y flag means "yes to everything."

Here are the most important fields in a package.json:

name — The name of your project. Must be lowercase, no spaces. Example: "my-api-server".

version — The current version of your project, following semantic versioning (semver): "1.0.0". The format is MAJOR.MINOR.PATCH. Major for breaking changes, minor for new features, patch for bug fixes.

description — A short description of what your project does. Useful for npm registry if you publish the package.

main — The entry point of your application. This is the file that runs when someone imports your package or when you run node . in the project directory. Defaults to "index.js".

scripts — An object where keys are script names and values are commands. This is one of the most powerful features. Common scripts include:

  • "start": "node server.js" — Runs your server in production
  • "dev": "nodemon server.js" — Runs your server with auto-restart for development
  • "test": "jest" — Runs your test suite
  • "build": "tsc" — Compiles TypeScript to JavaScript

dependencies — An object listing packages your project needs in production. When you deploy your app, these packages get installed. Example: "express": "^4.18.2".

devDependencies — An object listing packages only needed during development. Testing frameworks, linters, and build tools go here. They are NOT installed in production. Example: "jest": "^29.7.0".

The ^ symbol before a version number means "compatible with" — npm can install minor and patch updates but not major ones. So ^4.18.2 allows 4.19.0 but not 5.0.0.

Installing Packages

Installing packages is the most common thing you will do with npm. There are three types of installations, and understanding the difference is important.

Local dependency (production):

npm install express

This installs Express and adds it to the dependencies field in your package.json. The package files are downloaded into a node_modules folder in your project. Your application needs this package to run in production.

Local dependency (development only):

npm install --save-dev jest

The --save-dev flag (or -D for short) adds the package to devDependencies instead. These packages are only needed during development — testing tools, linters, type checkers, and build tools. They are not installed when you deploy to production with npm install --production.

Global installation:

npm install -g nodemon

The -g flag installs the package globally on your system, not in any specific project. Global packages are available as command-line tools from anywhere. Use this sparingly — most packages should be local to your project.

The node_modules folder is where all installed packages live. When you install Express, npm downloads Express and ALL of its dependencies (and their dependencies, and so on) into node_modules. This folder can contain hundreds or thousands of packages and get very large. That is why you always add node_modules to your .gitignore file — you never commit it to version control.

package-lock.json is automatically generated when you install packages. It records the exact versions of every package and sub-dependency that was installed. This ensures that when another developer runs npm install on your project, they get the exact same versions you had. Without the lock file, they might get slightly different versions, which could introduce bugs. Always commit package-lock.json to version control.

Installing from package.json:

npm install

Running npm install with no package name reads your package.json, looks at all dependencies and devDependencies, and installs everything. This is what you do when you clone a project — the node_modules folder is not in the repo, so you run npm install to recreate it.

Uninstalling a package:

npm uninstall express

This removes the package from node_modules and from your package.json.

npm Scripts

The scripts field in package.json is one of npm's most useful features. It lets you define custom commands that you run with npm run <script-name>. Think of scripts as shortcuts for long or complex terminal commands.

Here is a typical scripts section for a backend project:

json
"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js",
  "test": "jest",
  "test:watch": "jest --watch",
  "lint": "eslint .",
  "build": "tsc",
  "seed": "node prisma/seed.js"
}

You run these with npm run <name>:

  • npm run dev — Starts the development server with nodemon
  • npm run lint — Runs the linter to check code quality
  • npm run seed — Seeds the database with initial data
  • npm run test:watch — Runs tests in watch mode

Special scripts: Two script names are special — start and test. You can run these without the run keyword:

  • npm start (instead of npm run start)
  • npm test (instead of npm run test)

All other script names require npm run.

Pre and post hooks: npm supports automatic hooks. If you define a script called pretest, it runs automatically before test. If you define postbuild, it runs after build. For example:

json
"scripts": {
  "pretest": "npm run lint",
  "test": "jest",
  "posttest": "echo Tests complete!"
}

Running npm test would first run the lint, then run jest, then echo the completion message.

Why use scripts? They standardize how your project is run. Every developer on your team uses the same commands. New developers can look at the scripts section and immediately know how to start, test, and build the project. Scripts also let you compose complex commands — for example, chaining multiple tools together — behind a simple name.

A Complete package.json Example

json
{
  "name": "my-backend-api",
  "version": "1.0.0",
  "description": "A RESTful API server built with Express",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "jest",
    "test:watch": "jest --watch",
    "lint": "eslint .",
    "seed": "node seeds/run.js"
  },
  "keywords": ["api", "express", "nodejs"],
  "author": "Your Name",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.6.3",
    "dotenv": "^16.3.1",
    "bcryptjs": "^2.4.3",
    "jsonwebtoken": "^9.0.2",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "nodemon": "^3.0.1",
    "jest": "^29.7.0",
    "eslint": "^8.52.0"
  }
}

What command installs a package as a development dependency?

Ready to practice?

Create your free account to access the interactive code editor, run challenges, and track your progress.