Building a JavaScript Monorepo with Lerna

Erzhan Torokulov
JavaScript in Plain English
4 min readJan 6, 2019

--

JavaScript nowadays is almost everywhere: on the backend, frontend, desktop, mobile, tooling, etc.

If your project consists of multiple JavaScript repositories, now, it’s much better to move them into a single/mono repository and control them using Lerna.

What is Lerna?

Lerna is a tool for managing JavaScript projects with multiple packages.

I recommend you take a look at lerna commands before we proceed.

Why Monorepo?

Monorepo is NOT about mixing everything together inside a project(do not confuse with monolith).
Monorepo is about having source codes of multiple applications, services, and libraries that belong to one domain inside a single repository.

Note: Monorepo can be organized in any comfortable way, directory tree, it’s up to developer/team.

Pros:

  • Fixed versioning for the whole system (apps/services/libraries).
  • Cross-project changes. For instance, a feature that changes multiple parts of the system (libraries/services/apps) can be done in one Pull Requests.
  • Single clone. No need to clone several repositories.
  • Access to all parts of the system. Simplified refactoring of the whole system.

Cons:

  • Version control system performance downside.
  • Security. Access to all parts of the system by all developers.

Requirements

  • Node.JS version 8 or above

Get Started

For the purpose of this example, we will create a simple app consisting of:

  • API: API service (backend)
  • frontend: frontend/web app

Also, in order not to mix all logic together, we’ll create separate packages:

  • validator: custom validation library/package
  • logger: custom logging library/package

The overall file structure of our monorepo project would be:

packages/        # directory for our custom libraries
../validator # custom validation helpers
../logger # custom logger library
apps/ # directory for our apps/services
../api # API backend
../frontend # frontend/web

1. Initialize Monorepo

# install lerna globally
npm i -g lerna
# create a new dir for project
mkdir my-project && cd my-project
# initialize a new lerna managed repository
lerna init

and Edit lerna.json

{
"packages": [ "packages/*", "apps/*"],
"version": "0.1.0"
}

Note: We’ll use npm scoped package naming for all our apps and packages.
Example: @my-project/{package-name}

Let’s start with library packages.

2. Create a "validator" library package

  1. Create and initialize @my-project/validator package:
# create library directory and cd inside
mkdir -p packages/validator && cd packages/validator
# initialize library with scope name
npm init --scope=my-project --yes

2. Add packages/validator/index.js with the following content:

3. Create a “logger” library package

  1. Create and initialize @my-project/logger package:
# From the root directory of the repository# create library directory and cd inside
mkdir -p packages/logger && cd packages/logger
# initialize library with scope name
npm init --scope=my-project --yes

2. Add packages/logger/index.js with the following content:

4. Create an “api” application package

  1. Create and initialize @my-project/api package:
# From the root directory of the repository# create app directory and cd inside
mkdir -p apps/api && cd apps/api
# initialize app with scope name
npm init --scope=my-project --yes
# install express
npm i express --save
# add our logger library as dependency to our api app
lerna add @my-project/logger --scope=@my-project/api

2. Add apps/api/index.js file:

3. Add start script to apps/api/package.json :

"scripts": {
"start": "node index.js"
...
}

4. Run app: npm start and open http://localhost:8080/greeting

5. Create a “frontend” application package

  1. Create @my-project/frontend using create-react-app:
# From the root directory of the repository# create frontend app using create-react-app
cd apps && npx create-react-app frontend

2. Edit apps/frontend/package.json :

{
"name": "@my-project/frontend",
...
"proxy": "http://localhost:8080"
}

3. Add our validator as a dependency to our frontend.

# Add validator library as a dependency to frontend 
lerna add @my-project/validator --scope=@my-project/frontend

4. Add apps/frontend/src/Greeting.js :

5. Add <Greeting /> somewhere inside apps/frontend/src/App.js :

...
import {Greeting} from './Greeting';
class App extends Component {
...
render() {
return (
<div className="App">
<header className="App-header">
...
<Greeting />
</header>
</div>
);
}
}
...

6. Run the frontend app: npm start and open http://localhost:3000 .

Conclusion

As you can see, a mono repository can contain as many apps, libraries as needed for the project. The important thing is to keep everything loosely coupled:

  • One app/service/library -> One package

Common logic, utils or helpers can be placed in a separate package. Thus, we can build highly independent packages, which is much simpler to understand, maintain and refactor.

See full source code on Github.

Also, you can look at the more advanced monorepo example here.

A note from JavaScript In Plain English

We have launched three new publications! Show some love for our new publications by following them: AI in Plain English, UX in Plain English, Python in Plain English — thank you and keep learning!

We are also always interested in helping to promote quality content. If you have an article that you would like to submit to any of our publications, send us an email at submissions@plainenglish.io with your Medium username and we will get you added as a writer. Also let us know which publication/s you want to be added to.

--

--