paint-brush
How to Use Lerna to Create a Monorepo for Multiple Node Packagesby@aspecto
332 reads
332 reads

How to Use Lerna to Create a Monorepo for Multiple Node Packages

by AspectoMay 21st, 2021
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Lerna is a tool that enables us to manage (and publish) as many packages as we want in a single repository. It is usually quite easy to share code between the different parts of the monolith, just import from the relevant file. But what happens when you have 35 shared packages between 18 different microservices? It can be quite a hassle to manage all of these repos. In this post, I will walk you through how to use the tool to manage, and publish, two packages under the same monorepo.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - How to Use Lerna to Create a Monorepo for Multiple Node Packages
Aspecto HackerNoon profile picture

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 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 [email protected]: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 <[email protected]>",
 "homepage": "",
 "license": "ISC",
 "main": "lib/aloha-world.js",
 "directories": {
   "lib": "lib",
   "test": "__tests__"
 },
 "files": [
   "lib"
 ],
 "repository": {
   "type": "git",
   "url": "[email protected]: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 came up with to make this process less messy – It’s called the local router.

At this point you should have a directory structure that looks like this:

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:

1. First, create a GitHub personal access token to publish and read packages:

2. Go to https://github.com/settings/profile, 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.

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:

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!

About Aspecto

Aspecto helps developers find, fix, and prevent distributed application issues across the entire development cycle. Starting with their local dev environment, all the way to production. You can think of it as the Chrome DevTools for your distributed applications.

Our goal is to help developers troubleshoot their services faster while validating the impact of their changes, pre-production, giving them complete certainty nothing breaks before deploying to production, and allowing them to focus on innovating rather than fixing.