What “Bin” does in package.json?

Saravanan M
Nerd For Tech
Published in
4 min readFeb 3, 2024

--

Recently, I needed to execute a binary executable generated from one of my Haskell projects within a Node.js project. The manual process of copying the binary into my Node.js project directory and setting its executable path before running the project proved to be error-prone and inconvenient. Then I discovered that package.json provides a solution to this problem out of the box in the name of bin field

In this article, we’ll explore what cool things we can do using bin,and stay tuned for the next article, where I will explain how I made a cross-platform script by leveraging this feature.

Shall we get started then?

https://unsplash.com/s/photos/bin?orientation=landscape

Bin in a Nutshell

The bin feature allows you to execute not only bash scripts but any file (with the help of shebang) under the command name of your choice.

Still unclear on what bin does? Let’s kick things off with an example to grasp how to utilize bin before delving into its inner workings

Requirement

Suppose we have a file named welcome (note, there’s no file extension here) that simply prints the argument it receives:

#!/usr/bin/env bash

echo "Hello $1"

Note: The shebang in the first line instructs to execute this file as a bash script.

Now, let’s try to call this file in one of our JavaScript files:

const { execSync } = require('child_process');

function welcomeUser() {
const name = getCurrentUsername();
execSync(`welcome ${name}`, {stdio : 'inherit'})
}

However, calling the welcomeUser function will result in an error:

bash: welcome: command not found

But after setting it in the path and giving the script enough permission, we can run the function without any issue

export PATH = $PATH:${PWD}
chmod 777 welcome

But is this approach neat? And can we expect users to perform these setup steps when using our code as a npm package?

There’s a better way, and it is by utilizing the bin field provided by package.json

Specifying the command in bin field of Package.json

The bin field of package.json requires an object representing a map from the command name to the file it needs to execute:

{
...
bin :
{
"command-name" : "path/to/file"
}
}

In our case it would be,

bin : {
"welcome" : "./welcome"
}

Alternatively, if we only require one command and our package name is also welcome, we can simplify it:

{ name : "welcome"
, ...
, bin : "./welcome"
}

Note: Both of the above configuration enables us to execute the welcome file by running the welcome command.

Final Step — Linking

Even after specifying bin in package.json, we'll encounter the same error because simply declaring bin won't make the command available in our path. The final piece of the puzzle is executing:

npm link

Now we can execute the function without any issue.

How Bin works?

Are you wondering how declaring a command in bin is making it available in the path? where the script is getting stored? How its getting stored? Is the file getting copied to some global namespace or is it getting added directly to path or whats the magic?

Here’s a brief explanation on what goes behind the scenes.

  1. Symbolic Links

When you declare a command in the bin field, npm automatically creates a symbolic link for that command.

Symbolic links, also known as symlinks or soft links, are special files that act as pointers to other files or directories. They provide a way to access a file or directory from multiple locations in the file system without duplicating the content.

2. Linking Process

  • When you run npm link, npm creates symbolic links for all the commands specified in the bin field of your package
  • These symbolic links are placed in the appropriate directory based on your system configuration. On Unix-based systems like Linux and macOS, npm typically creates symbolic links in the /usr/local/bin directory. On Windows, it creates them in the C:\Users\{Username}\AppData\Roaming\npm directory.
  • Additionally, when someone installs your package as a dependency, npm automatically creates a symbolic link in the node_modules/.bin directory of their project.

If you are in any unix-based machine, you can test this by executing:

which welcome

This will print

usr/local/bin/welcome

By creating symbolic links with npm link, your command becomes globally available, so be cautious not to use any existing command names. However, if installed as a dependency, it’s confined to the node_modules/.bin folder within the project, accessible only via npm scripts.

Postscripts🤔

Furthermore, the versatility of the bin field extends beyond bash scripts. With the aid of a shebang (#!) in your script files, you can execute scripts written in any language.

In my next article, I will be explaining how I leveraged this bin feature and node js to execute different bash scripts depending upon the platform of my users. So stay tuned🎇️

If you have any questions or suggestions, feel free to mention them. As I’m relatively new to this feature, I’m eager to hear your feedback!

--

--

Saravanan M
Nerd For Tech

A place where I share my perspective on the tech topics I've read and enjoyed. Sometimes highly opinionated.