EXPLAINER

Package Managers

A package manager installs libraries and records versions. npm, pip, Cargo and NuGet are examples. A lockfile pins the tree so tomorrow's install matches today's. Installing global packages at random is how machines drift.

Short answer: A package manager installs libraries: npm for JavaScript, pip for Python, Cargo for Rust, NuGet for .NET. Commit the lockfile. Do not commit node_modules or trust an install script you have not thought about.

What it is

A package manager downloads libraries, puts them where the language can import them, and records which versions you asked for. npm (and compatible clients such as pnpm and Yarn) serves JavaScript. pip, plus tools like uv or Poetry, serves Python. Cargo serves Rust. NuGet serves .NET. Maven and Gradle serve the JVM. The ideas are the same even when the commands differ.

A manifest (package.json, requirements or pyproject.toml, Cargo.toml) states your direct dependencies, often as a version range. A lockfile records the exact tree that was resolved, including transitive dependencies you did not name. Git should store the manifest and the lockfile. It should not store node_modules or a virtualenv.

Why and when it is used

Use the package manager whenever you import code you did not write. Pinning is how CI and a teammate's laptop install the same bytes. Ranges (^1.2.0) let you take patches; the lockfile stops those ranges from moving silently between two installs.

Do not copy a library's source into your repo to "avoid dependencies" unless it is truly tiny and you will maintain it. Do not install everything globally. Global installs are convenient for CLIs and poisonous for libraries a project needs at a specific version. Do not run install scripts from untrusted packages on a machine that has production credentials.

How it works

The client asks a registry for metadata, solves versions against ranges, downloads archives, and checks integrity hashes. The lockfile stores those hashes. A later install uses the lockfile if you tell it to (npm ci rather than a loose install). Transitive dependencies are the ones your dependencies need. They are still code that runs. Semantic versioning is a social contract: a major bump may break you, and some authors get it wrong. Your tests are the real check.

Registries can be public or private. A private feed matters when the code is yours. Scopes and organisations in GitHub packages or a cloud artifact registry are how companies avoid publishing internal libraries to the world. Authentication to a private registry is a secret and belongs in the environment, not the repository.

Technologies and dependencies

The manager that matches the language, a lockfile committed to git, and CI that installs from the lockfile. Node.js ships with npm. Python needs a decision about venv plus pip, or a newer resolver, and then you stick to it. Mixing two Python installers in one project is how you get two copies and an import of the wrong one. Supply-chain tools (audit commands, pinned SHAs) are optional until the project matters, and then they are not optional.

How to get started

  1. In a new project, let the official tool create the manifest. Do not hand-type a half file.
  2. Add one dependency you actually import. Commit the manifest and the lockfile.
  3. Delete the install directory and install again from the lockfile. The import should work.
  4. In CI, use the clean install command (npm ci or the equivalent). Do not let CI resolve ranges afresh if the lockfile exists.
  5. When you update, update on purpose, run tests, and commit the new lockfile in the same change.
  6. Read the top of a dependency before you add it if it will run in production. A 40-dependency tree for a left-pad is a choice.

If two people are on different major versions of the language runtime, the package manager will not save you. Pin the runtime too (Environment Variables and CI images).

Cautions and trade-offs

npm install on a developer laptop can update the lockfile. npm ci will not. Use the strict one in CI. Install scripts can run arbitrary code at install time. That is normal for native modules and abused by malware. Look at new dependencies with that in mind.

Typosquatting publishes a name one character off a popular package. Copy names carefully. Committing node_modules bloats the repo and still does not pin transitive integrity as well as a lockfile. And a green audit report is not a design. Remove dependencies you do not use. The smallest reliable set is the one you can update when the next advisory arrives.