TL;DR: package.json is the manifest file for a Node.js project. It defines the project name and version, lists all package dependencies (and their required versions), and defines npm scripts for common tasks like starting the dev server, building for production, and running tests. Understanding it lets you add packages, run commands, and diagnose dependency conflicts.

Anatomy of package.json

Here is a fully annotated package.json from a typical Next.js project AI generates:

{
  "name": "my-app",          // Project name — lowercase, no spaces
  "version": "0.1.0",        // Your app's version (semver)
  "private": true,            // Prevents accidental npm publish
  "description": "My AI-built SaaS app",

  // Scripts — run with: npm run [script-name]
  "scripts": {
    "dev": "next dev",              // Start development server
    "build": "next build",          // Build for production
    "start": "next start",          // Start production server
    "lint": "eslint . --ext .ts,.tsx",
    "format": "prettier --write .",
    "type-check": "tsc --noEmit"    // TypeScript type checking
  },

  // Runtime dependencies — needed in production
  "dependencies": {
    "next": "^15.0.0",       // ^ means any compatible version (15.x.x)
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "@prisma/client": "^6.0.0",
    "zod": "^3.22.0"
  },

  // Dev dependencies — only needed during development
  "devDependencies": {
    "typescript": "^5.3.0",
    "@types/node": "^20.0.0",
    "@types/react": "^19.0.0",
    "eslint": "^9.0.0",
    "prettier": "^3.0.0",
    "prisma": "^6.0.0"      // CLI tool — dev only
  },

  // Node.js and npm version requirements
  "engines": {
    "node": ">=20.0.0",
    "npm": ">=9.0.0"
  }
}

Understanding Scripts

Scripts are shortcuts for terminal commands. npm run dev runs whatever is defined in "dev". You can chain commands:

"scripts": {
  // & runs both simultaneously (parallel)
  "dev": "next dev & prisma studio",

  // && runs second only if first succeeds (sequential)
  "build": "tsc --noEmit && next build",

  // Call other scripts using npm run
  "check": "npm run lint && npm run type-check",

  // Pass arguments with --
  // Usage: npm run lint -- --fix
  "lint": "eslint . --ext .ts,.tsx"
}

Common scripts AI generates for Next.js projects:

  • dev — Start the dev server (usually next dev or vite)
  • build — Production build
  • start — Run the production build
  • lint — Check for ESLint violations
  • format — Run Prettier
  • test — Run test suite
  • db:push or migrate — Database migration commands

Dependencies vs. devDependencies

dependencies

Needed to run your app. Installed in production. Examples: React, Express, Prisma client, Zod, Stripe SDK.

npm install express adds here.

devDependencies

Only needed during development. Not installed in production. Examples: TypeScript, ESLint, Prettier, testing frameworks, Prisma CLI.

npm install --save-dev eslint adds here.

The Prisma CLI (prisma) is a devDependency because you run migrations during development. The Prisma client (@prisma/client) is a dependency because your app uses it at runtime. AI sometimes gets this wrong — placing the CLI in dependencies or the client in devDependencies.

Reading Version Numbers (Semver)

Version numbers follow the pattern: MAJOR.MINOR.PATCH (e.g., 15.2.4)

  • MAJOR — Breaking changes. Upgrading may require code changes.
  • MINOR — New features, backward compatible. Usually safe to upgrade.
  • PATCH — Bug fixes. Always safe to upgrade.

Version range prefixes in package.json:

"next": "^15.0.0"   // ^ (caret) — accept 15.x.x, not 16.x.x
"react": "~19.0.0"  // ~ (tilde) — accept 19.0.x, not 19.1.x
"zod": "3.22.0"     // Exact version only
"eslint": ">=9.0.0" // Any version 9 or higher
"node": ">=20 <22"  // Range: 20 or 21, not 22+

The ^ caret is the default when you npm install. It allows automatic minor and patch updates but locks the major version. This means npm update will keep you on the same major version, avoiding breaking changes.

package-lock.json and node_modules

package.json defines the version ranges you accept. package-lock.json records the exact versions actually installed. This ensures everyone on your team (and your CI server) uses identical versions even if newer compatible releases are available.

Key rules:

  • Commit package-lock.json to Git — it ensures reproducible builds
  • Never commit node_modules/ — it is huge and regeneratable with npm install
  • Add node_modules to .gitignore — AI sometimes forgets this

Common Issues AI Creates

Missing from .gitignore

AI generates a project without adding node_modules/ or .env to .gitignore. You accidentally commit 300MB of dependencies or your API keys to GitHub. Always check .gitignore after AI scaffolds a project.

Wrong dependency type

CLI tools in dependencies instead of devDependencies. This bloats your production deployment. Check: does this run as a command (npx prisma, eslint)? That is a devDependency. Does your app import it at runtime (import { PrismaClient })? That is a dependency.

Version conflicts

AI installs packages with incompatible peer dependency versions. Symptom: npm install prints long warnings about peer dependency conflicts. Fix: check the conflicting package's docs for compatible version ranges, or use npm install --legacy-peer-deps as a temporary workaround.

What to Learn Next

Next Step

Open the package.json of your current project. Read every script and know what command it runs. Check that your CLI tools (ESLint, Prettier, Prisma CLI) are in devDependencies, and your runtime packages are in dependencies. This 5-minute audit ensures AI set it up correctly.

FAQ

package.json is the manifest file for a Node.js project. It defines the project name and version, lists all dependencies with their required version ranges, and defines npm scripts for common tasks. Every Node.js and JavaScript project has one.

Dependencies are packages needed to run your app in production. DevDependencies are only needed during development (building, testing, linting). When deploying, production installs only skip devDependencies to keep the deployment lean.

The caret (^) allows compatible version updates. ^15.0.0 means "15.x.x is fine, but not 16.x.x." It allows minor and patch updates (new features and bug fixes) while preventing major version updates that may include breaking changes.

Yes, always. The lock file records exact versions of every dependency and sub-dependency, ensuring reproducible installs across different machines, CI systems, and deployments. Omitting it can cause "works on my machine" problems.

npm run dev reads the "dev" key from the "scripts" section of package.json and runs that command. For a Next.js project, "dev" is typically next dev, which starts the Next.js development server on port 3000. The scripts section is how you define project-specific shortcuts.