Setting up npm workspaces from scratch


In a university project we had the challenge to combine multiple microservices in one GitHub repository. This challenge is effectively solved by npm workspaces.

Why?

I'm going to show you how to set this tool up and how to use it. But first, lets discuss why we'd want to do that. Because having all code in one place is advantageous. You will know what your team is working on and you avoid dependency hell. This is not making your monorepo be monolithic. You rather manage multiple indiviually deployable microservices in one repository.

Setting up npm workspaces

We will start with an empty git repository and will first initalize it as an npm package. Just run the following command and answer all questions:

npm init

You should end up with a package.json file that looks somewhat similar to this:

{
  "name": "npm workspaces demo",
  "version": "0.0.1",
  "description": "Source Code for demoing the set up of npm workspaces",
}

Create the module structure for the different npm workspaces

Next lets create the module structure that we imagine for our project. I like to keep all modules neatly organized in a packages folder.

mkdir -p packages/package-a
mkdir package-b

To now initialize our workspaces we need to extend the package.json like so:

{
  "name": "npm workspaces demo",
  "version": "0.0.1",
  "description": "Source Code for demoing the set up of npm workspaces",
  "workspaces": [
    "packages/*"
  ],
}

Initialize the workspaces

For npm to understand that the subfolders in packages are not just folders, but actual workspaces, we need to let npm know.

Adding a package.json to each subfolder is key here. Create a package.json-file in each package-folder, with minimal content. Make sure to name the packages accordingly. You can choose the name freely, but remember to not name your package the same way a dependency is called. So if you depende on the npm package graphql in one of the modules, none of your modules should be called graphql. You can solve this by choosing another name or scoping your package, like explained in detail here.

{
  "name": "package-a",
  "private": "true",
  "version": "1.0.0"
}

Don't forget to create a package.json file for package-b!

You can automate workspace creation with npm init -w packages/workspace, which will query the usual package.json info and create necessary folders, as well as update your root package.json. Your setup of npm workspaces is now complete. Lets check out how to work with this.

Managing dependencies

Installing all dependencies is super easy. Just run npm install on the root level of your project and npm will automatically install global dependencies which are specified in the root package.json like prettier and all the dependencies for each module. During this installation npm lifts the dependencies up to the root folder if possible. So if two workspaces require different versions of a dependency, each workspace gets their own node_modules folder with their version of the dependency.

Adding a dependency like graphql to a single workspace can be done via npm install graphql -w packages/package-a. This will only add graphql to package-a.

Use your workspaces code in another workspace

Consuming another workspaces code is totally straightforward. Since npm symlinks all workspaces to the root node_modules folder, we can just require contents within our code and use them. There is no need to add package-a as a dependency for package-b! This is due to the way node handles module resolution.

You now know everything you need to know to get started with npm workspaces. I hope I was able to help you understand npm workspaces better.