How to manage dependencies in a monorepo
Embarking on a personal project can be a rollercoaster ride of excitement and challenges, often punctuated by moments of both confusion and enlightenment. In my latest endeavor, I delved into the intricate world of project structures, navigating from the tangled simplicity of a monolithic single-folder approach to the organized efficiency of workspaces using Turborepo. This journey, filled with its trials and triumphs, has been a profound learning experience, reshaping my understanding of effective project management.
Through this article, I aim to share my transformative journey, highlighting the key decisions, obstacles, and revelations that marked my path from a chaotic single-folder system to the streamlined clarity of Turborepo workspaces. It's a tale of growth, resilience, and the relentless pursuit of efficiency in the ever-evolving landscape of software development.
In the early stages of my project, I decided to add everything into a single folder, a move reminiscent of a monolithic structure. However, this decision came with its own set of challenges. I encountered shared dependencies, such as a logger intended for both the front end and back end. Determined to address this, I created a 'libs' folder, envisioning it as a centralized hub for common elements. It was supposed to work but did not. Why you might ask. The libs folder had its own node_modules folder and both frontend and backend had another set of node_modules. So if I had to use something like winston a popular logging module in javascript. I had to install winston in places i wanted to use. Even though it was supposed to be dependency in libs folder.
Recognizing the need for a more nuanced approach, I opted to compartmentalize my project. This led to the creation of separate folders for each service, hoping to bring order to the chaos. Unfortunately, this only escalated the complexity of my project, prompting me to explore the use of Git submodules. In hindsight, the decision to create four distinct folders for four services, tethered by submodules, was not the panacea I had hoped for. As i had to update the submodules manually or make a ci/cd to update each submodule.
The realization struck when I acknowledged that even with Git submodules, I would still face the arduous task of rewriting code. Frustration mounting, I sought a more efficient alternative, stumbling upon node workspaces.
Workspaces emerged as the beacon of simplicity in managing dependencies. With its introduction, I could seamlessly organize my project, creating a dedicated folder for my Object-Relational Mapping (ORM). This proved to be a game-changer, as the ORM became a shared resource, effortlessly integrated into multiple subprojects. The ability to employ the ORM across various subprojects eliminated the cumbersome process of rewriting code for each service.
What are workspaces?
Workspaces, allows developers to manage multiple packages within a single repository. Workspaces enable developers to organize their codebase into separate packages, each with its own package.json
file, while still maintaining a centralized control over shared dependencies. In a workspace setup, the root directory of the project contains a package.json
file, which lists all the workspaces or packages in the project. Each workspace is a separate folder within the root directory, containing its own package.json
file that specifies its dependencies and scripts.
How do workspaces simplify dependency management?
Shared dependencies: Workspaces allow developers to define shared dependencies in the root
package.json
file. These dependencies are automatically linked to each workspace, ensuring that all packages use the same version of the dependency. This eliminates the need for manual dependency management and reduces the risk of version conflicts.Local package installation: When installing a package using npm or yarn, workspaces automatically install the package in the appropriate workspace folder, ensuring that the package is available locally for the project. This reduces the overall disk space usage and improves the performance of the build process.
Automatic linking: Workspaces automatically link packages within the project, ensuring that each package can access other packages without the need for manual installation or symlinking. This simplifies the development process and reduces the risk of errors due to misconfigured symlinks.
Tools for managing workspaces
While workspaces provide a solid foundation for managing dependencies and organizing codebases, there are several tools available that can further simplify the development process:
Lerna: Lerna is a popular tool for managing large-scale monorepo projects. It provides features such as automatic package versioning, shared dependencies, and script execution across multiple packages.
Yarn Workspaces: Yarn Workspaces is an extension of the Yarn package manager that provides additional features for managing workspaces, such as automatic linking and local package installation.
Turborepo: Turborepo is a high-performance monorepo tool that provides features such as caching, parallel execution, and remote caching. It is designed to handle large-scale monorepo projects with thousands of packages and dependencies.
I used turborepo for its as it was fast and easy to set up. The official documentation of turborepo was fast easy to understand and setting up a turborepo was as simple as running a command npx create-turbo@latest
on the cli. It has many flavors of javascript/typescript projects such Next Js and Remix or with React Native and so on.
In hindsight, my initial confusion between monorepo and monolithic architecture served as a catalyst for exploring more effective project structures. The journey from a monolithic single folder to a workspace-driven project was marked by trial and error, but the lessons learned were invaluable.
Workspaces not only addressed my immediate concerns but also paved the way for a more scalable and maintainable project structure. The ability to manage dependencies seamlessly not only simplified the development process but also enhanced the overall efficiency of my project.
As developers, embracing new tools and methodologies is crucial for staying ahead in the ever-evolving landscape of software development. My experience with workspaces serves as a testament to the importance of adaptability and the constant pursuit of more efficient solutions.
In retrospect, my journey from the monolithic confusion of a single folder to the organized world of workspaces has been an enlightening one. It taught me the importance of choosing the right tools and structures for efficient project management. Workspaces, particularly those managed with tools like Turborepo, not only resolved my immediate challenges but also set a foundation for a scalable and maintainable approach to handling complex projects. This experience has underscored the value of adaptability and the continuous search for better solutions in the dynamic field of software development. As I share this journey, I hope it serves as an inspiration for others to explore, learn, and adapt, finding their path to streamlined project management and enhanced productivity.