Lerna Hello World: How to Create a Monorepo for Multiple Node Packages
Tags: node, tutorial, webdev, javascript
Published: 2021-05-12 14:05:26

In this post, I will walk you through how to use Lerna to manage, and publish, two packages under the same monorepo. Publishing will be done to my private GitHub repository under the GitHub packages registry.

I decided to keep it as simple as possible, Lerna-only. No yarn workspaces to be found here.

##Intro & Motivation For Using Lerna##
Using a monolith, you have a single code base.

It is usually quite easy to share code between the different parts of the monolith, just import from the relevant file.

When it comes to microservices, however, by definition – you would have more than one microservice.

Most likely, you would have shared logic between the microservices, whether it is for everyday authentication purposes, data access, etc.

Then, one might (rightfully) suggest – let’s use a package. Where do you store that package? Yet another repo. 

So far so good, but what happens when you have 35 shared packages between 18 different microservices? 

You’d agree that it can be quite a hassle to manage all of these repos.

That is the part where [Lerna](https://lerna.js.org/) comes in.

A tool that enables us to manage (and publish) as many npm packages as we want in a single repository.

##1. Github Repository Creation##
Create a new private github repository (I called mine learna but call it as you see fit).

##2. Install Lerna & Setup the Project Locally##
In order to set up Lerna in our project, we first need to install it globally, create a git repository locally and run lerna init:
```
npm install --global lerna
git init learna && cd learna
lerna init
```
*Note:* there are two modes for initializing the Lerna repo independent and fixed. We’re going to use the default one for simplicity reasons. Essentially what it means is all version numbers are tied together and managed in top-level lerna.json. 

Read more about it here: https://github.com/lerna/lerna#how-it-works

Now let’s link this to our GitHub repository (replace names accordingly):
```
git remote add origin git@github.com:aspectom/learna.git
```
##3. Create Lerna managed packages##
Create two packages, hello-world and aloha-world (with the default options):
```
lerna create hello-world
lerna create aloha-world
```
`lerna create` is Lerna’s way to help us create packages managed by a Lerna initialized repo.

Inside both of the packages, modify the corresponding js files to have them greet as we want them to:

*aloha-world.js*
```
'use strict';

module.exports = alohaWorld;

function alohaWorld() {
 console.log('Aloha World');
}
```
*hello-world.js*
```
'use strict';

module.exports = helloWorld;

function helloWorld() {
 console.log('Hello World');
}
```
Now we have to make a modification in our package.json to contain the GitHub username of our account / organization:
```
{
 "name": "@aspectom/aloha-world",
 "version": "0.0.0",
 "description": "> TODO: description",
 "author": "Tom Z <tom@aspecto.io>",
 "homepage": "",
 "license": "ISC",
 "main": "lib/aloha-world.js",
 "directories": {
   "lib": "lib",
   "test": "__tests__"
 },
 "files": [
   "lib"
 ],
 "repository": {
   "type": "git",
   "url": "git@github.com:aspectom/learna.git"
 },
 "scripts": {
   "test": "echo \"Error: run tests from root\" && exit 1"
 }
}
```
Do this for both aloha-world and hello-world, and make sure to replace my GitHub username with your own.

PS: While we’re making managing multiple repos easier, here’s how you can make running multiple microservices locally feels like a walk in the park. It’s a simple, easy-to-use hack we, at [Aspecto](https://www.aspecto.io/), came up with to make this process less messy – It’s called the [local router](https://www.aspecto.io/blog/easy-way-to-route-traffic-between-microservices-during-development/?utm_source=dev.to&utm_medium=post&utm_campaign=lerna-hello-world-how-to-create-a-monorepo-for-multiple-node-packages).

[![How to Route Traffic Between Microservices During Development](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3ubszuzm7isd8fbn0bg.png)](https://www.aspecto.io/blog/easy-way-to-route-traffic-between-microservices-during-development/?utm_source=dev.to&utm_medium=post&utm_campaign=lerna-hello-world-how-to-create-a-monorepo-for-multiple-node-packages)

At this point you should have a directory structure that looks like this:
![directory structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u5rwkn400yeckkg7zp2q.png)

At the root of the repository, add an empty LICENSE.md.

This will be necessary later to avoid this error when publishing:

```
lerna WARN ENOLICENSE Packages aloha-world and hello-world are missing a license.
lerna WARN ENOLICENSE One way to fix this is to add a LICENSE.md file to the root of this repository.
lerna WARN ENOLICENSE See https://choosealicense.com for additional guidance.
```
Let’s make our initial commit to GitHub.

```
git add .  
git commit -m 'Initial commit'
git push -u origin master
```

##4. Generating a GitHub Personal Access Token##
First, create a GitHub personal access token to publish and read packages:

1. Go to https://github.com/settings/profile, 
2. Click on developer settings
3. Click on personal access token
4. Select write & read packages, which should also mark the repo automatically
5. Add a note so that you remember what it’s about and click on generate the token.

![Generating a GitHub Personal Access Token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5tp52zh2s79dkci58ntg.png)

Now, go to your .npmrc file and add the following lines (can be local .npmrc in each repo or global ~/.npmrc, but beware – better to not commit this file):

```
//npm.pkg.github.com/:_authToken=TOKEN
@aspectom:registry=https://npm.pkg.github.com/
```
***Do not forget*** to replace TOKEN with the token you have just created, and aspectom with your own GitHub account.

##5. Publishing The Packages to GPR##
Now let’s publish these packages to the GitHub package registry so that we can use them in a different project:
```
lerna publish --registry=https://npm.pkg.github.com/ 
```

If you had the following error, you probably omitted the registry part from lerna publish:

```
? Are you sure you want to publish these packages? Yes
lerna info execute Skipping releases
lerna info git Pushing tags...
Enter passphrase for key '/Users/tom/.ssh/aspecto_id_rsa': 
lerna info publish Publishing packages to npm...
lerna info Verifying npm credentials
lerna http fetch GET 401 https://registry.npmjs.org/-/npm/v1/user 1370ms
401 Unauthorized - GET https://registry.npmjs.org/-/npm/v1/user
```

Since it tries to go to npm registry instead of GitHub packages.

And if you had this error:
```
lerna info publish Publishing packages to npm...
lerna notice Skipping all user and access validation due to third-party registry
lerna notice Make sure you're authenticated properly ¯\_(ツ)_/¯
lerna http fetch PUT 404 https://npm.pkg.github.com/hello-world 694ms
lerna ERR! E404 404 Not Found - PUT https://npm.pkg.github.com/hello-world
```

You probably forgot to use @YOUR_GITHUB/package-name in one of your package.json files under the “packages” folder.

In my case – it was the hello-world package.

After resolving issues (if any) you should receive a success message, and looking at the repository you can see you have 2 packages:

![Two packages](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bar114sjqaseyjkz72xb.png)

Any time you want to publish, you have to make a change and commit it otherwise lerna will say that there’s no change.

You can make the change or force Lerna to publish by adding `--force-publish` to the `lerna publish` command, like this:
```
lerna publish --registry=https://npm.pkg.github.com/ --force-publish
```
##6. Using The Packages in a Different Project##
First, create a project to consume the aloha-world and hello-world packages:

```
mkdir use-lerna-repo
cd use-lerna-repo/
yarn init
```

Assuming you’ve used global .npmrc, no further steps needed to consume the packages with yarn or npm install.

If you used local npmrc in your lerna repo, copy it to the use-lerna-repo root folder.

```
yarn add @aspectom/aloha-world
yarn add @aspectom/hello-world
```
Create an index.js file:

```
const helloWorld = require('@aspectom/hello-world');
const alohaWorld = require('@aspectom/aloha-world');

helloWorld();
alohaWorld();
```

Package.json for this project: 

```
{
 "name": "use-lerna-repo",
 "version": "1.0.0",
 "main": "index.js",
 "license": "MIT",
 "scripts": {
   "start": "node index.js"
 },
 "dependencies": {
   "@aspectom/aloha-world": "^0.0.4",
   "@aspectom/hello-world": "^0.0.4"
 }
}
```
Then, run node index.js and you should get the following output:

```
$ node index.js
Hello World
Aloha World
```

And voila! We have just finished creating, publishing, and consuming our lerna-managed packages in the one monorepo.

Good luck, we at Aspecto wish you years of happy packaging and a lot of [downloads](https://github.com/aspecto-io)!