Creating a package file

Modules may be contained within a folder. Especially if you are developing a module designed to be shared with other developers, you should organize that module within its own folder, and create a package.json file within that folder.

The package.json file describes a module, usefully documenting the module’s name, version number, dependencies, etc. It must exist if you want to publish your package with npm.

In the terminal, type:

npm help json

to display detailed documentation for all available package.json fields, or visit the package.json documentation. A package.json file must conform to the JSON specification to be valid.

Adding scripts to package.json

Npm can also be used as a build tool. The scripts field in your package file allows you to set various build directives executed at some point following certain npm commands. For example, if you want to minify your Javascript code, or execute shell commands to build dependencies that your module will need whenever npm install is executed.

To clarify, when you run the directive start ( npm start ), it will automatically run the directives prestart and poststart.

The available directives are as listed:

prepublish | publish | postpublish : Run by the npm publish command as well as on local npm install without any arguments.

: Run by the command as well as on local npm install without any arguments. prepublishOnly : Run before published only on the npm publish command.

: Run before published only on the command. prepare : Run before the package is published and on npm install without any arguments. Run after prepublish , but before prepublishOnly .

: Run before the package is published and on without any arguments. Run after , but before . prepack : Run before a tarball (.tar file)is packed via npm pack or npm publish , and when installing git dependencies.

: Run before a tarball (.tar file)is packed via or , and when installing git dependencies. postpack : Run after a tarball has been generated and moved to its final location.

: Run after a tarball has been generated and moved to its final location. preinstall | install | postinstall : Run by the npm install command.

: Run by the command. preuninstall | uninstall | postuninstall : Run by the npm uninstall command.

: Run by the command. preversion | version | postversion : Run by the npm version command.

: Run by the command. preshrinkwrap | shrinkwrap | postshrinkwrap : Run by the npm shrinkwrap command (creates a package-lock.json ).

: Run by the command (creates a ). pretest | test | posttest : Run by the npm test command.

: Run by the command. prestop | stop | poststop : Run by the npm stop command.

: Run by the command. prestart | start | poststart : Run by the npm start command.

: Run by the command. prerestart | restart | postrestart : Run by the npm restart command. Note that npm restart will run the stop and start scripts if no restart script is provided.

It should be clear that pre- commands will run before, and post- commands will run after their primary command (such as publish ) is executed.

npm as a build system using custom scripts

You are not limited to the predefined script directives. Extending the scripts collection in a package file is a very common practice. Consider the following script definition:

“dev”: “NODE_ENV=development node --inspect --expose-gc index.js”

When this command is run via npm run dev, it starts the Node app in debug mode ( — inspect ), and exposes the garbage collector so that we can track its impact on our application’s performance.

In many cases you can completely replace more complex build systems like Gulp or Webpack with npm scripts. For example you might want to build your TypeScript application for deployment in a script:

“scripts” : {

"build": "tsc -p tsconfig.build.json"

}

Of course, you can make your script more complex by combining commands with && between them.

Additionally, npm scripts are running on the host system in the context of npm, so you are able to execute system commands ( mkdir , mv , cp , etc.) and address locally installed modules.

You can combine other scripts into one directive:

“prod”: “npm run build && npm run deploy”

Any number of steps can be chained in this manner. You might add tests, run a file watcher, and so forth.

Lastly, you can define custom directives for different context in the following way (by convention):

"scripts" : {

“start:dev” : “NODE_ENV=dev node main.js”,

“start:prod” : “NODE_ENV=prod node main.js”,

“start:staging” : “NODE_ENV=staging node main.js”

}

Registering package dependencies

It is likely that a given module will itself depend on other modules. These dependencies are declared within a package.json file using four related properties:

dependencies : The core dependencies of your module should reside here.

devDependencies : You may depend on some modules, while developing your module, that are not necessary to those who will use it. Typically, test suites are included here. This will save some space for those using your module.

bundledDependencies : Node is changing rapidly, as are npm packages. You may want to lock a certain bundle of dependencies into a single bundled file and have those published with your package, so that they will not change via the normal npm update process.

optionalDependencies : Contains modules that are optional. If these modules cannot be found or installed, the build process will not stop (as it will with other dependency load failures). You can then check for this module’s existence in your application code.

Dependencies are normally defined with a npm package name, followed by versioning information:

“dependencies” : {

“express” : “4.16.0”

}

However, they can also point to a tarball ( .tar file):

You can point to a GitHub repository:

They can even point to the shortcut:

These GitHub paths are also available to npm install , for example,

nest npm install nestjs

Additionally, in cases where only those with proper authentication are able to install a module, the following format can be used to source secure repositories:

Publishing and managing NPM packages

When you install Node, npm is also installed, and it functions as the primary package manager for the Node community. In order to publish to npm, you will need to create a user. You can do that with npm adduser that will trigger a series of shell prompts requesting your name, email, and password. You may then use this command on multiple machines to authorize the same user account.

To reset your npm password, visit: https://npmjs.org/forgot .

Once you have authenticated with npm, you will be able to publish your packages using the npm publish command. The easiest way is to run this command is from within your package folder. You can also target another folder for publishing (remembering that a package.json file must exist in that folder).

You can also publish a gzipped tar archive (.tar.gz file) containing a properly configured package folder.

To remove a package, use

npm unpublish <name>[@<version>]

Note that once a package is published, other developers may come to depend on it. For this reason, it is strongly discouraged to remove packages that others are using. If you want to discourage the use of a version, use

npm deprecate <name>[@<version>] <message>

To further assist collaboration, npm allows multiple owners to be set for a package:

npm owner ls <package name> : Lists the users with access to a module

: Lists the users with access to a module npm owner add <user> <package name> : The added owner will have full access, including the ability to modify the package and add other owners

: The added owner will have full access, including the ability to modify the package and add other owners npm owner rm <user> <package name> : Removes an owner and immediately revokes all privileges

All owners have equal privileges — special access controls are unavailable, such as being able to give write but not delete access (part of the open source philosophy).

Global installs and binaries

Some Node modules are useful as command-line programs. Rather than requiring > node module.js to run a program in the terminal, you can simply type > module on the console and have the program execute. In other words, we can run a module as an executable file installed on the system PATH and therefore accessible from anywhere. There are two ways to achieve this using npm.

The first and simplest way is to install a package using the -g (global) argument as follows:

npm install -g module

Another way to ensure global access is by setting the package.json ’s bin property:

“name”: “myModule”,

“bin” : {

“myModule” : “./path/to/module”

}

Please make sure that your file(s) referenced in bin starts with #!/usr/bin/env node , otherwise the scripts are started without the node executable!

When this module is installed (run npm i -g , without module identifier), myModule will be understood as a global CLI command. Any number of such programs may be mapped to bin. As a shortcut, a single program can be mapped, as shown:

“name”: “ myModule ”,

“bin” : “./path/to/module”

In this case, the name of the package itself ( myModule ) will be understood as the active command.

Other repositories

Node modules are often stored in version control systems, allowing developers to manage package code. The repository field of the package.json is mostly used to point developers to remote repositories. Consider the following example:

Similarly, you might want to point users to where bug reports should be filed using the bugs field:

Lockfiles

Ultimately, npm install is a command that takes a package.json and builds a node_modules folder from it. However, does it always produce the same one? The answer is sometimes, and we will cover the details in a bit.

If you’ve made a new project, or updated npm ( npm i -g npm@latest ), you may have noticed a new file alongside the familiar package.json -the package-lock.json .

Inside, the contents looks like this:

{ "name": "project", "version": "0.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", "integrity": "sha512- OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { "@babel/highlight": "^7.0.0" } }, ...

}

Here are the npm packages your project depends upon. Dependencies of dependencies are nested appropriately.

The real usefulness beyond package.json is delivered by the resolved and integrity fields. Here, you can see the exact file npm downloaded and unzipped to create the corresponding folder within node_modules , and, even more importantly, the cryptographically-secure hash digest of that file (a bit difference will generate a different hash).

With package-lock.json , you can now get an exact and reproducible node_modules folder, so basically to produce deterministic app dependencies. Committed into source control, you can see when a dependent module version has changed right in a diff during a code review. Also, with hashes everywhere, you can be more certain that the code your application depends upon hasn’t been tampered with (no malware code added).

You can pretty much ignore the package-lock.json . To explain how, and why, it’s helpful to expose two common questions (or exclamations) that developers commonly have when encountering the file:

When npm finds a newer version of a package, it’ll download it and update your node_modules folder. It will also update package-lock.json , with the new version number and the new hash.

If there’s a newer version of a package your project depends upon, you probably want npm install to give you the most recent one (fixed bugs).

However, what if you want npm to install a specific version? What if you want it to get the modules with exactly specific versions and hashes? The way to do this lies not in package-lock.json , but back in package.json , and deals with semantic version numbers. Take a look at these three:

1.2.3

~1.2.3

^1.2.3

1.2.3 means exactly that version, nothing earlier, and nothing later. Use npm i --save-exact <MODULE> to save the exact latest version and not later ones when running npm i.

~1.2.3 matches that version, or anything more recent. The ~ (tilde) means that you only accept patch updates (last digit) when running npm i . You could also use 1.2 or 1.2.x to limit the scope of the updates.

^1.2.3 , will bring in that version or something more recent, but stay in version 1. The ^ (caret) means you will receive new features (middle digit) and patches but nothing backward incompatible. You could also just write 1 or " 1.x".

Caret is the default when you run npm i <MODULE> . It makes sense, as a change to the first number indicates a major version which might break compatibility with previous versions, in turn potentially breaking your preceding code.

Far beyond these three common examples, there’s a whole language of comparators, operators, identifiers, tags, and ranges possible with semantic versioning and supported by npm (see the npm semver docs).