DEV Community

Cover image for Lerna Hello World: How to Create a Monorepo for Multiple Node Packages
Tom Weiss for Aspecto

Posted on • Updated on • Originally published at aspecto.io

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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

hello-world.js

'use strict';

module.exports = helloWorld;

function helloWorld() {
 console.log('Hello World');
}
Enter fullscreen mode Exit fullscreen mode

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"
 }
}
Enter fullscreen mode Exit fullscreen mode

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, came up with to make this process less messy – It’s called the local router.

How to Route Traffic Between Microservices During Development

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

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.
Enter fullscreen mode Exit fullscreen mode

Let’s make our initial commit to GitHub.

git add .  
git commit -m 'Initial commit'
git push -u origin master
Enter fullscreen mode Exit fullscreen mode

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

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/
Enter fullscreen mode Exit fullscreen mode

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/ 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Create an index.js file:

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

helloWorld();
alohaWorld();
Enter fullscreen mode Exit fullscreen mode

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"
 }
}
Enter fullscreen mode Exit fullscreen mode

Then, run node index.js and you should get the following output:

$ node index.js
Hello World
Aloha World
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (2)

Collapse
 
yilmazbingo profile image
bingolusa

I published code to npm but it does not push README.md. So how can I add readme.md. I also asked this question on stackoverflow. stackoverflow.com/questions/691743...

i have been tryiung to figure out how to solve this so i can finish up the package.

Collapse
 
tomweiss profile image
Tom Weiss

Hard to say your exact situation since I don't have all the context, but it seems like you put the readme in the wrong place (from your stack overflow question).
README.md file needs to be at the package folder, not at the root folder. cause every package should have its own readme.
See how it's located in the screenshot above where it shows the folders.

Hope that helps, best of luck!