Suggest an editImprove this articleRefine the answer for “What is package-lock.json?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**package-lock.json** records the exact version, registry URL, and SHA-512 integrity hash for every dependency in a Node.js project (direct and transitive). ```json { "express": { "version": "4.18.2", "integrity": "sha512-..." } } ``` **Key point:** commit it to git and use `npm ci` in CI/CD for guaranteed identical installs.Shown above the full answer for quick recall.Answer (EN)Image**package-lock.json** is an automatically generated file that records the exact version of every installed dependency, including transitive ones, so that `npm install` produces identical results on every machine. ## Theory ### TL;DR - **Analogy:** package.json is a recipe saying "use flour, version 4.x." package-lock.json is the receipt: "batch 4.17.21 from supplier X." - Main difference: package.json allows version ranges for flexibility; package-lock.json pins the exact resolved tree. - `npm ci` reads the lockfile strictly and fails on any mismatch. `npm install` may update versions within allowed ranges. - Always commit package-lock.json to git. Never add it to .gitignore. ### Quick example ```bash # package.json allows a range echo '{"dependencies":{"lodash":"^4.17.0"}}' > package.json npm install # Installs lodash@4.17.21, creates package-lock.json # On another machine or in CI: npm ci # Installs exactly lodash@4.17.21, not whatever is latest now ``` `npm ci` deletes `node_modules` first, then restores from the lockfile. No surprises. ### What the lockfile actually stores The lockfile records the resolved version, registry URL, and a SHA-512 integrity hash for each package. When you run `npm ci`, npm verifies those hashes against the downloaded tarballs. If a hash does not match, the install fails. A simplified entry looks like this: ```json "express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "integrity": "sha512-..." } ``` This is the v3 format, introduced in npm 7. It includes all hoisted packages and their transitive dependencies in a flat structure. ### Key difference package.json says "I need express at roughly 4.18.x." The registry can satisfy that with 4.18.2 today and 4.18.5 next month. package-lock.json records which one actually got installed. Without the lockfile, two developers running `npm install` a week apart may end up with different versions of a transitive dependency they never explicitly added. One of them gets a production bug. ### When to use - Team project: commit the lockfile, run `npm ci` in CI/CD pipelines. - Production Dockerfile: `RUN npm ci --omit=dev`, not `RUN npm install`. - Solo prototype: commit it anyway. Future you will thank present you. - Updating a specific package: use `npm update lodash`. Do not delete the lockfile. ### Common mistakes **1. Adding package-lock.json to .gitignore** This is the most common source of "works on my machine" bugs. Every developer ends up with slightly different transitive dependencies. ```bash # Wrong: package-lock.json in .gitignore # Developer A gets lodash@4.17.21 # Developer B gets lodash@4.17.22 a week later (different behavior in one edge case) ``` Fix: remove from .gitignore and commit. **2. Using `npm install` in CI instead of `npm ci`** ```dockerfile # Wrong RUN npm install # Updates lockfile if ranges allow newer versions # Correct RUN npm ci --omit=dev ``` `npm install` may pull a newer version automatically. `npm ci` treats any divergence as a hard error and stops the build. **3. Deleting the lockfile to resolve merge conflicts** ```bash rm package-lock.json && npm install # Resolves everything from scratch ``` This pulls the latest allowed version of every package in your ranges. You lose the dependency history and may introduce regressions. Use `npm install <package>` or `npm dedupe` for targeted fixes instead. **4. Mixing package managers in one project** If the project has package-lock.json, running `yarn install` to add a package creates two conflicting lockfiles with different resolved trees. Pick one package manager and stick with it. ### Real-world usage I saw a production incident where a transitive dependency released a minor version with a broken JSON parser. The team had package-lock.json committed, but used `npm install` in their pipeline, so the update slipped through. Switching to `npm ci` caught the same class of problem on the next occurrence. - React apps: locks `react@18.2.0` and `scheduler@0.23.0` together for consistent rendering. - Express servers: pins `helmet@7.1.0` so a security middleware update does not change behavior mid-deploy. - Next.js: locks `webpack@5.89.0` to keep build output predictable. - Docker builds: `npm ci` in the Dockerfile ensures the image matches what was tested locally. ### Follow-up questions **Q:** What is the difference between `npm install` and `npm ci`? **A:** `npm install` creates or updates the lockfile and installs packages. `npm ci` deletes `node_modules`, reads the lockfile without modifying it, and fails if package.json and the lockfile are out of sync. Use `npm ci` for CI/CD and Docker. **Q:** Should you commit package-lock.json for a library (not an app)? **A:** No. Libraries should only commit package.json and let consumers resolve their own trees. Committing a lockfile for a library forces specific transitive versions on every developer who installs it. **Q:** What happens if the lockfile's integrity hash does not match the registry? **A:** npm re-fetches the tarball using the `resolved` URL and re-checks the SHA-512. If it still does not match, the install fails with an integrity error. This protects against tampered packages. **Q:** In a monorepo with npm workspaces, is there one lockfile or many? **A:** One root-level package-lock.json. npm hoists shared dependencies to the root `node_modules` and records the full workspace tree in that single file. ## Examples ### Locking dependencies for an Express app ```json // package.json { "dependencies": { "express": "^4.18.0", "helmet": "^7.0.0" } } ``` ```bash npm install # package-lock.json now records: # express@4.18.2 with its full transitive tree # helmet@7.1.0 with its full transitive tree ``` The caret range in package.json allows anything from 4.18.0 up to (not including) 5.0.0. The lockfile picks one version and holds it. When 4.19.0 ships next month, `npm ci` still installs 4.18.2 on every machine. ### npm ci in a production Dockerfile ```dockerfile FROM node:20-alpine WORKDIR /app # Copy package files before source code COPY package.json package-lock.json ./ # Install exact versions from lockfile, skip dev dependencies RUN npm ci --omit=dev COPY . . CMD ["node", "index.js"] ``` Copying package.json and package-lock.json before the rest of the source code lets Docker cache the `npm ci` layer. If only source files change, Docker reuses the cached `node_modules` and the build runs faster. If either package file changes, Docker invalidates the cache and reinstalls from scratch.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.