Aspecto blog

On microservices, OpenTelemetry, and anything in between

Lerna Hello World: How to Create a Monorepo for Multiple Node Packages

How to create a monorepo for multiple node packages

Share this post

Share on facebook
Share on twitter
Share on linkedin

This article is part of the Aspecto Hello World series, where we tackle microservices-related topics for you. Our team searches the web for common issues, then we solve them ourselves and bring you complete how-to guides. Aspecto is an OpenTelemetry-based distributed tracing platform for developers and teams of distributed applications.

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 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.

P.S: If you use Lerna to share code between microservices, chances are you also need to understand and troubleshoot the complex relationships between them. That’s what we help with at Aspecto (for free). Check us out and get set up within a few minutes.

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:

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.

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!

Spread the word

Share on facebook
Share on twitter
Share on linkedin
Subscribe for more distributed applications tutorials and insights that will help you boost microservices troubleshooting.