Understanding package.json — The Heart of Every Node.js Project

Every Node.js project — whether it is an Express API, a React app, or a utility script — starts with a single file: package.json. This JSON file is the project’s identity card, dependency manifest, and task runner configuration all in one. When you clone a MERN project from GitHub, package.json tells you exactly what it is, what packages it needs, and how to run it. Understanding every meaningful field in package.json is a foundational skill that makes you a faster, more confident MERN developer from day one.

Creating package.json

# Interactive — asks questions one by one
npm init

# Fast — fills all fields with sensible defaults, no questions asked
npm init -y

# The -y flag is what you will use most of the time when starting a new project

Anatomy of a package.json File

{
  "name": "mern-blog-server",
  "version": "1.0.0",
  "description": "Express REST API for the MERN Blog application",
  "main": "index.js",
  "private": true,
  "scripts": {
    "start":   "node index.js",
    "dev":     "nodemon index.js",
    "test":    "jest --coverage",
    "lint":    "eslint src/",
    "lint:fix": "eslint src/ --fix"
  },
  "dependencies": {
    "bcryptjs":     "^2.4.3",
    "cors":         "^2.8.5",
    "dotenv":       "^16.4.1",
    "express":      "^4.18.2",
    "jsonwebtoken": "^9.0.2",
    "mongoose":     "^8.1.0",
    "multer":       "^1.4.5-lts.1",
    "nodemailer":   "^6.9.9"
  },
  "devDependencies": {
    "eslint":  "^8.56.0",
    "jest":    "^29.7.0",
    "nodemon": "^3.0.3",
    "supertest": "^6.3.4"
  },
  "engines": {
    "node": ">=20.0.0",
    "npm":  ">=10.0.0"
  },
  "keywords": ["mern", "express", "api", "blog"],
  "author": "Your Name ",
  "license": "MIT"
}

Field-by-Field Explanation

Field Purpose Notes
name Package identifier Lowercase, no spaces — used if you publish to npm registry
version Semantic version of your app Follow semver: MAJOR.MINOR.PATCH
description Short human-readable summary Shown in npm search results if published
main Entry point when package is required Usually index.js for Express servers
private Prevents accidental publish to npm Always set to true for app code
scripts Runnable commands via npm run The most important field for day-to-day development
dependencies Packages needed in production Installed with npm install pkg
devDependencies Packages only needed during development Installed with npm install -D pkg
engines Specifies required Node/npm versions Platforms like Render and Vercel respect this field
keywords Search tags for npm registry Optional for private apps
license Usage licence MIT for open source, UNLICENSED for private
Note: The "private": true field is a safety net that prevents npm publish from accidentally publishing your application code to the public npm registry. Always include it in application package.json files. It has no effect on installing dependencies or running scripts — it only blocks publishing.
Tip: The "engines" field does more than document the Node.js version — deployment platforms like Render, Railway, and Heroku read it to select the correct Node.js runtime for your deployment. Set it to "node": ">=20.0.0" and your production server will always run on a compatible version automatically.
Warning: The name field in package.json must be unique if you ever publish to the npm registry. For private applications this does not matter much, but using a name that already exists on npm can cause confusion if someone runs npm install your-app-name expecting to get the public package instead. Use scoped names (@yourorg/app-name) for company projects.

Semantic Versioning (semver)

Every version number in dependencies follows the MAJOR.MINOR.PATCH format, and the prefix character controls which updates are accepted automatically.

Version string Meaning Accepts updates to
"express": "4.18.2" Exact version only Nothing — pinned exactly
"express": "~4.18.2" Tilde — patch updates only 4.18.x — bug fixes only
"express": "^4.18.2" Caret — minor and patch updates 4.x.x — new features + bug fixes
"express": "*" Any version Everything including breaking — avoid!
Note: npm uses the caret (^) prefix by default when you run npm install express. This means npm install on a fresh clone may install a newer minor version than the one you originally used. The package-lock.json file locks the exact installed versions to prevent this — never delete it.

package.json for the React Client

{
  "name": "mern-blog-client",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev":     "vite",
    "build":   "vite build",
    "lint":    "eslint . --ext js,jsx",
    "preview": "vite preview"
  },
  "dependencies": {
    "axios":          "^1.6.7",
    "react":          "^18.2.0",
    "react-dom":      "^18.2.0",
    "react-router-dom": "^6.22.0"
  },
  "devDependencies": {
    "@types/react":     "^18.2.55",
    "@vitejs/plugin-react": "^4.2.1",
    "eslint":           "^8.56.0",
    "vite":             "^5.1.0"
  }
}

Common Mistakes

Mistake 1 — Editing package.json manually instead of using npm commands

❌ Wrong — hand-editing dependencies in package.json without running npm install:

// You add "helmet": "^7.1.0" manually to package.json
// But node_modules still does not contain helmet — your app will crash

✅ Correct — always install via npm so both package.json and node_modules stay in sync:

npm install helmet   # adds to package.json AND installs into node_modules ✓

Mistake 2 — Using the same package.json for both client and server

❌ Wrong — one package.json at the root mixing React and Express dependencies:

// root/package.json contains both:
"express": "^4.18.2"   ← server dependency
"react":   "^18.2.0"   ← client dependency
// Vite and Express configs conflict, build process is messy

✅ Correct — maintain separate package.json files in server/ and client/ as set up in Chapter 2.

Mistake 3 — Missing the “private”: true field

❌ Wrong — no private flag on an application that accidentally gets published:

npm publish   # publishes your entire app (including .env secrets if misconfigured) to npm!

✅ Correct — always include "private": true in application package.json files to make npm publish fail safely.

Quick Reference

Task Command / Field
Create package.json npm init -y
View current package.json cat package.json
Run a script npm run script-name
Run start script shorthand npm start
Run test script shorthand npm test
Pin exact version npm install --save-exact express
Specify Node version required "engines": {"node": ">=20.0.0"}
Prevent npm publish "private": true

🧠 Test Yourself

Your package.json has "express": "^4.18.2". A colleague runs npm install on a fresh clone six months later. Which version of Express will they get?