Dependency Management

Production engineering guide for dependency management covering patterns, implementation strategies, and operational best practices.

Dependency management is the process of tracking, resolving, and maintaining software components that your project requires to build, run, or test. It matters when you are building any software system with external libraries, frameworks, or services, especially in environments where version conflicts, transitive dependencies, or breaking changes can cause silent failures or runtime errors.

Managing Dependencies for Build Systems

Adding a New Dependency

To add a new dependency to a project using a standard package manager, run:

npm install [email protected]

The @ version specifier is required for installing a specific version. Without it, the latest version will be installed.

If you want to add a dependency as a development dependency, use:

npm install --save-dev [email protected]

This adds the dependency to the devDependencies section in package.json.

Resolving Version Conflicts

When multiple dependencies require different versions of the same package, the package manager resolves this by installing a compatible version or nesting dependencies.

To see dependency resolution details:

npm ls express

This shows all installed versions of express and their relationships. If there's a conflict, you'll see something like:

[email protected]
└── [email protected]
    └── [email protected]

To force a specific version across all dependencies:

npm install [email protected] --save-exact

This saves the exact version in package.json without a caret (^) or tilde (~) prefix.

Removing Dependencies

To remove a dependency:

npm uninstall express

To remove a dev dependency:

npm uninstall --save-dev jest

If you remove a dependency that is also used as a transitive dependency by another package, it won't be removed from node_modules unless no other package requires it.

Managing Dependencies in Containerized Environments

Dockerfile Dependency Management

In a Dockerfile, dependencies are typically installed during the build phase using package managers like apt-get, yum, or npm.

Example for a Node.js application:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]

The command npm ci ensures deterministic installs from a package-lock.json file, unlike npm install.

If you use npm install instead of npm ci, it may not respect the exact versions in package-lock.json, leading to inconsistent builds across environments.

Handling Dependency Updates in Containers

To update dependencies in a containerized environment:

docker run --rm -v $(pwd):/app node:18-alpine sh -c "cd /app && npm install express@latest"

Then commit the updated package-lock.json and rebuild the image.

If you update dependencies without updating the lock file, the containerized build will not reflect the new versions.

Managing Dependencies for Testing

Running Tests with Dependency Isolation

To run tests in an environment that mirrors production dependencies:

npm test

This command typically runs a script defined in package.json under the "test" key. If your test setup requires specific versions of dependencies, ensure these are specified in package.json.

If you're using Jest and want to run tests with coverage:

npm run test -- --coverage

The double dash (--) is required to pass flags to the underlying test runner.

Handling Test-Specific Dependencies

Add test-specific dependencies like:

npm install --save-dev mocha chai sinon

These dependencies should be added to devDependencies and not included in production builds. If they are accidentally included in production, they may increase image size or introduce unnecessary runtime overhead.

Managing Runtime Dependencies

Ensuring Correct Versions at Runtime

In Node.js applications, ensure that your application loads the correct versions of dependencies by using a lock file:

npm install --package-lock-only

This command updates package-lock.json without installing new packages. If you are deploying to production, always use npm ci to install from the lock file.

If a dependency is not present in package-lock.json, it may resolve to an unexpected version at runtime.

Handling Transitive Dependencies

To inspect transitive dependencies:

npm ls lodash

This command shows how lodash is used throughout your dependency tree. If a transitive dependency changes, it can cause silent failures without any direct change in your code.

If you rely on a transitive dependency's behavior and do not explicitly depend on it, your code may break when that transitive dependency is updated.

Managing Dependencies Across Environments

Using Environment-Specific Configuration

You can use environment variables to override dependencies:

NODE_ENV=production npm install

This affects how some packages behave during installation or runtime. For example, some packages may install additional development tools in a development environment but skip them in production.

If you are deploying to different environments, always verify that the installed dependencies match across environments using npm ci with package-lock.json.

Handling Platform-Specific Dependencies

Some packages have platform-specific binaries:

npm install node-sass

This package may fail to install if the target platform does not have compatible binaries. If you encounter an error like:

node-sass@latest: Cannot download "https://github.com/sass/node-sass/releases/download/v7.0.3/linux-x64-108_binding.node"

You should either specify a compatible version or install with the --force flag to bypass platform checks.

Managing Dependencies for Security

Auditing Dependencies for Vulnerabilities

To check for known vulnerabilities:

npm audit

This command analyzes your dependency tree and reports any known security issues.

If vulnerabilities are found, run:

npm audit fix

This attempts to update dependencies to fix vulnerabilities. If audit fix cannot resolve all issues, it will suggest manual fixes.

Handling Audit Failures

Sometimes, npm audit fix fails to resolve all issues. In such cases, manually edit package.json to update vulnerable packages to versions that address the security concerns.

You can also run:

npm audit fix --force

This forces updates even when they might introduce breaking changes.

If you are deploying a project with audit failures, consider documenting which vulnerabilities were not fixed and why.

Managing Dependencies in CI/CD Pipelines

Ensuring Consistent Builds

In a CI pipeline, always use npm ci instead of npm install to ensure reproducible builds:

- name: Install dependencies
  run: npm ci

If your pipeline uses npm install, it may install different versions than those used in development, leading to inconsistent behavior.

Handling Dependency Caching

To cache dependencies between CI runs:

- name: Cache node modules
  uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

If you do not cache dependencies, your CI pipeline will reinstall all packages every time, increasing build time.

Common Pitfalls and Silent Failures

Overriding Dependencies Silently

Adding a dependency that is already present in the tree may silently override it. For example:

npm install [email protected]

If lodash was already required by another package at version 4.17.20, and this version is compatible, no error occurs.

However, if a newer version introduces breaking changes, your application may fail without clear indication that it's due to an overridden dependency.

Missing Lock Files

If package-lock.json or yarn.lock is not committed to version control, different developers may install different versions of dependencies. This leads to inconsistent builds and potentially broken functionality in production.

Confusing npm install vs npm ci

Using npm install instead of npm ci in CI environments can lead to inconsistent dependency resolution. npm install will update the lock file if it finds newer compatible versions, while npm ci strictly installs based on the lock file.

Ignoring Dependency Updates

Not updating dependencies regularly can lead to accumulated security vulnerabilities or compatibility issues with new Node.js versions. Always review dependency updates before merging them into your codebase.

Monitoring and Maintenance

Tracking Dependency Changes

Keep track of changes to dependencies by monitoring package.json and package-lock.json:

git diff package.json package-lock.json

This helps identify when dependencies are added, removed, or updated.

Handling Breaking Changes

When a major version update introduces breaking changes, always test thoroughly. For example:

npm install [email protected]

May break existing code that relies on APIs available in the previous version of Express.

If you encounter an error like:

TypeError: express.Router is not a function

It indicates that your application expects the old API from Express 4, but now uses the new API from Express 5.

Troubleshooting Common Issues

Dependency Not Found Errors

If you see an error like:

Cannot find module 'express'

Ensure that express is present in package.json and that npm install or npm ci was run successfully. Also check that your deployment environment has access to the same dependency versions.

Transitive Dependency Conflicts

If you encounter an error like:

Error: Cannot find module 'lodash'

even though lodash is in your package.json, it may be a transitive dependency conflict. Run:

npm ls lodash

To see where lodash is being pulled from and how it's being used.

Version Mismatch in Production

If your application works locally but fails in production, check that:

  1. The package-lock.json file is committed.
  2. You are using npm ci in production builds.
  3. The Node.js version matches between development and production environments.

This ensures that all dependencies are installed with the exact same versions across environments.

This page was rewritten on 15 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.