npm
02 / 08

package.json Deep Dive

package.json Deep Dive

package.json is the heart of every Node.js project. Beyond name and version, it contains exports, engines, scripts lifecycle hooks, workspace configuration, and dependency overrides. Understanding all fields helps you write professional packages and avoid gotchas.

Key Fields Reference

{
  "name": "@myorg/my-package",   // Scoped or unscoped; lowercase, no spaces
  "version": "1.4.2",            // semver: major.minor.patch
  "description": "A short description",
  "license": "MIT",
  "author": "Jane Doe <jane@example.com> (https://jane.dev)",
  "homepage": "https://github.com/myorg/my-package#readme",
  "repository": {
    "type": "git",
    "url": "https://github.com/myorg/my-package.git"
  },
  "bugs": { "url": "https://github.com/myorg/my-package/issues" },
  "keywords": ["node", "utils", "parser"],

  // Entry points
  "main": "./dist/index.js",      // CommonJS entry (legacy)
  "module": "./dist/index.mjs",   // ESM entry (bundlers)
  "types": "./dist/index.d.ts",   // TypeScript declarations

  // Modern exports map (Node 12+, overrides main)
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    },
    "./utils": {
      "import": "./dist/utils.mjs",
      "types": "./dist/utils.d.ts"
    }
  },

  // Files to include when publishing (whitelist)
  "files": ["dist", "src", "!src/**/*.test.*"],

  // CLI binaries
  "bin": {
    "my-cli": "./bin/cli.js"
  },

  // Node/npm version requirements
  "engines": {
    "node": ">=18.0.0",
    "npm": ">=8.0.0"
  },

  // Mark as browser/Node only package
  "browser": true,
  // OR: ""browser": false"  (Node-only)

  // ESM vs CJS
  "type": "module"  // .js files treated as ESM; use .cjs for CommonJS
  // "type": "commonjs"  // Default; .js treated as CJS; use .mjs for ESM
}

Dependencies

{
  // Runtime dependencies (shipped to production/consumers)
  "dependencies": {
    "express": "^4.18.2",
    "zod": "^3.22.0"
  },

  // Development only (not installed by consumers of your package)
  "devDependencies": {
    "typescript": "^5.3.0",
    "jest": "^29.7.0",
    "@types/node": "^20.0.0",
    "eslint": "^8.57.0"
  },

  // Must be installed by the consumer (not installed automatically)
  // Use for: plugins that expect a specific version of a host package
  "peerDependencies": {
    "react": ">=17.0.0 || ^18.0.0",
    "react-dom": ">=17.0.0 || ^18.0.0"
  },
  // Mark peer deps as optional (no warning if missing)
  "peerDependenciesMeta": {
    "react-dom": { "optional": true }
  },

  // Optional: if install fails, npm continues (platform-specific packages)
  "optionalDependencies": {
    "fsevents": "^2.3.3"  // macOS file watching
  },

  // Override nested dependency versions (resolve security issues)
  "overrides": {
    "semver": "^7.5.4",       // Override ALL semver versions in tree
    "express": {
      "qs": "^6.11.2"         // Override qs only when required by express
    }
  }
}

Scripts Lifecycle

{
  "scripts": {
    // Lifecycle hooks run automatically
    "preinstall":   "node check-node-version.js",
    "postinstall":  "patch-package",         // Run after npm install
    "prepare":      "husky",                 // Runs on npm install + npm publish
    "prepublishOnly": "npm test && npm run build",  // Only before publish
    "prepack":      "npm run build",         // Before packing a tarball
    "postpack":     "",

    // User-defined scripts
    "dev":          "next dev --turbopack",
    "build":        "next build",
    "start":        "next start",
    "test":         "jest --passWithNoTests",
    "test:watch":   "jest --watch",
    "test:coverage": "jest --coverage",
    "lint":         "eslint src --ext .ts,.tsx",
    "lint:fix":     "eslint src --ext .ts,.tsx --fix",
    "type-check":   "tsc --noEmit",
    "format":       "prettier --write \"src/**/*.{ts,tsx,json}\"",
    "db:generate":  "drizzle-kit generate",
    "db:migrate":   "drizzle-kit migrate",

    // Compound scripts (parallel with & or concurrently)
    "dev:all":      "concurrently \"npm:dev\" \"npm:db:studio\"",

    // Pre/post hooks for custom scripts too
    "prebuild":     "npm run type-check",
    "postbuild":    "echo Build complete!"
  }
}

Workspaces

// Monorepo setup: root package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",      // All packages in packages/ directory
    "apps/*"           // All apps in apps/ directory
  ],
  "scripts": {
    "build": "npm run build --workspaces",
    "test":  "npm run test --workspaces --if-present"
  }
}

// Install dependency in specific workspace
npm install react --workspace=apps/web
npm install lodash -w packages/utils   // -w = --workspace shorthand

// Run script in specific workspace
npm run build --workspace=apps/web
npm run test -w packages/utils

// Run script in all workspaces
npm run build --workspaces
npm run test --workspaces --if-present  // --if-present skips if script missing

// Install all workspaces from root
npm install  // Hoists shared deps, symlinks workspace packages

// Cross-workspace dependencies (no version needed - uses local)
// packages/web/package.json:
{
  "dependencies": {
    "@myorg/utils": "*"  // Resolves to packages/utils symlink
  }
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free