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.

