Skip to content
Home JavaScript npm and package.json — EACCES Permission Error Without sudo

npm and package.json — EACCES Permission Error Without sudo

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Node.js → Topic 10 of 18
The EACCES error when running npm install -g is caused by root ownership from sudo.
🧑‍💻 Beginner-friendly — no prior JavaScript experience needed
In this tutorial, you'll learn
The EACCES error when running npm install -g is caused by root ownership from sudo.
  • npm is a package manager — it lets you install, share, and manage reusable code. It installs automatically with Node.js, so you don't need to set it up separately.
  • package.json is your project's identity card and shopping list — it records the project name, version, scripts, and every package the project depends on.
  • dependencies are packages your app needs to run in production (like express or axios). devDependencies are tools you only need while building or testing (like jest or eslint). Getting this wrong wastes resources on production servers.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • npm is the Node Package Manager — a registry, CLI, and file format combined
  • package.json is your project's manifest: name, version, dependencies, scripts
  • dependencies are for runtime; devDependencies are for development tools
  • npm install downloads packages into node_modules and records them in package.json
  • Semantic versioning (^1.4.2) controls which updates are safe to accept
  • The package-lock.json locks every sub-dependency version — never delete it
🚨 START HERE

npm Quick Debug Commands

When something breaks, these commands tell you exactly what's wrong.
🟡

Package not found / version mismatch

Immediate ActionCheck what's installed: `npm ls <package>`
Commands
npm list --depth=0
npm view <package> versions
Fix Nownpm install <package>@<exact-version> or delete node_modules and reinstall
🟡

Global command (like `create-react-app`) not found

Immediate ActionCheck npm global path: `npm bin -g`
Commands
npm list -g --depth=0
which <command> (or `where` on Windows)
Fix NowEnsure global prefix is in PATH or install locally via npx
🟠

Slow install / network timeouts

Immediate ActionCheck registry: `npm config get registry`
Commands
npm cache verify
npm config set registry https://registry.npmmirror.com (or your mirror)
Fix NowClear cache and retry: `npm cache clean --force` then `npm install`
🟡

Lockfile conflicts in Git (package-lock.json merge conflicts)

Immediate ActionKeep the lockfile from the base branch and rerun npm install
Commands
git checkout --theirs package-lock.json (or yours)
npm install (npm resolves conflicts automatically)
Fix NowCommit the regenerated lockfile — never manually edit package-lock.json
Production Incident

EACCES Permission Error During npm install -g

A junior dev runs `npm install -g ` on a shared Linux server and gets a permission error. They use sudo to fix it, which corrupts the global install and breaks the CI pipeline for everyone.
SymptomPermission denied errors when installing packages globally. sudo npm install works but changes ownership of node_modules to root, causing permission issues on subsequent pulls.
AssumptionUsing sudo is the normal way to fix permission problems.
Root causeDefault npm global install location (/usr/lib/node_modules) requires root access. Running with sudo changes ownership of installed packages to root, so later operations by non-root users fail.
FixConfigure npm to use a user-owned prefix: npm config set prefix ~/.npm-global, then add export PATH=~/.npm-global/bin:$PATH to your profile. Or use nvm to manage Node versions entirely in user space.
Key Lesson
Never use sudo with npm. It breaks permissions that cascade across the team.Always configure a user-level prefix for global installs.nvm avoids the problem entirely by isolating Node versions in your home directory.
Production Debug Guide

Symptom → Action guide for the npm errors that waste the most time on a team.

EACCES error when running npm install -gRun npm config get prefix. If it's /usr/lib/node_modules, reconfigure to ~/.npm-global and update your PATH. Or use nvm instead.
npm install succeeds but later require() fails with MODULE_NOT_FOUNDCheck that the package appears in node_modules or in package.json under dependencies. Verify your require path is correct. If using multiple packages, check for hoisting issues in npm v6 vs v7+.
Version conflicts after npm install — "conflicting peer dependency"Use npm ls <package> to see the tree. Common fix: add a --legacy-peer-deps flag temporarily, or better, manually align the conflicting versions. npm v7+ enforces peer deps strictly.
Audit warnings about moderate or high severity vulnerabilitiesRun npm audit fix to apply safe updates automatically. For breaking changes, manually update the offending packages. Use npm audit --json to get details.

Every professional JavaScript project you'll ever work on uses npm. It powers over 2 million packages and is downloaded billions of times every week. When you land a junior dev role and someone says 'just run npm install', you need to know exactly what's happening under the hood — not just how to type the command.

Before npm existed, sharing code between projects was painful. You'd copy-paste files manually, lose track of versions, and when a bug fix came out you'd have no way of knowing. npm solves all of that by giving every project a single source of truth: the package.json file. It tracks what external code your project depends on, what version of it you need, and even how to run your app.

By the end of this article you'll be able to create a project from scratch, install packages, understand every key field in package.json, and explain the difference between a dependency and a devDependency — the question that trips up a surprising number of candidates in junior dev interviews.

What npm Actually Is and Why It Was Built

npm stands for Node Package Manager. It ships automatically when you install Node.js, so if Node is on your machine, npm is already there.

The core job of npm is simple: let developers share and reuse code. That reusable chunk of code is called a 'package'. A package could be anything — a tool to format dates, a full web framework, a utility to send emails. Instead of writing all of that yourself, you pull in someone else's tested, maintained package.

npm has three parts that work together. First, there's the registry — a huge online database at registry.npmjs.org where packages live. Second, there's the CLI (command-line interface) — the npm commands you type in your terminal. Third, there's the website (npmjs.com) where you can search for and read about packages.

Think of the registry as the supermarket's warehouse, the CLI as your shopping trolley, and npmjs.com as the store's website where you browse before you go. You don't need to think about all three at once — most of the time you're just typing commands in the terminal and npm handles the rest.

check-npm-installed.sh · BASH
12345678910
# First, verify that Node.js is installed on your machine
# This prints the version of Node you have
node --version

# Now verify npm is installed — it comes bundled with Node
# This prints your npm version
npm --version

# If you see version numbers printed, you're good to go.
# If you see 'command not found', visit nodejs.org and install Node first.
▶ Output
v20.11.0
10.2.4
💡Pro Tip:
You never install npm on its own. Install Node.js from nodejs.org and npm comes with it automatically. If your npm is out of date, run 'npm install -g npm@latest' to update it.
📊 Production Insight
npm v7 changed default peer dependency handling, causing CI failures for older projects using legacy peer deps.
Lockfile conflicts in Git are the #1 source of "works on my machine" bugs — always commit package-lock.json.
Don't use sudo with npm. Configure a user-level prefix or use nvm.
🎯 Key Takeaway
npm is the standard package manager for Node.js.
It comes bundled with Node — no separate install needed.
Always update npm before starting a new project: 'npm install -g npm@latest'.

Creating Your First package.json — The Project's Identity Card

Every npm project needs a package.json file sitting at its root. This file is the heart of your project. It records your project's name, version, which packages it depends on, and useful scripts for running or testing your app. Without it, npm has no idea what your project needs.

You create it by running 'npm init' inside your project folder. npm will ask you a series of questions — project name, version, description, entry point, and so on. If you just want sensible defaults and don't want to answer every question, use 'npm init -y' (the -y flag means 'yes to everything').

The resulting file is plain JSON — just text. You can open it in any code editor and edit it by hand. That's one of the things beginners find surprising: it's not magic, it's just a config file you can read and change.

Every field in package.json has a specific meaning. The two most important are 'name' (what your package is called, lowercase, no spaces) and 'version' (which follows a three-number system called Semantic Versioning, or semver, like 1.0.0). We'll look at what those numbers mean in a moment.

create-project.sh · BASH
123456789101112
# Step 1: Create a new folder for your project
mkdir my-first-node-project

# Step 2: Move into that folder
cd my-first-node-project

# Step 3: Create a package.json with all default values
# The -y flag skips the questionnaire and accepts all defaults
npm init -y

# Step 4: Look at what was created
cat package.json
▶ Output
Wrote to /Users/you/my-first-node-project/package.json:

{
"name": "my-first-node-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
🔥What is 'main'?
The 'main' field tells Node.js which file is the entry point of your application — the first file it should run. By convention this is index.js. If someone installs your code as a package themselves, this is also the file that gets loaded when they 'require' your package.
📊 Production Insight
Forgetting 'license' in package.json can block corporate npm registry publishing.
The 'name' field must be unique on npm — choose a scoped name (@your-scope/package) for internal packages.
Setting 'private': true prevents accidental publishing to public registry.
🎯 Key Takeaway
Use 'npm init -y' for a quick start.
Edit package.json by hand — it's just JSON.
Add 'private': true for internal projects to avoid accidental publish.

Installing Packages — Dependencies vs devDependencies

Now for the fun part: pulling in other people's code. When you run 'npm install <package-name>', npm downloads that package into a folder called node_modules and automatically records it in your package.json under a field called 'dependencies'.

But not all packages are equal. Some packages your app needs to actually run in production — like express (a web server) or axios (for making HTTP requests). These go in 'dependencies'. Other packages are only needed while you're developing — like jest (a testing tool) or eslint (a code quality checker). Your end users will never need these. These go in 'devDependencies'.

To install a production dependency: 'npm install axios' To install a dev-only dependency: 'npm install jest --save-dev'

The '--save-dev' flag is the difference. Get this right and your production deployments only install what they actually need, which makes them faster and leaner.

Semantic versioning controls which version gets installed. The version number '1.4.2' means: major version 1, minor version 4, patch 2. A caret symbol (^1.4.2) means 'install 1.4.2 or any compatible minor/patch update'. A tilde (~1.4.2) means 'install 1.4.2 or any patch update only'. Most of the time you'll see the caret and that's fine for beginners.

index.js · JAVASCRIPT
1234567891011121314151617181920212223242526272829
// First, in your terminal run:
// npm install axios
// npm install chalk --save-dev

// Now let's use axios — a package for making HTTP requests
// This is a PRODUCTION dependency (your app needs it to run)
const axios = require('axios');

// Fetch a public post from a free test API
async function fetchBlogPost(postId) {
  try {
    // axios.get sends an HTTP GET request to the URL
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/posts/${postId}`
    );

    // response.data contains the actual JSON returned by the API
    const post = response.data;

    console.log('Post Title:', post.title);
    console.log('Post Body:', post.body);
  } catch (error) {
    // If the request fails, catch and print the error
    console.error('Failed to fetch post:', error.message);
  }
}

// Fetch the first blog post
fetchBlogPost(1);
▶ Output
Post Title: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
Post Body: quia et suscipit
suscipit recusandae consequuntur expedita et cum
reprehenderit molestiae ut ut quas totam
nostrum rerum est autem sunt rem eveniet architecto
⚠ Watch Out:
Never commit the node_modules folder to Git. It can contain hundreds of thousands of files. Instead, add node_modules to your .gitignore file. Anyone who clones your repo just runs 'npm install' and npm recreates the whole folder from your package.json automatically.
📊 Production Insight
Installing everything as a dependency bloats production builds — a tool like Webpack that's only used at build time should be devDependency.
npm v7+ requires explicit --save-dev for dev dependencies; npm v5-v6 automatically saved to dependencies if you didn't use the flag.
Always verify your dependencies with 'npm ls --prod' before deployment.
🎯 Key Takeaway
Use '--save-dev' for development-only tools.
Caret (^) allows minor updates; tilde (~) allows patch only.
Run 'npm install --production' in CI to install only runtime deps.

npm Scripts — Your Project's Command Centre

The 'scripts' field in package.json is one of the most underappreciated features for beginners. It lets you define shortcut commands for your project that anyone on the team can run without knowing the underlying tool or its exact flags.

For example, instead of everyone having to remember 'node --watch src/index.js', you define a 'start' script once and everyone just types 'npm start'. Instead of 'jest --coverage --verbose', you write a 'test' script.

There are two special script names: 'start' and 'test'. These run with 'npm start' and 'npm test'. Any other custom script name you invent runs with 'npm run <script-name>' — note the extra 'run' keyword.

Scripts can also chain commands together using '&&' (run second command only if first succeeds) or '&' (run both simultaneously). This is how teams build, lint, and test their code in one command. It's also how CI/CD pipelines (automated deployment tools) know how to build your project on a server.

Think of scripts as the user manual for your project. A new developer clones your repo, reads the scripts section, and immediately knows how to start, test, build, and deploy the app — without asking you.

package.json · JSON
12345678910111213141516171819202122232425262728
{
  "name": "my-first-node-project",
  "version": "1.0.0",
  "description": "A demo project to learn npm and package.json",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",

    "dev": "node --watch index.js",

    "test": "jest --coverage",

    "lint": "eslint src/**/*.js",

    "build:and:lint": "npm run lint && node index.js"
  },
  "dependencies": {
    "axios": "^1.6.7"
  },
  "devDependencies": {
    "chalk": "^5.3.0",
    "eslint": "^8.56.0",
    "jest": "^29.7.0"
  },
  "keywords": ["demo", "nodejs", "npm"],
  "author": "Your Name <you@example.com>",
  "license": "MIT"
}
▶ Output
# Running: npm start
# Executes: node index.js
# Output: whatever your index.js prints

# Running: npm run dev
# Executes: node --watch index.js
# Node watches for file changes and auto-restarts — great for development

# Running: npm test
# Executes: jest --coverage
# Jest finds and runs all test files and prints a coverage report
💡Interview Gold:
Interviewers love asking 'what's the difference between npm start and npm run start?' The answer: there is none — both work. 'start' and 'test' are blessed shortcuts that don't need the 'run' keyword. Every other custom script requires 'npm run <name>'.
📊 Production Insight
Using 'npm run' without pre/post hooks can miss validation steps — add 'prebuild' and 'postbuild' scripts for consistency.
CI environments expect scripts like 'test', 'build', 'lint' — standardize them across projects.
Scripts that fail silently (no exit code) break CI — always ensure commands return proper exit codes.
🎯 Key Takeaway
'npm start' and 'npm test' are special shortcuts.
Use 'pretest', 'posttest' hooks for setup/teardown.
Document scripts clearly — they're the team's playbook.

Understanding node_modules and package-lock.json

When you run npm install, npm downloads every package and its own dependencies (sub-dependencies) into a folder called node_modules. This folder is the runtime environment for your project. It's also massive — a typical React project can have 200,000+ files in node_modules. That's why you never commit it to Git.

Instead, npm created package-lock.json. This file records the exact version of every single package and every sub-package that was installed. It's a complete snapshot of your dependency tree. When someone else clones your repo and runs npm install, npm reads the lockfile and installs the exact same versions, down to the sub-dependency. This eliminates "works on my machine" problems.

Without package-lock.json, if package 'A' depends on package 'B' with range ^1.0.0, and 'B' releases version 1.1.0 after you last ran install, your teammate gets B@1.1.0 while you have B@1.0.0. The lockfile locks everything.

You should ALWAYS commit package-lock.json to Git. Never delete it, never add it to .gitignore. Some old tutorials say to delete it because it's "magical" — that's terrible advice. Keep it.

package-lock.json (excerpt) · JSON
1234567891011121314151617181920212223242526
{
  "name": "my-first-node-project",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "my-first-node-project",
      "dependencies": {
        "axios": "^1.6.7"
      }
    },
    "node_modules/axios": {
      "version": "1.6.7",
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
      "integrity": "sha512-...",
      "dependencies": {
        "follow-redirects": "^1.15.4"
      }
    },
    "node_modules/follow-redirects": {
      "version": "1.15.5",
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
      "integrity": "sha512-..."
    }
  }
}
⚠ Critical:
Do NOT add package-lock.json to .gitignore. This file is essential for reproducible builds. If you accidentally ignored it, remove it from .gitignore and commit the lockfile immediately.
📊 Production Insight
Merge conflicts in package-lock.json are common — never manually edit it. Accept theirs, then run npm install to regenerate correctly.
Different npm versions (v6 vs v7+) produce different lockfile formats. Ensure all developers use the same npm version, or commit using the lowest version.
npm v7+ uses lockfileVersion 2; npm v6 uses lockfileVersion 1. Upgrading npm changes the lockfile format — that's fine.
🎯 Key Takeaway
Always commit package-lock.json.
Never delete it or ignore it.
For merge conflicts, take theirs then run 'npm install' to regenerate.
Should I Commit package-lock.json?
IfI'm writing an npm package (library)
UseYes, commit the lockfile — it's recommended for libraries too, though some publishers choose not to. At minimum, commit it for your development environment.
IfI'm writing an application (app or service)
UseAbsolutely yes — always commit it. Reproducible builds depend on it.
IfMy team uses yarn or pnpm instead of npm
UseCommit the equivalent lock file (yarn.lock or pnpm-lock.yaml). Never commit both package-lock.json and yarn.lock — pick one package manager.
🗂 dependencies vs devDependencies at a Glance
AspectdependenciesdevDependencies
PurposeCode your app needs to actually runTools you need while building/testing
Examplesexpress, axios, mongoose, reactjest, eslint, typescript, webpack
Install commandnpm install <package>npm install <package> --save-dev
Installed in production?Yes — alwaysNo — skipped with npm install --production
Appears in package.json under"dependencies": {}"devDependencies": {}
Risk if missing in prodApp crashes or won't startNo impact — prod doesn't need them

🎯 Key Takeaways

  • npm is a package manager — it lets you install, share, and manage reusable code. It installs automatically with Node.js, so you don't need to set it up separately.
  • package.json is your project's identity card and shopping list — it records the project name, version, scripts, and every package the project depends on.
  • dependencies are packages your app needs to run in production (like express or axios). devDependencies are tools you only need while building or testing (like jest or eslint). Getting this wrong wastes resources on production servers.
  • npm scripts in package.json are shortcut commands for your whole team. 'npm start' and 'npm test' are built-in shortcuts; every other custom script runs with 'npm run <script-name>'.
  • Always commit package-lock.json to Git. It locks exact versions of every dependency and prevents irreproducible builds.

⚠ Common Mistakes to Avoid

    Committing node_modules to Git
    Symptom

    Your repo balloons to hundreds of megabytes, clones take forever, and Code review diffs are polluted with library files.

    Fix

    Add node_modules/ to .gitignore before your first commit. If already committed, run git rm -r --cached node_modules then commit again.

    Installing everything as a regular dependency instead of using --save-dev
    Symptom

    Testing frameworks like Jest and build tools like Webpack end up in dependencies instead of devDependencies. Production servers download thousands of files they'll never use, increasing deploy time and attack surface.

    Fix

    Always ask yourself: 'Does my running app need this, or only my development process?' If it's the latter, use --save-dev. Run npm ls --prod to verify only runtime packages are present.

    Deleting package-lock.json because it 'looks confusing'
    Symptom

    Two developers running npm install a week apart can get different sub-dependency versions, causing subtle 'works on my machine' bugs that are hard to reproduce.

    Fix

    Never delete package-lock.json. Always commit it along with package.json. If a merge conflict occurs, accept either side and re-run npm install to regenerate correctly.

    Using `npm init` with interactive questions instead of `npm init -y`
    Symptom

    Wasted time answering prompts, and some beginners accidentally create an invalid package.json by hitting enter with blank fields that cause later issues.

    Fix

    Use npm init -y for defaults, then manually edit package.json. It's faster and produces a known-good starting point.

Interview Questions on This Topic

  • QWhat is the difference between dependencies and devDependencies in package.json, and can you give a real-world example of each?JuniorReveal
    dependencies are packages your application needs to run in production — for example, express (web server) or axios (HTTP client). devDependencies are tools you only need during development — like jest (testing) or eslint (linting). When you deploy with npm install --production, devDependencies are skipped, making deploys faster and smaller. A concrete example: If you build an API with Express and write tests with Jest, express is a dependency and jest is a devDependency. If you accidentally put jest in dependencies, your production server will download all of Jest's files (including test runners you never use) for no reason.
  • QIf a colleague clones your Node.js repository and the node_modules folder isn't there, what command do they run and how does npm know what to install?JuniorReveal
    They run npm install. npm reads the package.json file to see which packages are listed under dependencies and devDependencies, then downloads the appropriate versions from the npm registry. If a package-lock.json exists, npm uses the exact versions specified there to guarantee identical installations across machines. The install process: 1) npm resolves version ranges against the registry, 2) checks the lockfile for pinned versions, 3) downloads tarballs, 4) extracts them into node_modules, 5) runs any install scripts if present.
  • QWhat does the caret symbol (^) mean in a version like ^1.4.2 inside package.json, and how is that different from specifying an exact version like 1.4.2 with no symbol?Mid-levelReveal
    The caret ^1.4.2 means 'install any version from 1.4.2 up to, but not including, 2.0.0'. It allows minor and patch updates but not major breaking changes. An exact version 1.4.2 without any symbol means 'install only 1.4.2, never update'. The tilde (~1.4.2) is stricter: it allows only patch updates (1.4.3, 1.4.4, etc.) but not minor version bumps. In production, you typically want caret ranges to get bug fixes automatically, but serious projects pin exact versions via lockfile. The lockfile always records the exact installed version, so the range in package.json is just a declaration of intent.
  • QExplain what happens when you run npm install and how the lockfile ensures deterministic builds.SeniorReveal
    When you run npm install, npm reads package.json to get the list of dependencies and their version ranges. It then consults the package-lock.json (if present) to see if an exact version tree has been recorded. If the lockfile exists and matches the package.json ranges, npm installs exactly those versions, skipping resolution. If no lockfile exists, npm resolves the latest versions matching the ranges, builds a dependency tree, and creates a new lockfile. The lockfile is deterministic because it records the exact version, integrity hash, and dependency tree of every package — including sub-dependencies. This ensures that every person, CI server, and production instance gets the exact same node_modules, preventing "works on my machine" bugs. Without it, version ranges like ^1.4.2 could resolve to different minor versions over time.

Frequently Asked Questions

What is the difference between npm and Node.js?

Node.js is a runtime — it's the engine that lets you run JavaScript outside of a browser. npm is a package manager that comes bundled with Node.js. Think of Node.js as a car engine and npm as the mechanic's tool kit — one runs your code, the other helps you manage the parts you need to build it.

What is package-lock.json and do I need it?

package-lock.json is an automatically generated file that records the exact version of every package and sub-package installed in your project. You should always commit it to Git. It ensures that every developer on your team and every production server installs the exact same versions, preventing subtle 'works on my machine' bugs.

Can I edit package.json by hand or should I only use npm commands?

You can absolutely edit package.json by hand — it's just a plain text JSON file. Developers do it all the time to update scripts, add metadata, or tweak version ranges. After editing it manually, run 'npm install' to make sure your node_modules folder reflects the changes you made.

Why should I not use sudo with npm?

Using sudo changes ownership of installed files to root, which causes permission errors for other users and can break global commands like create-react-app. Instead, configure npm to use a user-level directory by running npm config set prefix ~/.npm-global and adding that to your PATH. Better yet, use nvm to manage Node versions without any permission issues.

What's the difference between npm and npx?

npm installs packages globally or locally and adds them to your project. npx executes packages without installing them permanently — ideal for running one-off tools like create-react-app without cluttering global space. For example, npx create-react-app my-app downloads and runs the latest version without leaving a permanent install.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousNode.js Event EmitterNext →Node.js File System Module
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged