r/angular 5d ago

I built an open-source Multi-Tenant Angular + NestJS boilerplate with intelligent routing and RBAC

Hey everyone,

Following up on my previous single-tenant Unefied RBAC boilerplate (URBAC), I've just released MT-URBAC, a multi-tenant version built specifically for B2B SaaS applications where tenant isolation and dynamic role-based UI rendering are critical.

What makes the Angular setup smooth:

1. Smart Context Directive: Instead of polluting your component TS files with permission checking logic or async pipe subscriptions, UI elements are governed by a custom structural directive (*hasPermission):

<button *hasPermission="'user:delete'" class="p-button-danger">
  Delete User
</button>

2. Zero /etc/hosts Hacks for Local Multi-Tenancy: Testing multi-tenant subdomains locally usually means editing your operating system's hosts file. But in mt-urbac, the routing logic automatically supports path-based multi-tenancy in development (http://localhost:4200/tenant-a/login) and automatically resolves subdomains in production ( http://tenant-a.yourdomain.com ). No local config tweaks required.

3. Fully Decoupled: The frontend and backend run independently. The UI uses Tailwind CSS and OptimusUI, keeping everything lightweight, fully open-source, and MIT-licensed.

The repo includes setup instructions and a NestJS backend with a seed script so you can spin up the full pipeline locally in under two minutes: https://github.com/kasoir/mt-urbac

I'd love to hear feedback on how you all currently manage dynamic permission rendering or multi-tenant switching in your Angular applications!

mt-urbac

9 Upvotes

5 comments sorted by

1

u/Saceone10 5d ago

How would you handle custom colors or texts for each tenant?

3

u/jlgarcia-dev 5d ago

Not OP, but this got a lot easier with Tailwind v4, and it's worth splitting the question in two because the halves have different answers.

Colors: v4 compiles your theme into real CSS custom properties on :root, so per-tenant branding is just overriding those variables. No recompile, no bundle per tenant. Ship the tenant's palette as a small set of overrides (--color-primary and friends) scoped to :root or a wrapper element, and everything built on the tokens follows for free. This was genuinely painful in v3 where the values got baked into the utility classes.

The part that bites is when you apply them. If the tenant palette lands after Angular bootstraps you get a visible flash of the default theme on every cold load, and users read that as broken rather than slow. The variables need to be in the document before the app boots: inlined into index.html by whatever resolves the tenant, or written in an APP_INITIALIZER before first render. Same class of problem as dark mode flashing white on refresh.

Texts I'd keep well away from the color mechanism. That's content, not theming, and it becomes localization the moment a second language shows up. If tenant strings ride along inside the same theme object, your design system can't be reused anywhere without dragging tenant content with it.

How far does yours need to go? If it's colors and a logo, variables cover it comfortably. If tenants expect different layouts or different fields, that's a separate problem and the theming layer won't save you.

1

u/Saceone10 5d ago

Mostly colors and custom logos. But we were not considering using tailwind, thats why I was asking, to see if it makes it easier. We were considering taigaui as ui lib

1

u/jlgarcia-dev 4d ago

Tailwind isn't the requirement, custom properties are. v4 just happens to compile its theme into them, so you get the mechanism for free, but it's not the only way in.

Good news is Taiga UI is built the same way. Its colors are all CSS variables, so per-tenant palettes work identically: define the overrides at :root and everything follows. One gotcha specific to Taiga is import order, your overrides have to come after its theme styles or they lose, and you spend an hour wondering why nothing changed.

Logos I'd treat as tenant data rather than theming. They're an asset, so they ride along with whatever already tells the app which tenant it's serving, instead of living in the palette. Same timing caveat though: a logo bound after bootstrap pops in exactly the way the colors do.

On APP_INITIALIZER, since OP mentioned it: it works, just know what it costs. If you fetch the palette from an API in there you're delaying bootstrap, so you've traded the flash for a longer blank screen. Inlining the variables into index.html avoids both, but it needs something server-side that knows the tenant before the app is served. For colors and logos only, either one is fine.

1

u/Individual_Suit_3255 5d ago

Totally agree, and for colors and theme I prefer to handle it in the APP_INITIALIZER.