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.jsonandpackage-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 getaxios@1.3.1 - On Friday, your teammate runs
npm install→ they getaxios@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
| Practice | Recommendation |
|---|---|
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?
| Problem | Cause |
|---|---|
| "Works on my machine" bugs | Different versions of sub-dependencies |
| CI builds randomly fail | Transitive dependencies change |
| Debugging becomes harder | Can'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.jsonandpackage.jsonare 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.3causes 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 Type | Commit Lock File? | Why? |
|---|---|---|
| Applications | ✅ Yes | Locks everything for stable builds |
| Libraries | ⚠️ Optional | Consumers 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.

