The Monorepo Blueprint – How to Create a Scalable Architecture for an Angular Monorepo

Share on facebook
Share on google
Share on twitter
Share on linkedin

This article will teach you how to create a scalable architecture for an Angular monorepo. The principles here can be applied to any front end monorepo, as they are based on universal best practices for getting a scalable and maintainable architecture.

But first, what should a good Angular architecture do?

  1. Separate UI from business logic using abstraction layers
  2. UI should be decoupled from technology specific tools such as communication and state management
  3. The architecture should reflect the domain knowledge, not the technologies
  4. The architecture should enforce module boundaries to ensure proper encapsulation and dependency control

Let’s consider how this will look like for a todo app that:

  • Have a web and mobile app
  • Can show todo list
  • Can show a todo list admin dashboard
  • Can get and save todos using a todo service (node app)



The overall setup can be created using Angular and NX (for affected commands and TsLint rules for module boundaries).

Let’s look at how all of this ties together.


Apps here should only focus on the UI and should delegate all data access to the corresponding sandbox. That means that there should be no HttpClient or state management framework referenced inside of an app. This gives us the luxury of having apps decoupled from the data access logic, which makes it easier to later change the state management framework or communication technology.


Normally you might think of libraries as a means to code sharing. That is partly true here, but it can also have other purposes: enforce encapsulation of modules. By creating a library we can use the Nx TsLint rules to enforce that libs can only be communicated with through the public interface (normally a barrel export index.ts file) which helps with a more maintainable architecture because of better encapsulation and control of the dependencies. It will also break your app up in smaller independent pieces, which will make troubleshooting and test execution faster. For each lib, you can freely choose eg. the testing framework and build configuration without it is affecting other apps’ setup (it might still be a good idea to keep the app consistent).

App-specific libs

We want to create a library specific to each app to encapsulate all the data access logic. We do this by only exposing “sandboxes”.


A sandbox is a facade which can contain business logic. It is used for setting and getting data through an easy-to-use interface. Why is it easy to use? The interface of a sandbox should never contain technology-specific terms such as: dispatch and selector. You will just write methods with a clear intent in the domain language eg. saveTodo.

Notice on the picture above that there are no effects/thunk/epic/saga. That is because it is handled in the sandbox. Why don’t I use effects?

  1. Complicates the interaction with indirection when there is no one to many communication
  2. Stream dies if an effect throws an unhandled error, resulting in a broken app and need for defensive error handling in all places
  3. Couples business logic to a framework specific technology – harder to change
  4. A state management framework is for state management, not business logic (IMO!)

This is my personal opinion and experiences from seeing these problems over and over in teams, and simply fixing them by not using effects/epics and such. Also, one big drawback with effects is that you need to write protective error handling to make sure you keep the stream alive and don’t break the app, which complicates things quite a lot

Notice here how the sandbox is the externally exposed interface, which will handle business logic and the delegation to the state management framework and HttpClient. By having this abstraction in our architecture we can easily change the state management and communication framework without it is affecting the apps. This is the power of good abstractions and encapsulation. From the app developers perspective, he might not know anything about NgRx and HttpClient, but because the abstraction is so easy to use, he doesn’t need to if he is only working with UI.

Because an app lib should only be used by that specific app it is a good idea to enforce this constraint by tagging the app and the lib with the same tag, eg. todo-app and enforce that only todo-app lib can be called by todo app using Nx TsLint rules.


Inside here goes the libs that can be used by multiple apps. You should enforce the module boundary by using tagging and setting the Nx TsLint rules accordingly.

Grouping of shared libs

A shared lib can be grouped after:

  1. Feature
  2. Platform (web, iOS, Android)
  3. Function (UI, data access)


A feature library is a grouping based on a specific feature in the app. That could eg. be the responsibility for authentication. Here you might have the login page (smart component) which will contain the necessary UI and data-access logic to log users in and out.


Feature libs can be used by apps only.


The UI lib should only contain shared presentation components. That means no feature context should go here. All interaction is through input and output. You want to copy UI component from apps to here whenever a UI component should be shared with the other apps.


UI libs can be used by apps only.

Data Access

Here we have the shared data access and HTTP logic, that can be used by sandboxes. That includes HttpInterceptors, shared reducers, actions, and selectors for example.


Shared data-access libs can be used by all projects except UI and utils libs.


Shared utils libs are normally containing static pure functions, that provide different kinds of helpers. This is for example date utils.


Utils can be used by all projects.


In this post, we saw how to create a scalable monorepo architecture that will ensure maintainability by:

  • Decoupling UI and business logic
  • Ensure dependency control and module boundaries
  • Domain-based instead of technology-focused

By having this architecture in place it is possible to change eg. state management framework inside of the sandbox without needing to change the apps, because of the decoupling and the abstraction using sandboxes. Also, the module boundaries help the team to make sure the architecture stays clean.

Coming up

I will go through how to set this up completely in the next article on a code level. Make sure you subscribe so you don’t miss it.


NRWL book Angular enterprise monorepo patterns

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

Walkthrough: Building An Angular App Using Tailwind CSS and Nx

In this video, we will cover a walk-through from scratch covering how to Build and Angular app using Tailwind CSS and Nx including Angular standalone components. 🖌 Tailwind CSS setup and usage⚙️ Nx monorepo configuration🧍‍♂️ Standalone components✅ Building a complete app in less than 2 hours with best practices ⏳ Timestamps:0:00 – Intro1:00 – Tailwind

Read More »

18 Performance Optimization Techniques For Angular Applications (podcast with Michael Hladky)

Performance is extremely important in front-end applications as they directly impact the application’s user experience. Poorly performing applications can be frustrating for the end-users. For example, an e-commerce site that is slow and unresponsive might deter users which could have a direct impact on the business.  Although there is a growing number of tools to

Read More »

Announcement: Angular Testing Workshop

I’m announcing a new live workshop: Angular Testing Workshop. It will be half-day workshops over three days, 100% online, where we will learn all the industry best practices with Angular testing so you can apply them in your daily work – taking straight out of my experience with doing Angular testing for big projects. The

Read More »

Server-side Rendering (SSR) with Angular Universal

As the name suggests, Single-page App (SPA) is a single HTML document that can be initially served to the client. Any new views required in the application can be created by using JavaScript solely on the client. Besides this, the request-response cycle still happens, but only to RESTful APIs to get static resources or images.

Read More »

How Fabrice Became An Angular Architect In 8 Weeks

Learn how Fabrice became an Angular architect in 8 weeks. He asked himself “How can I strengthen my knowledge” and he joined Angular Architect Accelerator. Fabrice shares his experience with the course and how he became a better Angular developer. Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Read More »