node_modules, package-lock.json and .gitignore

Every time you run npm install, npm creates or updates two things: the node_modules directory and the package-lock.json file. Understanding what these contain, how they relate to each other, and why one must never be committed to Git while the other always should be โ€” is essential knowledge for working on any MERN project alone or in a team. Mishandling these files is the single most common source of “works on my machine” bugs in Node.js projects.

node_modules โ€” What Is Inside?

server/node_modules/
โ”œโ”€โ”€ express/               โ† Express itself
โ”‚   โ”œโ”€โ”€ index.js
โ”‚   โ””โ”€โ”€ package.json
โ”œโ”€โ”€ accepts/               โ† dependency of Express (you did not install this)
โ”œโ”€โ”€ body-parser/           โ† dependency of Express
โ”œโ”€โ”€ debug/                 โ† dependency of a dependency of Express
โ”œโ”€โ”€ mongoose/              โ† Mongoose itself
โ”‚   โ”œโ”€โ”€ lib/
โ”‚   โ””โ”€โ”€ package.json
โ”œโ”€โ”€ ...                    โ† 100s of transitive dependencies
โ””โ”€โ”€ .bin/
    โ”œโ”€โ”€ nodemon            โ† CLI tools installed as executables
    โ””โ”€โ”€ jest

Running "npm install express" actually installs:
  express (1 package you asked for)
  + ~50 transitive dependencies (express's dependencies and their dependencies)

A typical MERN server project with ~10 direct dependencies
will have ~300-500 packages in node_modules totalling 50-150 MB
Note: You should never manually edit files inside node_modules. Any changes you make will be overwritten the next time someone runs npm install. If you need to patch a package, use npm’s patch-package tool which records the diff and reapplies it automatically after install โ€” but this should be a last resort.
Tip: If you ever see mysterious module errors after switching branches or pulling changes, the first fix to try is deleting node_modules and running npm install fresh. This cleans up any ghost packages from the previous branch. On Unix: rm -rf node_modules && npm install. For Windows PowerShell: Remove-Item -Recurse -Force node_modules; npm install.
Warning: Never commit node_modules to Git. It contains hundreds of thousands of files, makes repositories gigabytes in size, makes git status and git log unusable, and provides zero benefit since anyone can regenerate it exactly from package-lock.json with npm install. If you have accidentally committed it, see the Common Mistakes section below for how to remove it from history.

package-lock.json โ€” Reproducible Installs

While package.json specifies version ranges (e.g. ^4.18.2), package-lock.json records the exact version that was actually installed for every package โ€” including all transitive dependencies. This guarantees that npm install produces byte-for-byte identical node_modules on every machine and in every CI environment.

// package-lock.json โ€” a snapshot of exact resolved versions
{
  "name": "mern-blog-server",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "mern-blog-server",
      "dependencies": { "express": "^4.18.2" }
    },
    "node_modules/express": {
      "version": "4.18.3",
      "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz",
      "integrity": "sha512-...",
      "dependencies": {
        "accepts": "~1.3.8",
        "body-parser": "1.20.2"
      }
    },
    "node_modules/body-parser": {
      "version": "1.20.2"
    }
  }
}

npm install vs npm ci

npm install npm ci
Use when Development โ€” adding/updating packages CI/CD pipelines, production deploys
Reads package.json (may update lock file) package-lock.json only (never changes it)
node_modules Updates incrementally Deletes and reinstalls from scratch
Fails if package.json is invalid lock file is missing or out of sync with package.json
Speed Slower (resolves dependencies) Faster (uses exact lock file versions)

The Complete MERN .gitignore

# โ”€โ”€ Root .gitignore for a MERN monorepo โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

# โ”€โ”€ Dependencies โ€” NEVER commit these โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
node_modules/
**/node_modules/

# โ”€โ”€ Environment variables โ€” contain secrets โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# โ”€โ”€ Build output โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
dist/
build/
client/dist/

# โ”€โ”€ Logs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
logs/
*.log
npm-debug.log*
yarn-debug.log*

# โ”€โ”€ Testing โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
coverage/
.nyc_output/

# โ”€โ”€ OS files โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
.DS_Store
Thumbs.db
desktop.ini

# โ”€โ”€ Editor files โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
.vscode/settings.json    โ† only if you do NOT want to share editor settings
# Note: .vscode/extensions.json and .vscode/launch.json are worth committing
*.swp
*.swo

# โ”€โ”€ Uploads directory (user-generated content) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
server/uploads/*
!server/uploads/.gitkeep   โ† keep the directory itself but not its contents

Files You SHOULD Commit

File Commit? Reason
package.json โœ… Always Defines project dependencies and scripts
package-lock.json โœ… Always Ensures reproducible installs on all machines
.env.example โœ… Always Documents required variables without exposing secrets
.gitignore โœ… Always Must be in repo to protect all collaborators
node_modules/ ๐Ÿšซ Never Regenerated from package-lock.json by npm install
.env ๐Ÿšซ Never Contains real secrets โ€” JWT_SECRET, DB passwords
dist/ or build/ ๐Ÿšซ Usually not Generated at deploy time โ€” not source code

Common Mistakes

Mistake 1 โ€” Accidentally committing node_modules

โŒ Wrong โ€” running git add . before creating .gitignore:

git init
git add .   # adds ALL files including node_modules โ€” hundreds of thousands of files!

โœ… Correct โ€” always create .gitignore before the first git add. If node_modules is already tracked, remove it from Git history:

# Remove node_modules from tracking (keeps local copy)
echo "node_modules/" >> .gitignore
git rm -r --cached node_modules
git commit -m "remove node_modules from tracking"

Mistake 2 โ€” Deleting package-lock.json

โŒ Wrong โ€” deleting the lock file because it “causes conflicts”:

Without package-lock.json:
  Developer A installs express โ†’ gets 4.18.3
  Developer B installs express โ†’ gets 4.18.4 (released next day)
  CI server installs express   โ†’ gets 4.19.0 (released next week)
  โ†’ Three different versions in production โ€” unpredictable behaviour

โœ… Correct โ€” commit package-lock.json and resolve merge conflicts in it properly. The lock file is your guarantee of consistent installs.

Mistake 3 โ€” Committing the .env file

โŒ Wrong โ€” .env containing real credentials committed to a public repo:

JWT_SECRET=supersecret123
MONGODB_URI=mongodb+srv://admin:realpassword@cluster.mongodb.net/prod
# Now public on GitHub โ€” database can be accessed by anyone, forever
# (Git history preserves it even if you delete the file in a later commit)

โœ… Correct โ€” add .env to .gitignore before creating it. Provide a .env.example with placeholder values instead. If you already committed it, rotate all exposed credentials immediately.

Quick Reference

Task Command
Install all dependencies npm install
Reproducible install (CI) npm ci
Clean reinstall rm -rf node_modules && npm install
Remove from Git tracking git rm -r --cached node_modules
Check what git will stage git status
Verify .gitignore is working git check-ignore -v node_modules

🧠 Test Yourself

Your team uses Git for source control. A new developer clones the repo and notices there is no node_modules folder. What should they do?