Node.js & Express

NPM & Package Management

18 min Lesson 6 of 40

Understanding NPM (Node Package Manager)

NPM is the default package manager for Node.js and the world's largest software registry. It allows developers to share, discover, and use code packages created by the community. NPM is essential for modern Node.js development, providing access to hundreds of thousands of reusable packages.

What is a Package?

A package is a reusable piece of code that can be shared and used across different projects. Packages can range from simple utility functions to complex frameworks like Express.js or React. NPM makes it easy to install, update, and manage these packages in your projects.

Note: NPM is automatically installed when you install Node.js. You can verify your NPM version by running npm --version or npm -v in your terminal.

Initializing a Node.js Project

Every Node.js project that uses NPM starts with a package.json file. This file contains metadata about your project and manages its dependencies.

Creating package.json

To create a new package.json file, navigate to your project directory and run:

npm init

This command will prompt you with several questions about your project (name, version, description, entry point, etc.). You can press Enter to accept the defaults for any question.

Quick Initialization

To create a package.json file with all default values without answering questions:

npm init -y

This creates a basic package.json file that you can edit later:

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

Installing Packages

NPM provides several ways to install packages depending on your needs.

Local Installation

To install a package locally in your project:

npm install package-name

Or using the shorthand:

npm i package-name

This command:

  • Downloads the package from the NPM registry
  • Installs it in the node_modules folder
  • Adds it to the dependencies section of package.json
  • Creates or updates package-lock.json to lock dependency versions

Example: Installing Express

npm install express

After installation, your package.json will include:

"dependencies": { "express": "^4.18.2" }

Global Installation

Some packages are meant to be installed globally, typically command-line tools:

npm install -g package-name

Examples of globally installed packages:

npm install -g nodemon npm install -g typescript npm install -g create-react-app
Warning: Global installations may require administrator privileges (sudo on Mac/Linux). Use global installation sparingly—most packages should be installed locally to avoid version conflicts between projects.

Dependencies vs DevDependencies

NPM distinguishes between two types of dependencies based on when they're needed.

Dependencies

These are packages required for your application to run in production:

npm install express npm install mongoose npm install dotenv

These packages are listed under "dependencies" in package.json.

DevDependencies

These are packages only needed during development (testing tools, build tools, linters):

npm install --save-dev nodemon npm install --save-dev jest npm install --save-dev eslint

Or using the shorthand:

npm i -D nodemon

These packages are listed under "devDependencies" in package.json:

{ "dependencies": { "express": "^4.18.2", "mongoose": "^7.0.3" }, "devDependencies": { "nodemon": "^2.0.22", "jest": "^29.5.0" } }
Tip: When deploying to production, you can install only production dependencies using npm install --production, which skips devDependencies and reduces installation time and size.

Understanding Semantic Versioning

NPM uses semantic versioning (semver) to manage package versions. Version numbers follow the format: MAJOR.MINOR.PATCH

Version Number Format

  • MAJOR: Breaking changes (incompatible API changes)
  • MINOR: New features (backward-compatible)
  • PATCH: Bug fixes (backward-compatible)

Example: 4.18.2 means Major version 4, Minor version 18, Patch version 2

Version Range Symbols

NPM uses special symbols to specify acceptable version ranges:

"express": "4.18.2" // Exact version only "express": "^4.18.2" // Compatible with 4.18.2 (minor and patch updates allowed) "express": "~4.18.2" // Approximately equivalent (only patch updates allowed) "express": "*" // Latest version (not recommended) "express": ">=4.18.2" // Greater than or equal to 4.18.2
Note: The caret (^) is the default when you install packages. It allows updates that do not change the leftmost non-zero digit. For example, ^4.18.2 allows versions like 4.19.0 or 4.25.1, but not 5.0.0.

Managing Installed Packages

Viewing Installed Packages

To see all locally installed packages:

npm list

To see only top-level packages (without dependencies):

npm list --depth=0

To view globally installed packages:

npm list -g --depth=0

Updating Packages

To update all packages to their latest allowed versions (respecting semver):

npm update

To update a specific package:

npm update express

To check for outdated packages:

npm outdated

Uninstalling Packages

To remove a package:

npm uninstall package-name

This removes the package from node_modules and package.json. Shorthand:

npm un package-name

NPM Scripts

NPM scripts allow you to define custom commands in your package.json file. This is a powerful feature for automating tasks.

Defining Scripts

Scripts are defined in the "scripts" section of package.json:

{ "scripts": { "start": "node index.js", "dev": "nodemon index.js", "test": "jest", "build": "webpack --mode production" } }

Running Scripts

To run a script:

npm run script-name

For example:

npm run dev npm run test npm run build

Special Scripts

Some script names have special meaning and can be run without run:

npm start // Runs "start" script npm test // Runs "test" script npm stop // Runs "stop" script

Pre and Post Scripts

NPM automatically runs pre and post scripts if they exist:

{ "scripts": { "prestart": "echo Starting application...", "start": "node index.js", "poststart": "echo Application started!" } }

Running npm start will execute all three scripts in order.

Tip: You can pass arguments to scripts using --. For example: npm start -- --port=3000 passes --port=3000 to your script.

Understanding package-lock.json

The package-lock.json file is automatically generated when you install packages. It serves several important purposes:

  • Exact versions: Records the exact version of every installed package, including dependencies
  • Consistency: Ensures all team members and deployment environments install identical versions
  • Faster installs: NPM can skip some resolution steps when this file exists
  • Security: Includes integrity hashes to verify package contents
Note: Always commit package-lock.json to version control. It ensures reproducible builds across different environments.

NPX - Node Package Executor

NPX is a tool that comes with NPM (version 5.2+) that allows you to execute packages without installing them globally.

Benefits of NPX

  • Run packages without global installation
  • Always use the latest version of a package
  • Execute packages from remote URLs
  • Run different versions of the same package for testing

Using NPX

Instead of installing a package globally:

// Old way npm install -g create-react-app create-react-app my-app // NPX way npx create-react-app my-app

Running Local Binaries

NPX can also run locally installed packages:

npx nodemon index.js npx jest npx eslint .

Specifying Package Versions

You can run specific versions of packages:

npx node@14 script.js npx create-react-app@4.0.0 my-app
Tip: NPX is perfect for one-time commands and avoiding global package pollution. Use it for tools you don't need permanently installed.

Publishing Your Own Packages

NPM allows you to publish your own packages for others to use.

Prerequisites

  1. Create an NPM account at npmjs.com
  2. Login from the command line:
npm login

Preparing Your Package

Ensure your package.json has the required fields:

{ "name": "my-awesome-package", "version": "1.0.0", "description": "A brief description of your package", "main": "index.js", "keywords": ["keyword1", "keyword2"], "author": "Your Name", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/yourusername/your-repo" } }

Publishing

To publish your package:

npm publish

To update your package, increment the version number and publish again:

npm version patch // 1.0.0 → 1.0.1 npm version minor // 1.0.1 → 1.1.0 npm version major // 1.1.0 → 2.0.0 npm publish
Warning: Package names must be unique on the NPM registry. You cannot publish a package with a name that already exists. Consider using scoped packages (@username/package-name) for personal projects.

Best Practices for Package Management

1. Keep Dependencies Updated

Regularly check for and install updates to fix security vulnerabilities:

npm audit npm audit fix

2. Use .npmignore

Create a .npmignore file to exclude files from your published package:

node_modules/ tests/ .env .git/ *.log

3. Specify Engine Requirements

Define the Node.js and NPM versions your package requires:

{ "engines": { "node": ">=14.0.0", "npm": ">=6.0.0" } }

4. Use Lockfiles

Always commit package-lock.json to ensure consistent installations.

5. Clean Installs

For troubleshooting, use clean install which strictly follows the lockfile:

npm ci
Exercise: Create a new Node.js project and practice package management:
  1. Initialize a new project with npm init -y
  2. Install Express as a dependency
  3. Install Nodemon as a dev dependency
  4. Create custom scripts: "start" (runs with node) and "dev" (runs with nodemon)
  5. Test both scripts
  6. Use npm list --depth=0 to view your packages
  7. Check for outdated packages with npm outdated
  8. Try using NPX to run a package without installing it: npx cowsay Hello NPM!