Skip to main content

Understanding package.json And package-lock.json

· 5 min read
Sivabharathy

Learn the key differences between package.json and package-lock.json, how they impact Node.js projects, and best practices for using them in GitHub Actions. Ensure consistent, reliable builds with real-world examples and actionable tips.

In every Node.js project, package.json and package-lock.json are essential files for managing dependencies. Though they may seem similar, they serve different purposes and have a significant impact on the consistency, security, and stability of your application — especially when deploying or testing via GitHub Actions.

In this post, you'll learn:

  • The difference between package.json and package-lock.json
  • Real-world examples of how these files affect your project
  • Best practices for using them
  • How to use them effectively in GitHub Actions CI/CD pipelines

📘 What is package.json?

The package.json file is the heart of any Node.js project. It serves as a manifest that describes:

  • The project name, version, and metadata
  • The list of dependencies and dev dependencies
  • Scripts for building, testing, and running your project
  • Configuration for tools like Babel, ESLint, Jest, etc.

✅ Example package.json

{
"name": "my-node-app",
"version": "1.0.0",
"scripts": {
"start": "node index.js",
"test": "jest"
},
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"jest": "^29.0.0"
}
}

This file tells npm what your project needs to function.


🔒 What is package-lock.json?

The package-lock.json file is automatically generated by npm when you install dependencies. It locks the exact versions of all dependencies and their sub-dependencies, creating a snapshot of the entire dependency tree.

Why it's important:

  • Ensures consistent installs across environments
  • Avoids unexpected bugs from updated sub-dependencies
  • Makes CI/CD and production environments predictable
  • Speeds up installations using npm ci

🧠 Real-World Difference

Let’s say your package.json has:

"dependencies": {
"axios": "^1.3.0"
}

This allows any version from 1.3.0 up to but not including 2.0.0.

Now:

  • On Monday, you run npm install → you get axios@1.3.1
  • On Friday, your teammate runs npm install → they get axios@1.3.3

That can lead to bugs that only happen on some machines.

However, if your project has a committed package-lock.json, then everyone gets axios@1.3.1, including GitHub Actions and production servers.


✅ Best Practices

PracticeRecommendation
Commit package-lock.json✅ Always for apps, optional for libs
Use npm ci in CI/CD✅ Guarantees reproducibility
Don’t manually edit lock file❌ Let npm manage it
Handle merge conflicts carefully⚠️ Lock files can conflict
Audit your dependencies✅ Use npm audit or GitHub Dependabot

💥 What Happens Without package-lock.json?

ProblemCause
"Works on my machine" bugsDifferent versions of sub-dependencies
CI builds randomly failTransitive dependencies change
Debugging becomes harderCan't recreate the environment

🔁 Using package-lock.json in GitHub Actions

In CI/CD, stability and speed are everything. You should use:

npm ci

instead of

npm install

Why npm ci?

  • Faster install times
  • Fails if package-lock.json and package.json are out of sync
  • Exactly reproduces dependency tree from lock file

🔧 Example GitHub Actions Workflow

name: Node.js CI

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "18"

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test

This guarantees your tests and builds are always run against the exact same versions of dependencies.


🌍 Real-World Scenario

🚨 Scenario: Bug in Production

  • On Monday, you deploy to staging → axios@1.3.1
  • On Friday, you deploy to production → axios@1.3.3
  • A new breaking change in axios@1.3.3 causes errors in production, but not in staging.

Had you committed and used the package-lock.json, both environments would use the exact same version, preventing the bug from reaching production.


📦 Should You Commit package-lock.json?

Project TypeCommit Lock File?Why?
Applications✅ YesLocks everything for stable builds
Libraries⚠️ OptionalConsumers manage their own deps

🧪 Bonus: Useful Commands

# Clean install using lock file
npm ci

# Update only the lock file based on current package.json
npm install --package-lock-only

# Audit security vulnerabilities
npm audit

🚀 Conclusion

package-lock.json is more than a side effect of npm install — it's a powerful tool to ensure consistent and reliable environments.

When used correctly, it:

  • Prevents bugs caused by changing dependencies
  • Speeds up and stabilizes your CI/CD pipelines
  • Helps deliver a consistent experience from dev to production

Tip: Always commit your package-lock.json and use npm ci in automation workflows like GitHub Actions.


💬 Have thoughts or questions?

Drop them in the comments — or reach out if you'd like help setting up rock-solid GitHub Actions workflows for your Node.js projects.