Monorepo with JavaScript: Code Organization and Best Practices
Modern JavaScript (Node.js) development often faces the challenge of managing code in large projects. When the number of microservices, libraries, and applications grows, traditional multi-module repositories (Polyrepo) start creating chaos: configuration duplication, version synchronization difficulties, and slow CI/CD. The solution is a monorepo. In this article, we will examine how to organize code in a JavaScript monorepo, which tools to use, and how to avoid common mistakes.
What is a monorepo and why is it needed?
A monorepo is an approach where all project code (multiple applications, libraries, services) is stored in a single repository. Unlike Polyrepo, where each component lives in its own Git repository, a monorepo unifies everything into a single structure.
Advantages of a monorepo for JavaScript:
- Single version of dependencies — all packages use the same library versions, eliminating conflicts.
- Code reuse — shared utilities and components are easily imported between sub-projects without publishing to npm.
- Atomic commits — changes across multiple packages can be made in a single commit, simplifying review and rollback.
- Simplified CI/CD — one pipeline for the entire project, with caching of builds and tests.
However, a monorepo also has drawbacks: it requires strict discipline in code organization and the right choice of tools. Next, we will examine how to organize the structure and which tools to use.
Tools for managing a monorepo in Node.js
The JavaScript ecosystem offers several mature tools for working with monorepos. Let's look at the three most popular ones:
1. Lerna
A classic tool that was the standard for a long time. Lerna manages package publishing, versioning, and running scripts across all packages. Example lerna.json configuration:
{ "version": "independent", "packages": ["packages/*"], "npmClient": "yarn"}The lerna bootstrap command installs dependencies for all packages and links them together.
2. Nx (Nrwl)
A more modern and powerful tool with an advanced caching system, dependency graphs, and code generators. Nx supports not only JavaScript but also TypeScript, Angular, React, Next.js. Example command for creating a library:
npx nx generate @nrwl/node:library my-lib --directory=sharedNx automatically analyzes dependencies and rebuilds only the changed packages.
3. Turborepo
Created by Vercel, Turborepo focuses on build speed and simplicity. It uses smart caching and parallel task execution. Example turbo.json:
{ "pipeline": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, "test": { "dependsOn": ["build"], "outputs": [] } }}Turborepo is ideal for projects with a large number of packages where CI speed is important.
Organizing the project structure in a monorepo
A proper folder structure is the foundation of a successful monorepo. Let's look at a typical structure for a Node.js project:
my-monorepo/├── packages/│ ├── shared-utils/ # Shared utilities│ │ ├── src/│ │ │ └── index.js│ │ └── package.json│ ├── api-gateway/ # API gateway│ │ ├── src/│ │ │ └── server.js│ │ └── package.json│ └── web-app/ # Web application (React/Vue)│ ├── src/│ │ └── App.js│ └── package.json├── tools/ # Scripts and configurations