# npm and the Modern JavaScript Package Ecosystem

> Managing JavaScript dependencies with npm, configuring package.json correctly, and handling the practical challenges you run into in the package ecosystem.

- Published: 2018-08-05
- Category: Tools & Technologies
- Tags: JavaScript, npm
- Reading time: 3 min read
- Source: https://www.muhammetsafak.com.tr/en/blog/npm-and-the-modern-javascript-package-ecosystem/
- Language: en-US
- Author: Muhammet Şafak

---
After [adopting Composer](/en/blog/switching-to-composer-stop-managing-php-dependencies-by-hand/) in the PHP world, I remember thinking "every language should have something like this." On the JavaScript side, npm has been filling that role for years. Working on Vue and frontend projects for a while now has deepened my relationship with npm considerably. In this post I'm collecting what I've learned through daily practice.

## What is npm?

**npm** is a package manager that lets you install, share, and manage JavaScript packages. It ships with Node.js and also serves as your gateway to the world's largest software package registry.

The `package.json` file defines a project's identity and its dependencies. Think of it as the JavaScript equivalent of Composer's `composer.json`:

```json
{
  "name": "my-project",
  "version": "1.0.0",
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  },
  "dependencies": {
    "axios": "^0.18.0",
    "vue": "^2.5.0"
  },
  "devDependencies": {
    "webpack": "^4.0.0",
    "vue-loader": "^15.0.0"
  }
}
```

## The dependencies vs devDependencies distinction

It's easy to mix these two up. The rule is simple: packages that need to run in production go into `dependencies`; packages used only during development go into `devDependencies`.

- `vue`, `axios` → `dependencies` (needed at runtime)
- [`webpack`](/en/blog/migrating-to-webpack-from-gulp-to-module-bundling/), `babel`, `eslint` → `devDependencies` (build/development tools)

```bash
# Add as a production dependency
npm install axios

# Add as a development dependency
npm install --save-dev webpack
```

Why does it matter? When you run `npm install --production` on a server, only `dependencies` are installed — `devDependencies` are skipped. This keeps your production environment from being bloated with packages it doesn't need.

## The package-lock.json file

After installing packages, npm generates a file called `package-lock.json`. This file locks the exact versions of every installed package and all their transitive dependencies — the same purpose served by `composer.lock` in the Composer world.

Committing this file to Git is important. To avoid "it worked on my machine" problems, everyone on the team (and your servers) needs to install the exact same versions. `package-lock.json` is what guarantees that.

To install using the locked versions:

```bash
npm ci
```

Using `npm ci` instead of `npm install` enforces strict adherence to `package-lock.json` and rebuilds `node_modules` from scratch. It's ideal for CI environments.

## Version ranges

The version specifiers inside `package.json` can be confusing at first:

- `"axios": "0.18.0"` — install exactly this version
- `"axios": "^0.18.0"` — 0.18.0 or higher, but below 0.19.0 (below 1.0.0, `^` pins the left-most non-zero component — here `18`)
- `"axios": "~0.18.0"` — 0.18.0 or higher, but below 0.19.0 (patch updates only)

Most projects use `^`, which means "open to compatible updates." Because `package-lock.json` stores the exact installed version, that flexibility stays under control when you use `npm ci`.

## npm scripts

The `scripts` section of `package.json` is a heavily used feature. You can define short aliases for build commands, test runners, and other repetitive tasks:

```json
{
  "scripts": {
    "dev": "webpack --watch --mode development",
    "build": "webpack --mode production",
    "lint": "eslint src/"
  }
}
```

Once defined, running them is straightforward:

```bash
npm run dev
npm run build
npm run lint
```

This means someone new to the project doesn't have to learn which tool does what and how to invoke it — `package.json` acts as a kind of project guide.

## node_modules and .gitignore

The `node_modules` directory can get large — sometimes hundreds of megabytes. It should never be committed to Git. Add this line to your `.gitignore`:

```
node_modules/
```

That's all you need. Running `npm install` will read `package.json` and `package-lock.json` and download everything required.

## The Yarn alternative

As of 2018, Yarn has become a serious alternative to npm. Developed by Facebook, Yarn offers some advantages over npm — particularly around performance and reliability. Because it's fully compatible with the `package.json` format, the two tools can be used interchangeably on the same project without friction. Which one you choose usually comes down to team preference or habit.

## Conclusion

Using npm properly is the fundamental step toward avoiding dependency chaos in JavaScript projects. Respecting the `dependencies`/`devDependencies` split, committing `package-lock.json` to your repository, and standardizing your workflow commands with `npm scripts` — these are small habits, but they're critical for maintaining a tidy project structure.
