Core element of Node.js ecosystem - package.json
Posted
Updated
Europe’s developer-focused job platform
Let companies apply to you
Developer-focused, salary and tech stack upfront.
Just one profile, no job applications!
The package.json
file is a core element in the Node.js ecosystem and is basic for understanding and working with Node.js, NPM, modern JavaScript and JavaScript frameworks and libraries.
The package.json is used as a manifest about applications, modules and packages. It's a tool to make modular and efficient applications.
This article is based on Node v16.14.0.
💰 The Pragmatic Programmer: journey to mastery. 💰 One of the best books in software development, sold over 200,000 times.
Understanding the basics of package.json
is essential, so I outline in the following article the most common and important properties of the package.json file.
Let's start with initiating a project to create a basic package.json
.
Create Folder mkdir super-mario
and run npm init
(option with the -y
flag to answer yes to every question).
This command will create this package.json
:
{
"name": "super-mario",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Let's go over the properties from the generated package.json
.
This is the name for the project and is optional if the project is private. When a npm package is published, the name will be used as an URl. Hence, when the package gets published the name is required and must be unique on the npm repository. It has requirements to be URL-safe. It should be:
A good value if you have publicly published this package on Github is the repository name.
A version number that should be understandable by node-semver. The version property is optional for private and required for public modules.
A brief description for the project, optional for both, private and public, but useful if you publish your package to npm to provide more information.
The entry file/entry point for the package. When you import this package in an application, that’s where the application will search for the module exports.
The scripts key expects an object with script names as keys and commands as values, so it basically defines a set of node scripts you can execute.
These scripts are command line applications. You can run them by calling npm run COMMAND
or yarn COMMAND
.
When we have a look at the package.json
above, we see the test
key in the scripts.
"test": "echo \"Error: no test specified\" && exit 1"
So we can run npm run test
in the commandline and would get Error: no test specified
logged.
There are no limitations in the command name and scripts can do things like starting your project on a local server, building for production or running your tests.
Typically, in the scripts commands you'll make the most manual changes in your package.json
file.
An array of keywords to helps with finding the module on the npm repository.
This property lists the package author name and expects an object with keys for the name, email and url.
Possible formats are:
"author": "Mario Kandut <[email protected]> (https://www.mariokandut.com)"
"author": {
"name": "Mario Kandut",
"email": "[email protected]",
"url": "https://www.mariokandut.com"
}
Indicates the license of the package and expects a license name using its SPDX identifier. It defaults to the ISC license, and MIT would be another popular license choice. You can also use UNLICENSED for projects that are private and closed-source.
There are many more properties in the package.json
than generated by npm init
.
This property defines where the source code of the package repository is located. Typically, this would be a public GitHub/Gitlab repo, with the repository array noting that the type of version control is git, and the URL of the repo itself.
The repository property would look like this:
"repository": {
"type": "git",
"url": "https://github.com/mariokandut/REPOSITORY-NAME.git"
}
You can also use the prefix for github
or gitlab
.
"repository": "github:mariokandut/REPOSITORY-NAME"
"repository": "gitlab:mariokandut/REPOSITORY-NAME"
One of npm’s main strength is the ability to easily manage project’s dependencies.
Hence, the package.json
centers mostly around specifying the dependencies for a project.
There are the regular dependencies, but there can also be devDependencies, peerDependencies, optionalDependencies and bundledDependencies.
The dependencies property is where dependencies, the other modules that this module uses, are defined. It takes an object that has the name and version at which each dependency should be used. You'll frequently find carets (^) and tildes (~) included with package versions, these are the notation for version range.
The dependencies property could look something like this:
"dependencies": {
"async": "^0.2.10",
"npm2es": "~0.4.2",
"optimist": "~0.6.0",
"request": "~2.30.0",
"skateboard": "^1.5.1",
"split": "^0.3.0",
"weld": "^0.2.2"
}
When you install a package using npm or yarn, npm install <PACKAGENAME>
or yarn add <PACKAGENAME>
,
that package is automatically inserted in this list.
"dependencies": {
"react": "^16.10.2",
"react-dom": "^16.10.2",
"react-helmet": "^5.2.1",
}
The devDependencies property is almost identical to the dependencies property, when it comes to structure. There is one key difference. The dependencies property is used to define the dependencies that a module needs to run in production. The devDependencies property is used to define the dependencies the module needs to run in development.
Example:
"devDependencies": {
"@types/react-helmet": "^6.1.0",
"@types/react-typist": "^2.0.1",
"@typescript-eslint/eslint-plugin": "^4.1.0",
}
When you install a package using npm or yarn, npm install --dev <PACKAGENAME>
or yarn add --dev <PACKAGENAME>
,
that package is automatically inserted in this list.
In some cases, you want to express the compatibility of your package with a host tool or library,
while not necessarily doing a require
of this host.
This is usually referred to as a plugin.
Notably, your module may be exposing a specific interface, expected and specified by the host documentation.
For example:
{
"name": "tea-latte",
"version": "1.3.5",
"peerDependencies": {
"tea": "2.x"
}
}
This ensures your package tea-latte can be installed along with the second major version of the host package tea only.
If a dependency can be used, but you would like npm to proceed if it cannot be found or fails to install,
then you may put it in the optionalDependencies object.
This is a map of package name to version or url, just like the dependencies object.
The difference is that build failures do not cause installation to fail.
Running npm install --no-optional
will prevent these dependencies from being installed.
It is still your program's responsibility to handle the lack of the dependency. For example, something like this:
try {
var foo = require('foo');
var fooVersion = require('foo/package.json').version;
} catch (er) {
foo = null;
}
if (notGoodFooVersion(fooVersion)) {
foo = null;
}
// .. then later in your program ..
if (foo) {
foo.doFooThings();
}
Entries in optionalDependencies will override entries of the same name in dependencies, so it's usually best to only put in one place.
This defines an array of package names that will be bundled when publishing the package.
In cases where you need to preserve npm packages locally or have them available through a single file download, you can bundle the packages in a tarball file by specifying the package names in the bundledDependencies array and executing npm pack.
For example:
{
"name": "awesome-web-framework",
"version": "1.0.0",
"bundledDependencies": ["renderized", "super-streams"]
}
There are more configurations that can optionally go into your project’s package.json file:
You can specify the version of node that your stuff works on:
"engines": {
"node": ">=0.10.3 <15"
}
And, like with dependencies, if you don't specify the version (or if you specify "*" as the version), then any version of node will do.
You can also use the "engines" field to specify which versions of npm are capable of properly installing your program. For example:
"engines": {
"npm": "~1.0.20"
}
Unless the user has set the engine-strict config flag, this field is advisory only and will only produce warnings when your package is installed as a dependency.
A config
object can be used to set configuration parameters used in package scripts that persist across upgrades.
For instance, if a package had the following:
{
"name": "foo",
"config": {
"port": "8080"
}
}
Then with a start
command that referenced the npm_package_config_port
environment variable.
A user could override that by doing npm config set foo:port 8001
.
If you set "private": true
in your package.json, then npm will refuse to publish it.
This is a way to prevent accidental publication of private repositories.
If your module is meant to be used client-side the browser field should be used instead of the main field.
This is helpful to hint users that it might rely on primitives that aren't available in Node.js modules. (e.g. window
)
The URL for the home page of the project.
A URL where issues and bugs can be reported. This will often be an URL to the Github issues page for a project.
package.json
file acts like a manifest for your application.Thanks for reading and if you have any questions, use the comment function or send me a message @mariokandut.
If you want to know more about Node, have a look at these Node Tutorials.
Never miss an article.