Intro
Hello everyone, my name is Stas Kohut. I'm a frontend developer and have been doing frontend for 8 years. During those years, I participated in numerous projects using different frontend technologies. But recently, I have stuck to the React ecosystem, so I'll speak from React developer's perspective in today's post. However, it can be applied to any frontend technology so stick around.
A little disclaimer: I'll try to tell a story rather than read it as a manual. It's not a tutorial! Given that there won't be any code examples. My goal is to raise awareness of the technologies and maybe spark someone's interest to do some research for themselves on the topic. And maybe after that, you would want to use it in your next project.
What's NX?
In short, it's a developer experience (DX for short) toolkit that helps building projects inside monorepo architecture. Think of it as create-react-app (or angular-cli) for monorepos.
So it might be obvious from the name: monorepo stands for the mono repository - a repository that contains all domain-related files. And I mean ALL files: your backend, your frontend, your utility libraries, documentation, etc. In opposition to that, polyrepo is the approach where each of your domain-related projects has its own repository. I couldn't find when the first monorepo was created. Still, Wikipedia says this approach dates from the early 2000s and is currently widely used by Google, Facebook, Microsoft, and other large and small companies.
Knowing what monorepo is, we can say that NX is a tool to manage monorepos. It was created by ex-google employees who worked on Angular.js and Angular in particular: Jeff Cross and Viktor Savkin. In 2016 they left to start their own consulting firm Narwal Technologies Inc (Nrwl for short), and 2 years after, they released the first alpha version of their own monorepo manager.
Being angular guys to the bone, the first versions supported only angular projects. Heck, they even used literal angular.json for their config. A bit later, NX became a framework agnostic. At first, the meat of this tool was a great set of generators for various frameworks that made starting an app a single command job (create-react-app wink-wink). And they didn't stop on it and added a bunch of stuff: a visual inspector of the dependency graph, github actions, cloud caching, and so on.
That's all cool, but you might ask yourself why you need it. And most importantly, why choose NX over other options: Lerna, Turborepo, Bazel, or even npm/yarn workspaces (and the list goes on)?
Okay, the answer to the first question depends on your situation and preferences. As I mentioned, I'm here to share, not sell. So, you'll need to do the homework yourself. I found a great page about the pros and cons of both approaches. If you decide to try monorepo, be my guest and suit yourself with whatever option is available. I can share an excellent website that might help you decide https://monorepo.tools/. My story of using NX is based on occasion and curiosity, but I'll tell it later.
Next, I want to talk about some use cases. And although I'll be talking about NX, most of the stuff applies to other monorepo tools.
How can you use NX?
The prime example of the setup is to have both the API and the client project in the same repo. You can run everything with a single command, share typings, and develop full-stack features without switching between editor windows. You might think it's most suitable for full-stack developers. In some way, it is. But as a dedicated frontend or backend developer. Sometimes you want to peek at what the heck is going on over there: how the endpoint builds the response or how the frontend handles your API. But often, you even don't have access to other team repo. I know. I've been there.
NX nicely fits the microservices architecture: small isolated applications that might have some shared interfaces of utilities.
Of course, you can argue that it might not always work. Not all benefits can be used. And it's true, NX works great with JavaScript frameworks only. If your backend is not Node.js based, you won't be able to use it. I invite you to learn about other language-agnostic tools here.
Another example is to use it for libraries. For instance, UI-kit. I often see that a theme, icons, basic components, and advanced components are separated in such libraries for consumer convenience. It's reasonable to release them separately. The end user might not need all of the packages. And NX shines here, allowing you to develop everything together but release it separately.
Also, you can use it for a single frontend (or backend) project. At first, it may not have any sense. Why would you need a monorepo for a single project? But NX has a trick up its sleeves - the libraries structure. You see, NX provides a way to have shared libraries area between several applications. Nothing unusual. However, they introduce libraries types approach where you should put your libs under 4 basic categories: feature, data, UI, and utils. It helps structure application by layers. All API interactions code and state management should go to data libraries. UI components - to UI libraries and so on. Since it's just a convention, you can just ignore it altogether and use it as a basic area for shared code. Or you can create your own types of libraries to fit your needs in the project. For instance, we wanted to share typings separately at some point and made a types-library type of signaling that typings should go there.
Library categories have a dependency constraint, meaning a specific library type can depend only on a particular type of other libraries. For example, UI components usually shouldn't interact with the API directly. This is a job of a container. So following that, UI libraries shouldn't import any data libraries. Or utils shouldn't depend on anything other than maybe other utils. And it's reflected in libraries hierarchy. By default, it's not enforced and is just a convention, but NX provides a tool to prevent unwanted imports.
Those are just examples that I’m more familiar with. NX supports various JavaScript frameworks and tools, so it’s a good chance it already supports what you want to achieve.
My path with monorepos and NX
My first encounter with monorepo was way back in 2017. I ended up on a project that has its own UI library, and this library was a Lerna-based monorepo. I didn't participate in choosing the monorepo approach or the tool. It was already there. But I liked the approach because all components depended on a theme, and it was straightforward to navigate a single project during its development. Also, releasing all libraries as separate libraries from a single repo seemed like magic to me at the time. Eventually, as angular libraries matured, we replaced Lerna with the native angular solution, which looked like a better option from the support view.
The next time I worked with monorepo was a few years later. In 2020 we were starting a new project, and the setup was very similar to the first project: an app and a UI library. We started developing the UI library first. And because we already used Lerna, the choice was a no-brainer. However, once we were about to start developing the app, NX began to gain popularity. They introduced React support, and out of curiosity, we began investigating it. The libraries hierarchy appealed to us, and we decided to give NX a shot with the app. Although we never had a chance to convert the UI components library to NX.
At the end, it was a good choice. The project structure looked clear and easy to follow (once you wrap your head around NX concepts). Dependency constraints prevented us from doing silly stuff (most of the time). So, needless to say, we started to use NX in all oncoming projects.
I never used NX with the backend project in the production, but I had some toy projects exploring that idea, and I liked it.
Another "success story" using NX was on a project where we developed a chat widget. It was not a standalone app and intended to be integrated into different environments we didn't own. We came up with an idea to create a hoster app that will simulate all possible environments to test our widget without a dependency on another team. NX was an excellent fit for such requirements because we didn't have to pollute our main app (the widget) with the throwaway test code. It lived as a separate app inside the monorepo.
By the way, we liked the idea of the hoster app, and for the next project we did, we created a similar app to provide a dynamic environment config for the main app. But it's a story for another time.
Struggles
You might think that since I shared only success stories, it means that all went nice and smooth. Unfortunately, it's not always great, and monorepos and NX, in particular, have their own set of problems.
I want to start with the documentation. The NX team provides a ton of learning material: they have a video course, books, and even their own conference. And they improved their docs throughout the years significantly. However, the API documentation is not ideal. Whenever I want to find a command to rename a library or delete it, I struggle to find anything in their doc. And it's usually faster for me to just google it or even do it manually (don't do it, though). It would be good to have the page with the most common commands because, honestly, it's all you need 90% of the time, yet there's none. Or I couldn't find it, which isn't speaking in favor of the docs as well.
The next struggle is not a big issue anymore, but it was previously. I'm talking about low-level access to the webpack config. I think Angular started this trend, and NX developers followed it for some reason. Maybe it's their Angular roots. I don't know. It was a painful to get to the config. We had to use patch-package util to overwrite config directly inside the node_modules folder to make the change we needed. Luckily it's not the case anymore. You can provide a custom webpack config per application without needing a 3rd party package.
The NX team follows the single-version policy. Meaning if you need to maintain several different versions of the same dependency, you better look elsewhere. I think it's one of the rare cases, but we totally had it. On one of the projects, we used an in-house package that required a specific version of React. But in our repo, we already moved on to a newer version. Although I think some hacks might help you achieve it, we couldn't make it work for us. Likely we could convince the team that maintained the package to put the upgrade in their roadmap sooner. However, it could quickly become a serious problem for us.
Last but not least, and maybe the most prominent issue with monorepos, is bloating of the sources. Besides all the boilerplate files, you basically cram all of your projects into a single repo. It can and will affect your install time and your build time. Currently, in our project, it takes 20 minutes to release a version. And it's for a frontend app and without tests! About 7 minutes takes to install all dependencies which might not be relevant to the app we're about to release. And about the same time to run the build command. Plus a bunch of other smaller scripts. In our case, it's manageable: we're going to reduce the number of dependencies as we get rid of the legacy. Also, we're still running node 12, which is way slower than 16 in terms of npm install. Although those issues are partially on our side, I imagine it wouldn't be so drastic in polyrepo architecture.
By the way, fun trivia: Google runs all their codebase in a single monorepo that contains 2 billion lines of code representing 86 Tb of storage. You can read more about it here.
Conclusions
As I started learning and using more technologies, people asked me what I liked better: the framework I used before or the one I'm using now. I always answer: it doesn't matter to me. A tool is a tool. But the more you use a tool, the more familiar you get with it and more proficient. And after some time, you would prefer your current tool over others, maybe not because you like it the most, but because you're more comfortable with it. It becomes your preference.
I know there's always debate over the right tool for a job, but the truth is most of the time, it comes down to preference and resource availability.
It's not always unicorns and rainbows with NX. But it has proven to be an excellent tool for various projects. I know I'll most likely use it in my next project. It became my preference.
I invite you to read more about it, to be aware of such a tool. Maybe you would also want to try it in your next project.
Links
- nx.dev
- How google does monorepo
- Why Google Stores Billions of Lines of Code in a Single Repository
- Monorepo vs Polyrepo
- monorepo.tools
- Awesome monorepos
Thanks
I'd like to thank @eransakal and @amirch1 for their feeback. Check out Eran's blog.
#StandWithUkraine 🇺🇦
It's been 5 months since russia waged a full-scale war against my homeland. We're still strong, fighting, and will continue to fight until our victory. Stand with Ukraine and support us.
The best way is to donate to those charity foundations:
Every penny counts!
Top comments (1)
*thank you, this article so helpfully, wish you best. *