Demystifying package.json
Tags: frontend, programming, webdev
Published: 2022-04-25 11:15:27

If you work with a frontend, your project usually contains *package.json* file. Do you know what it's for and why this file is so important? Let's take a closer look at *package.json*.

In earlier times, when you met a representative of some company, you were likely to receive a business card. Small piece of paper contained all necessary information: name of the person, contact, company details, etc. File called *package.json* is something similar to a business card, but it describes your project. Let's see how the file can look.

```js
{
  "name": "react-modal",
  "version": "3.15.1",
  "description": "Accessible modal dialog component for React.JS",
  "main": "./lib/index.js",
  "module": "./lib/index.js",
  "repository": {
    "type": "git",
    "url": "https://github.com/reactjs/react-modal.git"
  },
  "homepage": "https://github.com/reactjs/react-modal",
  "bugs": "https://github.com/reactjs/react-modal/issues",
  "directories": {
    "example": "examples"
  },
  "scripts": {
    ...
  },
  "authors": [
    "Ryan Florence"
  ],
  "license": "MIT",
  "devDependencies": {
    ...
  },
  "dependencies": {
    ...
  },
  "peerDependencies": {
    ...
  },
  "tags": [
    ...
  ],
  "keywords": [
    ...
  ],
  "engines": {
    "node": ">=8"
  }
}
```
The above file comes from *react-modal*, an open-source library. As you can see, it contains some data about the project (`name`, `version`, `description`, etc.) and lists all external projects that were used to build the library (`devDependencies`, `dependencies`, `peerDependencies`). 

### Dependencies
Currently, software projects aren't created in a vacuum. Otherwise, a development of new software would not be effective because you would need to reinvent the wheel repeatedly. In reality, most projects use previously developed solutions and build new user functionalities on top of them. This is called dependencies. 

### Versions of dependencies
Each dependency contains a name and a version: `"react-dom": "^16.13.1"`. As you can see, the version is not just a number, but it has some characters. There is a special logic behind this and we will take a look at it:
- `16` - in our example, it is a major version. Usually, number that represent the major part is changed when incompatible or really big, new functionalities are added the project.
- `13` - it is a minor version. When small, new functionalities are added to the project, the minor version is usually incremented.
- `1` - it is a patch version. If the code contains some bugs, after the deployment of fixes, the patch number is changed. 
- `^` - it means that only minor and path number can be increased. For example, `"17.10.2"` wouldn't comply this whereas `"16.14.2"` is ok and can be used by our project.

There are more options for special characters:
- `>` - it means that all versions higher than the given can be used. For example, `"17.10.2"` is ok, but `"15.11.3"` isn't - the number is lower than our initial version `"16.13.1"`.
- `~` - it means that only patch number can be increased. For example, `"16.14.2"` is not correct, because minor version is changed, but `"16.13.2"` is ok - the difference is only for the path part.
- `no character` - it means that only the exact version is valid.

If you want to to play around with the versions, I recommend this site: https://semver.npmjs.com/ 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4yrf1whege8ie68qkd56.png)
 
 
### Installing dependencies
Let's assume that we have a *package.json* file and we want to run our project. One of the necessary actions that we need to take is to install all dependencies. It can be done by `npm install`. As a result, a new *node_module* folder will be created, and it will contain all packages from the section `dependiencies` of *package.json*. If your dependencies are dependent on other packages, they also will be downloaded and placed in *node_module* folder, even though they are not explicitly listed in your *package.json* - it is good to know that installing packages from `dependencies` section is transitive.

Sometimes when we use a lot of external packages, *node_module* folder can have an immense size. Because it is easy to recreate that folder (by installing the dependencies), you shouldn't include it to the repository - all team members are able to execute `npm install` to download necessary dependencies. 

You can also notice that there is a new file added in your solution: *package-lock.json*. In the `dependiencies` section of that file, all dependant packages will be listed (even those that are indirectly dependent). I will explain *package-lock.json* more deeply later in this post. 

### Types of dependencies
In our example, *react-modal* has three types of dependencies: `devDependencies`, `dependencies`, and `peerDependencies`. Let's clarify the differences between them.
- `dependencies` - as we mentioned earlier, all packages from `dependencies` section are installed along with its dependencies. If I want to install package X (as either `dependencies` or `devDependencies`) that has Y as `dependencies`, and Y has package Z as `dependencies`, all three packages (X, Y, and Z) will be installed and placed in *node_modules*.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95xmrbmobirjch04p40u.png) 
If you want to add a new package to your solution, you can use `npm install NameOfThePackage`.
- `devDependencies` - while `dependencies` are transitive, `devDependencies` aren't. In the above example, when X is defined as `dependencies` or `devDependencies` in your project and both Y and Z packages are defined as `devDependencies`, they will not be added to the solution - only X will be downloaded. 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gqfvs92j6bvs1z0cr7m.png)
If you want to add a new package as a `devDependencies` to your solution, you can use `npm install --save-dev NameOfThePackage`. Development dependencies as a name suggest are helpful when it comes to develop the project locally. When it is deployed to the production, they are not useful and won't be added to *node_module*. So when you use a command `npm install --production`, and X is defined as `devDependencies` it won't be downloaded.

Given the above dependency classification, the question may arise of what type to use. There is a lot of opposite views on this topic, so I would like to discuss it more deeply. Let's assume that you want to build a React application and you used Create React App (CRA) tool for a configuration. When you open the *package.json* file, you can notice that there are only `dependencies`, not `devDependencies`:
```json
...
"dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.1.1",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  }
...
```

Some of the Internet suggestions say that you should not use `dependencies` for packages that are focused on local development activities, such as testing libraries, code formatters, etc. But as you can see above, *@testing-library/jest-dom* that is helpful for writing tests is added as `dependencies`. To complicate matters further, when you visit, for example, *prettier* or *eslint* projects, you can see that on https://www.npmjs.com (official npm package repository) the instruction for installation is the following: `npm i eslint`. However, the *eslint* documentation says that you should use `npm install eslint --save-dev`. So, which one is better to use?
Before answering the question, let's separate the types of applications that you can build with the help of *package.json* file:
- user applications: when you use, for example, React and your application is accessible for users by the browser, we can say that it is a user application. 
- developer application: when you build a piece of code that will be used by other developers, we can assume that you are working on a developer application. For example, *react-modal* is a developer application, because the main goal of it is to reuse its functionalities in external projects.

Having this distinction in mind, we can finally answer our question. When you build a developer application, you should add all packages which your app actively uses as an imported external functionality as `dependencies`. So, when you build a modal library that uses on its foundation *react-modal*, *react-modal* should be placed in `dependencies` section. But when you use *prettier* to format your code, you should download it as `devDependencies`. The above suggestions are valid when you build a developer application, for user applications there are some changes: it doesn't matter what type of dependencies you use. This is the reason why CRA placed all external packages in `dependencies`.  

If you're observant, you probably noticed that we haven't discussed one type of dependencies yet: `peerDependencies`. I deliberately delayed explaining this because we need a distinction of application types which has just been introduced. `peerDependencies` are used for developer applications. Let's assume that our project uses package X and there is a high probability that other developers that will download our package also use X:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jc99ilnbgo90gopuekhc.png)
In that case, we can require a specific version of X package from developers who use our code. We can do it by writing: `"X": "^2.14.0` in the `peerDependencies` section. As a result, the project that uses our package won't be able to download X with a lower version. If you, for example, tried to download X in `"1.10.2"` version you will receive the error:
```
Found: x@1.10.2
node_modules/x
   x@"1.10.2" from the root project
Could not resolve dependency:
peer x@"^2.14.0" from yourProject@1.0.0
```

### Why do we need *package-lock.json*
As we mentioned earlier, when you run `npm i` you can see that *package-lock.json* file is added to your project. The main goal of this file is to store information about all dependencies and versions that your project uses. The file is able to contain nested dependencies (for example, our project is dependent on X, X on Y, and Y on Z - in *package-lock.json* all three dependencies, X, Y, and Z will be added). To understand why *package-lock.json* is so important take a look at the picture:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ar8sibtl8whhtvjjoiir.png)
We have a project that has several dependencies. If you remember the version principles, you should know that, for example, Z package can have not only `"7.8.9"` version but also all versions with minor number increased. So, let's assume, that there is a release of a new version for Z package: `"7.12.3"`. The same situation can be for our direct dependant: A package. When one user run `npm i` before a release of an updated version, the up-to-date version won't be installed. As a result, we can end up with a situation, when we use different versions of dependencies for our project (depending on when `npm i` took place):
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/as3ee1jg8zf8h0s5cb78.png)
Why might this situation be a problem? Let's assume that package A has a new functionality in version `"10.23.8"` that wasn't there before. Dorothy decided to use this new feature in a code and merged her changes to a repository. When Adam tried to run the code, there is an error - he used version `"10.11.12"`. *package-lock.json* as a name suggests, lock all versions of used dependencies so that we are sure that the entire team will use the same version of external packages. It is important that *package-lock.json* should be included in our repository to use the power of locking dependencies' versions.

### Scripts
Another thing that is specified in *package.json* file is a section called `scripts`. When you create a React application using CRA, you can see the following code:
```json
 "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
```
When you, for example, run `npm start` the command will be translated to `react-scripts start` because of the section `scripts`. There are a huge number of script's names that are supported by npm, but you are also free to specify your own names. Thanks to it, all team members that download your project with *package.json* file will be able to run the script with the custom name by using `npm run CustomNameOfTheScript`. 

## Summary
As you can see, one *package.json* contains many important things for your project. I hope that this post has made you more aware of the intricacies that may lie behind this file.