Featured resource
Forrester Wave Report 2025
Pluralsight named a Leader in the Forrester Wave™

Our tech skill development platform earned the highest scores possible across 11 criteria.

Learn more
  • Labs icon Lab
  • Core Tech
Labs

Guided: Package Management in JavaScript

This hands-on lab teaches you npm and pnpm through real-world scenarios that every developer faces. You’ll learn to set up projects, resolve dependency conflicts, troubleshoot common issues, and choose between package managers. Experience the problems that break builds daily: peer dependency warnings, lock file conflicts, cache corruption, and security vulnerabilities. By the end, you’ll confidently manage dependencies and migrate npm/yarn projects to use pnpm.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 37m
Published
Clock icon Jul 02, 2025

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction to JS Package Management

    JavaScript projects could depend on third-party code — utility libraries, frameworks, tooling, etc. Managing all of these manually is inefficient and error-prone. That’s where package managers come in.

    Package managers let you:

    • Install libraries/packages from public registries like npm.
    • Track version constraints and dependencies.
    • Share your own code with others.

    Think of them as your app’s supply chain.

    There are various package managers for JavaScript projects. The most popular is npm, but others like pnpm, Yarn, and Bun are gaining traction for their performance and features.

    Package managers can also help you with scaffolding new project. Let's start the lesson by showing you how to create a new JavaScript project using npm. A package.json was created when you initialized the project. Let’s break it down:

    • name: This is the project’s name, by default it uses the name of the working directory.

    • version: The version of the app. Typically follows semantic versioning style. If you're building a private/internal app, this field is not important and can be removed.

    • scripts: Contains scripts and their related commands. For example "start": "node index.js" adds a script that starts the Node.js app.

    There are other important fields that were not added. They are:

    1. dependencies: contains the list of packages which are required to run the application in production.

    2. devDependencies: contains the list of packages which are required to run the application during development or build process.

  2. Challenge

    Working with npm

    Most projects need dependencies for various functions. This step is going to show you how to add dependency to your projects. The prompts project was added as a runtime dependency to the project. Next, you'll add a devDependency to the project. After adding those dependency, you will notice the following values have been added to the package.json file:

    "dependencies": {
        "prompts: "^2.4.2"
      },
      "devDependencies": {
        "prettier": "^3.5.3"
      }
    

    That is how you can tell what kind of dependencies are in a project. ## Lock File Mismatch

    Every project has a lock file, package-lock.json when using npm. This is used to keep the version consistent across systems.

    A lock file mismatch happens when package-lock.json was manually tampered, whereby the version in the lock file and package.json are different. This can cause serious bugs in production system.

    To avoid such problems, you should use the npm ci command to install dependecies in your CI/CD pipeline.

  3. Challenge

    Peer Dependency Hell

    Peer dependencies are packages that a library expect the host application to provide, rather than bundling them directly. This prevent duplicate installations of the same library, or multiple versions of the same library.

    They are listed under peerDependencies field in package.json. Using peer dependency is very common with UI libraries like React.

    Peer Dependency Hell

    Peer dependency hell occurs when a peer dependency version requirements conflict between packages or what's installed in the host application. There by making installation impossible or leading to runtime bugs if undetected.

    I have set up an example with a peer dependency mismatch. The project has the following dependency

    "dependencies": {
        "react": "^16.14.0",
        "react-router-dom": "^7.6.2"
      }
    

    How do you know detect the problem? The command exits with an error describing the issue. Somewhere in the error log it says invalid: ">=18" from node_modules/react-router-dom

    This means that the minimum react version we should use is 18.

    The solution here would be to install that version using npm add react@18.

  4. Challenge

    Exploring pnpm

    pnpm is a different package manager known for being faster than npm, with efficient disk-space management. It uses a unique approach involving hard links and content-addressable storage. This leads to significant benefits, such as:

    1. Disk Space Efficiency: Dramatically reduces the disk space consumed by node_modules because packages are stored only once.
    2. Faster Installs: Subsequent installs are often much quicker as packages are frequently already in the global store.
    3. Stricter Dependency Resolution: Its linking mechanism creates a "flat" node_modules structure where packages can only access explicitly defined dependencies, preventing accidental access to hoisted dependencies and leading to more reliable builds.

    You're going to create a project using pnpm. It'll be similar to what you did in the previous steps After the project is set up, and pakcages installed, you should notice the lock file as pnpm-lock.yaml. This is pnpm's way of dealing with lock files. The lock file contains information about the lockfileVersion as well as other pnpm related data.

    Understanding pnpm's Disk Space Magic

    Unlike some other package managers, pnpm doesn't duplicate packages for every project. Instead, it keeps a single global store and links files into each project using hard links. That helps with multiple projects using the same dependency only store it once, which further leads to faster package installs.

    pnpm’s Strict Approach to Dependencies

    pnpm enforces strict dependency boundaries. It uses a non-flat node_modules, therefore your code has no access to packages not listed in your package.json.

    For example, the prompts package you installed in the previous step has a dependency named kleur. Although that dependency was installed alongside prompts, you won't be able to use kleur directly without installing it. That means, the following code won't work:

    const { bold, green } = require("kleur");
    
    console.log(bold().red("this is a bold red message"));
    console.log(bold().italic("this is a bold italicized message"));
    

    Meanwhile, if you had used npm, that would work just fine.

    Follow the instructions below to verify the node_modules tree structure difference between npm and pnpm:

    1. Open and view the file tree for workspace/step-02/node_modules. You should see 4 modules, although you only installed prettier and prompts.
    2. Do the same for the directory in step-04/js-pnpm-lab/node_modules. You should only see prompts as a module directory. Now try to run the code snippet from the previous section, which should show you an error message because kleur is not explicitly installed.

    Afterwards, you'll fix it by installing kleur as a dependency.

    The code inside the project located in step-04/js-pnpm-lab has been prepared for you. The re-run should successfully execute the code, with the following message printed in the Terminal:

    this is a bold red message
    this is a bold italicized message
    
  5. Challenge

    Security & Supply Chain Attack

    JavaScript applications face significant security risks due to vulnerabilities in dependencies and supply chain attacks. A notable example was the compromise of the ua-parser-js package in 2021, which affected millions of downloads and injected cryptocurrency miners into applications.

    Supply chain attacks target the dependency ecosystem by exploiting the trust developers place in third-party packages. These attacks can introduce malicious code that steals data, creates backdoors, or compromises entire systems.

    Audit Commands

    The following commands can be used to check for and fix dependency vulnerabilities in JavaScript applications:

    • npm audit: Scans your project's dependency tree for known security vulnerabilities, reporting severity levels and affected packages.
    • pnpm audit: Provides similar functionality for pnpm users, identifying vulnerable dependencies with detailed reports.
    • pnpm audit --fix: Automatically updates vulnerable packages to patched versions where possible, resolving security issues without manual intervention. The npm equivalent is npm audit fix.

    Best Practices

    Run audits regularly during development and in CI/CD pipelines. Review audit reports carefully before applying automatic fixes, as updates might introduce breaking changes. Consider using tools like npm ci for production builds to ensure consistent, secure deployments.

  6. Challenge

    Fixing Corrupt node_modules

    There are times when your application fails to run due to corrupt node_modules files or mismatched package versions. Some common scenarios where this may occur include:

    1. Corrupted Installation: Module installation interrupted by system crashes, network issues, or Ctrl+C during install can leave dependencies in a broken state.

    2. Version Conflicts: Switching between Node.js versions, encountering conflicting peer dependencies, or using mismatched package versions can cause runtime errors and build failures.

    3. Cache Issues: Stale npm/yarn/pnpm caches containing outdated or corrupted package data—especially after network interruptions or registry changes—can lead to unexpected behavior.

    4. Lock File Mismatches: Discrepancies between package.json and lock files (package-lock.json, yarn.lock, pnpm-lock.yaml) can cause inconsistent installations across environments.

    5. Platform Migration: Moving projects between different operating systems or architectures, where native modules may need to be recompiled.

    These issues often prompt developers to perform a clean installation by deleting the node_modules folder and associated lock files.

    Here's a handy command to help recover from such problems:

    # Remove everything
    rm -rf node_modules package-lock.json
    npm cache clean --force
    npm install
    
    # For pnpm
    rm -rf node_modules pnpm-lock.yaml
    pnpm store prune
    pnpm install
    

    This approach resolves most dependency-related issues by ensuring clean, fresh installations.

  7. Challenge

    Other Alternatives to npm

    Package managers have evolved to address npm's limitations, such as performance bottlenecks, disk space usage, and dependency resolution issues. First came Yarn, then pnpm, and now some runtimes come bundled with their own package managers, for example, Deno and Bun.

    Yarn Classic vs. Yarn Berry

    Yarn Classic (v1) introduced deterministic installs with lock files and parallel downloads, solving npm’s early inconsistency problems. Teams adopted it for faster, more reliable builds.

    Yarn Berry (v2+) revolutionized dependency management with Plug'n'Play (PnP), eliminating node_modules folders entirely. Dependencies are stored in zip files with direct module resolution, dramatically reducing install times and disk usage.

    Bun: The Speed Demon

    Bun combines a package manager, runtime, and bundler into one tool. Written in Zig, it is 10–25x faster than npm for installations. Its built-in JavaScript runtime competes with Node.js while maintaining compatibility.

    When to Consider Alternatives

    You should consider alternatives to npm for the following reasons:

    • Large monorepos requiring faster installs (pnpm, Yarn Berry)
    • Teams needing consistent environments (Yarn Classic)
    • Performance-critical projects (Bun)
    • Modern architectures embracing PnP (Yarn Berry)
  8. Challenge

    Migrating From npm to pnpm

    Migrating between package managers is simple for most cases. In those cases, you could delete the lock file and node_modules folder, then reinstall all the packages. Yarn v1 and pnpm has the import command, that generates a lock file based on package-lock.json. pnpm takes this further to support migrating npm-shrinkwrap.json and yarn.lock files.

    For the next task, you're going to migrate a npm project to use pnpm.

  9. Challenge

    Putting It All Together

    Let's imagine a scenario where your team has been assigned a new project. You're asked to set up the JavaScript project, using Express as the framework, and Prettier for formatting rules.

    The project folder (in step-09) has been created, which includes a package.json file that comes when you pnpm init in a folder.

    The following steps will guide you in doing setting up an Express project, with scripts to run the app or fix formatting issues. The start and dev scripts is used to run the Express app. The dev script is used for development where you need to watch for file changes, and restart when files change. The format script fixes any Prettier issues, while format:check shows what files has issues without fixing them.

    Next, we're going to add index.js with a simple Express set up. You get a warning when you run that command:

    Checking formatting...
    [warn] index.js
    [warn] Code style issues found in the above file. Run Prettier with --write to fix.
    

    You can fix that using the format script. Now that the formatting issues are fixed, the next step is to run the app and see it working When you're done trying the app, you can terminate the process using CTRL + C.

  10. Challenge

    Outro

    Congratulations!

    You've mastered the essential skills for modern JavaScript package management through hands-on experience.

    The focus was on npm and pnpm, while briefly covering other package managers.

    npm: The industry standard, offering maximum compatibility and extensive documentation.

    pnpm: Offers superior disk space efficiency through content-addressable storage, strict dependency resolution that prevents phantom dependencies, and significantly faster installation times. Ideal for large projects and monorepos.

    Best Practices

    Always commit lock files to ensure consistent environments across your team. Use npm ci in CI/CD pipelines for reproducible builds rather than npm install.

    Conduct regular security audits with npm audit to identify and fix vulnerabilities promptly.

    Quick Command Reference

    • Initialize: npm init / pnpm init
    • Install: npm install / pnpm install
    • Clean install: npm ci / pnpm install --frozen-lockfile
    • Add package: npm add / pnpm add
    • Audit: npm audit / pnpm audit

    You're now equipped to confidently manage dependencies, troubleshoot issues, and choose the right package manager for your JavaScript projects.

Peter Mbanugo is a software developer, writer, and maker of Hamoni Sync. He focuses more on GraphQL, Offline-First, and Software Architecture.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.