Node.js package.json

Chapter 6 19 mins

Learning outcomes:

  1. What is package.json
  2. Creating package.json manually
  3. Automating package.json's creation via npm init
  4. The package-lock.json file

The birth of npm

In its nascent days, when developers from all over the globe were actively discussing new paths to pave for Node and make it a better and more powerful technology, it's not surpising to know that much of this came in the form of creating helpful utilities using it.

Some people were coming up with database drivers, some were coming with with generalized utilities for JavaScript, some were adding in HTTP proxies, and so on. It was a really healthy and prolific time for Node to be in, and definitely a reason for its ultimate success in the years to come.

However, a big issue with this exchange of programs in these early times was that there was no centralized place for developers to publish their packages and for others to easily install them.

Node's mailing lists were full of zip files that interested developers had to manually download, unzip, make (a terminal command), then integrate with their applications in order to see them in action — in short, do just about every single thing manually.

As you can expect, this approach was fraught with concern by some developers.

Finally, Isaac Schlueter, a developer involved in the early development stages of Node, decided to solve the problem. He introduced a centralized package management system for Node. And thus born npm.

npm put forth the idea of documenting and describing a given Node package with the help of a JSON file — or better to say, a metafile — referred to as package.json.

Let's explore more about this mere JSON file, the cornerstone of the Node ecosystem.

What is package.json?

From a distant view, package.json is the very cornerstone of the entire ecosystem that Node sits within. It's the bread and butter of Node.

It's the heart of Node. It's the ... OK, let's get to the point.

package.json is a JSON file describing a Node.js package.

As discussed above, and as we shall explore in depth in the next chapter, npm allows Node developers to publish their packages for others to use and, conversely, to use packages written by others.

Certainly, there needs to be some way to be able to describe what a package does what it relies upon (the packages that it uses itself), who wrote it (the name of the author), and other suchlike details.

package.json is the way all of this metadata of a package is conveyed to another party that gets the package.

npm itself relies extremely upon package.json. It uses it to:

  • Understand which packages to install when a third party installs a package on their end.
  • Realize which file to execute when a package is imported (either in the CommonJS style or the ESM style); and much more.
  • Execute certain helpful commands configured by the developer. These are referred to as npm scripts.
  • ...and do many more things.

As we'll find out below, package.json defines an object containing numerous properties, each of which deals with one aspect of describing the underlying package.

For instance, the property name states the name of the package, used as a unique identifier by npm in its humongous collection of over a million packages.

Similarly, the property type (as we learnt in the previous chapter) specifies the type of the module system to be used when importing .js files in the package. The possible values are "commonjs" for CommonJS modules and "module" for ECMAScript modules.

There is no required property in package.json unless we intend to publish it to npm (in which case, name and version are required). It's completely valid to have an empty object in the file.

Choosing JSON as the file format for package.json

Quite obviously, the reason for choosing JSON for package.json was its easy of use and because ... well ... it was JavaScript.

And to address the question as to why wasn't JavaScript itself used, there wasn't really a need for control flow and programming abilities for describing a Node project; it was all just information, information and information, and that suited well to the textual nature of JSON.

Essentially, every single Node package — and not just a package but simply any Node project, including our learning-node project — must contain a package.json file.

But how to create one, and where? Fortunately, it's all plain sailing.

Creating package.json

There are two ways to create a package.json file in a Node project:

  • Manual creation
  • Use npm init

Let's explore both of these...

Manually creating package.json

Because it's just a JSON file, and the JSON format is pretty elementary, creating package.json is a task that can well be done manually.

Let's do this for our learning-node Node project.

Go on and create a new file from the Explorer menu in VS Code and name it package.json, as shown below:

package.json created in VS Code Explorer menu
package.json created in VS Code Explorer menu

We'll add in the name of our project (which we choose ourselves) and also its module type (CJS or ESM). The name corresponds to the name property whereas the module type corresponds to the type property:

{
   "name": "learning-node",
   "type": "module"
}
  • The name property is simply set to the name of the project's directory. Although there's no strict necessity of doing so, this is a common convention. Note that name must be in lowercase, without any spaces or special characters.
  • The type property is set to "module" which means ... you got it ... that the default module system Node will use while executing .js files is the ECMAScript module system.

As this basic example demonstrates, package.json is being used both to document our project as well as configure the way it is exectued by the Node runtime.

This is the power that package.json holds — it's much more than just a means of describing a project.

Anyways, now, while a lot of things in package.json can, and can only, be done manually, NOT everything follows this rule. There are certain things that are impractical, if not impossible, to be done manually in package.json.

The best example of this would be to install a package (or equally, uninstall it) — we'd rather use npm to install a package for our project and then let npm update package.json with the respective package information than do this manually (which is completely impractical).

Using npm init

Of course, creating a new Node project isn't just about its name or module type. There are numerous other pieces of information, and crucial information, that we can state in package.json.

However, manually adding all these details every single time we create a new Node project can be repetitive (a programmer's nightmare) and cumbersome.

The good news is, we don't have to do so, for we have a command line tool to help us do this automatically or at least simplify it to a great extent. It is npm init.

As per its name, the npm init command is used to initialize a new Node project.

Step one is to create the directory for the project and then navigate to it in the terminal before running npm init. npm init automates the process of writing a package.json file for the project, in the project's directory.

So following this, let's first navigate to our learning-node directory in the terminal:

.../learning-node>

As is evident here, the input prompt clearly labels our current working directory (the directory in which we currently are) as learning-node.

Now, enter npm init in the terminal:

.../learning-node>npm init

This begins the process of npm gathering details from us in order to add them to the package.json file it'll eventually create in the project's directory.

Questions such as the package's name, the author's name, the package's license, etc. are asked from us. We need to appropriately answer them or leave them empty in which case npm uses their default values (shown in brackets after each question).

In our case, we press the Enter key at every stage without entering a value since we're comfortable with the default values, at least for now:

.../learning-node>npm init

Once all the questions are answered, npm init displays the generated content that it'll soon dump into package.json, asking for our confirmation to proceed with the file's creation.

npm init running in the terminal
npm init running in the terminal

Once we give a green signal to it, it finally creates the package.json file and exits.

Here's the package.json file we get:

{
   "name": "learning-node",
   "version": "1.0.0",
   "description": "",
   "main": "index.js",
   "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
   },
   "author": "",
   "license": "ISC"
}

Let's quickly go through each of these properties:

  • name specifies the name of the project, as we learned before.
  • version specifies the version of the project. We'll understand versioning later on in this course and explore the most common approach to versioning in Node, i.e. semantic versioning.
  • description gives a little description of the project, for e.g. 'Learning Node by creating stuff.' is a good description of our learning-node project.
  • main represents the entry point into the package. This is used when Node calls on to the package (either because of an import in another package or by virtue of invoking the package as a global utility. More on this later in this course.)
  • scripts specify npm scripts for simplifying certain commands down npm run commands. We'll learn more about npm scripts in the next chapter.
  • author specifies the name of the author.
  • license specifies the license of the package. Software licensing is just a completely different topic to explore; we'll get to it quite later in this course when we're ready to ship and publish our packages for other developers to use.

And that's pretty much it for the npm init command in these beginning stages.

Once the package.json file is created, we're free to modify it manually as we wish to.

In the following snippet, we add in the type property and set it "module" since we need the default module system in our project to be ESM:

{
   "name": "learning-node",
   "version": "1.0.0",
   "description": "",
   "main": "index.js",
   "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
   },
   "author": "",
   "license": "ISC",
"type": "module"
}
As one can expect, the order of properties in package.json doesn't matter.

Including the -y flag for skipping question prompts

If all we need to do after running npm init is pressing Enter on the keyboard to accept the default value for every question asked to us, then we're better off at skipping the entire questionnaire entirely.

This can easily be done by adding in the -y flag after the npm init command, as follows:

>npm init -y

-y stands for 'yes' and instructs npm to go with the default value for every question.

The package-lock.json file

Besides package.json, you might also come across a similar metadata file in Node projects but one that doesn't document any other aspect of those projects except for the packages they depend on.

This file is called package-lock.json.

A more recent feature introduced to Node in contrast to package.json:

The package-lock.json file is used to lock in the entire graph of the exact dependencies of a given Node package.

Where package.json only specifies the direct dependencies of a Node package, package-lock.json specifies the direct dependencies of the package as well as those of its dependencies, those of their dependencies, and so on.

Furthermore, where package.json might specify a range of acceptable dependencies (we'll see how later in this course), package-lock.json specifies the exact dependencies.

As an example, package.json might specify that our Node project depends on a package called express having a version greater than 4.2.0 (with the second digit incrementing only), and that's it; our package.json file won't go beyond this in, let's say, specifying deeper packages required by express itself.

Comparing this to package-lock.json, it would specify the exact version of express installed in our Node project, let's say 4.18.0, and the exact version of every package installed in express (in our project), and the exact version of every package installed in every package therein, and so on.

Effectively, package-lock.json locks the current state of all the dependencies of a project so that if we transfer the package-lock.json file itself to another computer, we're able to install everything exactly the way we had it on our original system.

With package.json, npm might install newer versions of packages in a project; package-lock.json makes sure that we install precisely the version we have right now, elsewhere.

This naturally means that package-lock.json is much more bulkier than package.json as it accounts for the dependencies of every single package in a given Node project until there are no more dependencies to go over.

Moreover, this also means that package-lock.json is definitely NOT something we'll ever create on our own. It's a file exclusively meant to be created and used by a package manager.

Don't worry if you're unable to understand the intuition behind package-lock.json right now. It'll make much more sense once you understand the idea of dependencies in Node.

"I created Codeguage to save you from falling into the same learning conundrums that I fell into."

— Bilal Adnan, Founder of Codeguage