By MidJourney AI

To be frank, it’s difficult for me to understand why anyone would skip using Nx for their Angular projects. And no, a monorepo isn’t required for it to be useful. Let’s walk through the reasoning.

Setting up a folder layout and application architecture that stays maintainable, scales over time, and works well with many contributors is a challenge for many teams. That difficulty is present in polyrepos and is even more pronounced in monorepos. Yet, regardless of whether you use a separate repository or a combined one, Nx makes the process smoother.

Angular with Nx in 2023? Seriously, You Should! — figure 2

In a Nutshell — What Exactly Is Nx?

Nx is a powerful open-source build system that provides tools and techniques for enhancing developer productivity, optimizing CI performance, and maintaining code quality. — Nx docs.

That definition comes right out of the docs. In my experience, Nx acts as a solid bedrock for almost any Angular project. I’ve worked on numerous apps with Nx, and honestly, it feels like a reliable sidekick throughout the journey.

Nx pushes you toward:

  • Dividing your codebase into clearly scoped modules so it stays easy to handle

  • Keeping everything uniform by leaning on code generation consistently

  • Steering clear of tangled dependencies that could hurt your code’s long-term health

  • Getting an automatic view of your project’s layout via the dependency graph, along with a host of other perks.

Sure, Nx sets you up for success, but there are no guarantees. The real responsibility falls on you to stick with solid practices, adopt an approach that’s seen real-world use (like DDD or FDD), and lay down proper *module-boundary rules, plus similar guardrails.

After you fine-tune the configuration, introducing new features or scaling up the app becomes a breeze. Yet, the journey kicks off with that initial move.

By MidJourney AI

The Big Decision

Projects vary wildly in scope, from compact and straightforward to sprawling and intricate, with teams shaped differently in each case. Therefore, the right project structure hinges on factors such as team size, the number of applications in development, codebase magnitude, and the extent of shared code across projects.

Opting for a polyrepo or a monorepo is a significant call, and it deserves a dedicated conversation with your lead or architect. Ideally, that individual will have solid expertise with Angular apps, development workflows, and the broader architectural picture.

By MidJourney AI

Nx Specific Approach

Let’s now examine how the Nx monorepo setup stacks up against a non-monorepo configuration, using an Angular application as our reference point.

When working with Nx, your features must be structured as libraries in either setup. Don’t let that requirement put you off. Treating them as libs lets Nx work its magic, and for you, they behave just like straightforward folders.

For this example, our app relies on two libraries: Tasks to handle everything task-related, and Users for all user-facing features.

By MidJourney AI

Nx Monorepo Setup

Monorepo Approach

Think of the monorepo approach as gathering every tightly coupled project into one single, large repository.

That sounds intimidating, right? Which is exactly why so many developers write off Nx right away — they assume it’s purely for monorepos. That’s not true. I’ll cover that in a moment—first, let’s dig into the monorepo side of things.

Key Steps and Differences:

  1. Creating the workspace: Run npx create-nx-workspace@latest — preset=angular-monorepo to set up your Nx workspace.

  2. Creating the application: Use npx nx generate @nrwl/angular:app task-manager to scaffold a new Angular app within that workspace.

  3. Generating libraries: Execute npx nx g @nx/angular:library tasks, npx nx g @nx/angular:library users.

  4. Dependency management: In a monorepo, a shared node_modules directory serves all apps and libraries, making dependency handling easier.

  5. Building the application: The npx nx build task-manager command compiles your app. With Nx's clever computation caching, you often get much faster builds in a monorepo. Only the sections of the app you actually modified get rebuilt.

    task-management/
    ├── apps/
    │   ├── task-manager/
    │   │   ├── src/
    │   │   │   ├── app/
    │   │   │   ├── main.ts
    │   │   │   ├── index.html
    │   │   │   ├── ...
    │   │   ├── angular.json
    │   │   ├── ...
    │   ├── task-manager-e2e/
    │   │   ├── ...
    ├── libs/
    │   ├── tasks/
    │   │   ├── src/
    │   │   │   ├── lib/
    │   │   │   ├── index.ts
    │   │   │   ├── ...
    │   │   ├── ...
    │   ├── users/
    │   │   ├── src/
    │   │   │   ├── lib/
    │   │   │   ├── index.ts
    │   │   │   ├── ...
    │   │   ├── ...
    ├── node_modules/
    ├── package.json
    ├── nx.json
    ├── project.json
    ├── ...
Enter fullscreen mode Exit fullscreen mode

All projects in the workspace live under apps/; shared libraries and features go under libs/. Both tap into the root node_modules/ folder, where all workspace-wide dependencies reside.

Pros:

  • Code Sharing: A monorepo enables seamless sharing of code between different applications and libraries. If you have multiple apps that use shared features, you can drastically cut down on duplicated code. Since everything lives in a single repository, your IDE has complete awareness. You can also forget about managing NPM packages.

  • Atomic Changes: You can commit modifications across multiple projects in one single "git branch". This simplifies tracking. Nx is also clever: it builds and tests only what was affected by those changes, e.g., 2 out of 20 apps and 5 out of 100 libraries.

  • Unified Build & Test Setup: With all projects in one location, you can execute builds, tests, and releases across the board collectively. Furthermore, when all projects are co-located, setting up and maintaining smooth-running integration tests becomes easier.

  • Simplified Dependency Management: Projects within the workspace share a common set of dependencies. While it's theoretically possible to use multiple versions, the Nx team strongly advocates for a single-version policy. This means you have exactly one package.json to manage. It also guarantees everything stays in sync, though it pushes you to update all apps to keep everything working post-upgrade—which is ultimately better for the project's health.

  • Code Consistency: Maintaining uniform rules and practices is easier when all projects are centralized. You can assign specific parts or apps to different people/teams. Additionally, you can leverage code generators to define the structure, which significantly simplifies onboarding.

  • Streamlined Onboarding: New developers can get up to speed quicker, as all the relevant code is in a single, centralized location.

Of course, a monorepo comes with its own share of challenges. Here’s what you should be cautious about:

Cons:

  • Scalability: As the codebase grows, so can the build and test durations. Nx addresses this pretty well through computation caching and affected commands. However, with very large repositories you might still face laggy IDE performance and extended CI times.

  • Increased Complexity: Monorepos can become complicated, especially when dealing with large teams and sizable code bases. To maintain order effortlessly, strict rules and discipline are essential.

  • Merge Conflicts: When several teams work in the same repo on separate apps, the likelihood of running into merge conflicts rises.

  • Read Access Control: Typically, everyone has read access to the entire repository. Restricting access to just specific projects is tricky. If you must control who can see certain code, this structure might not work for you.

  • Code Reviews: Since changes might affect many areas, they can be overwhelming to review. You can mitigate this by setting "codeowners" for different parts of the repo.

By MidJourney AI

Nx Non-monorepo Setup

Polyrepo Approach

Now, let’s explore the Polyrepo Approach. In this model, every project gets its own dedicated repository, completely separate from the others. For large organizations, this often means managing anywhere from ten to twenty distinct repositories simultaneously.

Key Steps and Differences:

  1. Creating the workspace: To spin up an Nx workspace for a standalone application, we run npx create-nx-workspace@latest, then choose the Standalone Application option and give it the name task-manager.

  2. Generating libraries: Two library generation commands are needed: npx nx g @nx/angular:library tasks and npx nx g @nx/angular:library users

  3. Building the application: Running npx nx build task-manager compiles the application. While computation caching functions in a standalone setup, its benefits are reduced because there’s only a single app to build.

    task-manager/
    ├── src/
    │   ├── app/
    │   ├── main.ts
    │   ├── index.html
    │   ├── ...
    ├── libs/
    │   ├── tasks/
    │   │   ├── src/
    │   │   │   ├── lib/
    │   │   │   ├── index.ts
    │   │   │   ├── ...
    │   │   ├── ...
    │   ├── users/
    │   │   ├── src/
    │   │   │   ├── lib/
    │   │   │   ├── index.ts
    │   │   │   ├── ...
    │   │   ├── ...
    ├── node_modules/
    ├── project.json
    ├── nx.json
    ├── package.json
    ├── ...
Enter fullscreen mode Exit fullscreen mode

In this case, the layout is more straightforward. The app and its source files sit at the workspace's root. Libraries remain within a libs/ folder — though you're free to rename it. It's also possible to maintain multiple such folders.

With a non-monorepo setup, all of these features still come bundled:

  • A ready-to-run Angular application located at the Nx workspace root (src/app)
  • E2e tests powered by Cypress (e2e/)
  • Prettier set up for you
  • ESLint configured from the start
  • Jest ready to go

Advantages:

  • Cleaner Project Layout: A standalone setup brings a more pared-down structure, which is a great fit for small teams or those new to Nx. With one repository per project, navigating the codebase becomes intuitive and straightforward.

  • Better App Separation: Standalone setups keep each application isolated from the others. This is ideal when apps share little in common or require strict boundaries. Each repo operates as an independent unit, prompting you to write self-contained code—which often results in higher-quality output.

  • Tailored Configs: Individual apps can carry their own customized settings, offering adaptability when different projects have unique needs.

  • Selective Access Control: You have the power to grant or deny access as you see fit. This allows you to restrict read permissions to certain apps or parts of the codebase, making polyrepos a solid pick when varied teams or individuals need different access levels.

  • Smoother Code Reviews: Reviewing code is less of a chore since changes tend to be limited to specific repositories.

Still, polyrepos have their flaws. A few hurdles may pop up along the way.

Drawbacks:

  • Duplicated Effort: Sharing code across projects might devolve into a copy-paste routine if you aren't vigilant. Alternatively, you could find yourself juggling 10 NPM packages across your apps, struggling to keep them in sync with the latest releases. Standalone setups don't encourage code sharing as effectively as monorepos, which makes them a weaker option for projects where multiple apps depend on substantial shared functionality.

  • Separate Build Setups: Each standalone app might demand its own distinct build configuration.

  • Dependency Complexity: This can get knotty. Each project comes with its own package.json, so you might wind up with mismatched versions of third-party libraries across different projects.

  • Change Coordination: Rolling out updates that touch several projects simultaneously is daunting. A single feature or bug fix could require work across multiple repositories.

  • Integration Test Headaches: Getting integration tests up and keeping them running smoothly across various repositories is far more demanding than with a monorepo.

Angular with Nx in 2023? Seriously, You Should! — figure 7

Go with Nx

The decision between a monorepo and a polyrepo hinges on your project’s specific demands. For sizable projects with significant shared code, a monorepo often proves advantageous. Conversely, if your project is compact, needs to maintain separation, or requires strict control over read permissions, an independent structure may serve you better.

Each approach carries its own trade-offs—yet as noted earlier, your choice doesn’t limit you. Nx accommodates both configurations.

I see no compelling reason to begin an Angular project without Nx as a partner, regardless of whether you lean toward monorepo or polyrepo.


I hope you liked my article!

If you enjoyed it, you might appreciate what I’m sharing on Twitter. I regularly host live Twitter Spaces focused on Angular, featuring GDEs and industry experts! Join in real time to ask questions or catch abbreviated replays as short clips :)

Interested? Give me a follow on Twitter @DanielGlejzner — it would mean a lot :). Thanks!