How to Ship Updates to 40+ Apps Every Week With Nx

— by Santosh Yadav

Our Studio Application

Problem Statement

Why are we doing this?

App with NavBar

App1

App2

App3

App4

Before module federation with Nx

Repo 1

Repo 2

Repo 3

Repo 4

Each App is loaded via URL routing

Build and Deployment workflow for each App

App

Build and deployment

Build code on GitHub Action

Push the build artifact to DB

* X

X = number of Apps

Sharing code became problem

Design System and Shared Libs

V3

V2

V1

App1

App3

App5

App2

App4

App6

App7

App8

App9

  • Page reloads for each app redirect
  • Huge bundle size
    • No tree shaking
    • No lazy loading 
  • Too much effort in upgrading to the new Angular version.
  • Maintaining multiple versions of shared libs and design system
  • Had to synchronize releases across apps to change design system at the same time.

What is Nx

A build tool with mono-repo support

  • Provides build cache for tasks like build, test
  • Framework and technology agnostic
  • Plugin based so you can bring your own tool 
  • Support major frameworks like Angular, React and Vue out of the box
  • Support for micro-frontend
  • Support for backend technologies like spring and .net core

Our Codebase

We have a mono-repo with Angular and Nx, which contains Angular Apps, NextJs App, Libraries, Unit Test, and e2e. We use Trunk based development approach.

2M LOC

200 Projects

40+ Apps

40+ Teams

Shell App

App1

App2

App3

App4

With module federation with Nx

Single repository

Tasks

Build

Build all the projects including apps and libraries and bundle them together.

1.

Unit Test

We use jest and cypress component test to write unit tests and run them before merging our code.

2.

e2e

For end to end we run all User Journey test suites before going to development.

3.

npx nx generate @nx/angular:host 
--name=shell 
--remotes=home,about,blogs 

Feature Flags

Feature Flag is tricky but becomes very essential when we are shipping features frequently and want to make sure we dont break existing functionality. 

Shipping code without feature flag

  • Ability to ship code based on 
    • User 
    • Cluster
    • % of users or customers
    • specific build
    • combination of all the above
  • Isolate bugs
  • Ability to rollback feature flags
  • Ship features with more confidence

What tool do we use for Feature Flag?

nx add @nx/plugin

nx g @nx/plugin:plugin 
nx-plugin 
--directory infra/nx-plugin

Developer Experience

Ability to enable/disable flag

Update feature flags by creating a Pull Request

1.

Dashboard

Dashboard to view all active features

2.

Weekly Alert

Alert to clear the feature flags once they are GA (Generally Available)

3.

Proof of Concepts

We need our developers to ship POCs fast so they can experiment and meet our customer's future needs.

@nx/angular:remote 
--name=llm-agent 
--no-interactive 

Nx Generator

export default async function (tree: Tree, schema: ApplicationGeneratorSchema) {
  
  // call built-in generator from Nx
  await generateAngularProject(tree, {
    standalone: false,
    name: schema.name,
    style: "scss",
    skipPackageJson: true,
    port: schema.port,
    e2eTestRunner: E2eTestRunner.None,
    tags: tags,
    prefix: "org-name",
  });

  // Add cypress component test when apps are created
  await generateComponentTests(tree, {
    project: schema.name,
    port: schema.componentTestPort,
    generateTests: false,
    buildTarget: `${schema.name}:build-component-test`,
  });
}
export default async function (tree: Tree, schema: ApplicationGeneratorSchema) {

// add files to new project if needed  
generateFiles(tree, join(__dirname, 'files'), `${projectConfig.root}`, {
      name: options.name, // Pass options which can be used inside file
    });
  
}

// index.html.template 
<h1>
<<%= name %>>
</<%= name %>>
</h1>

// Ths above file will be created with name passed as parameter on line no:5

Maintaining Large Codebase

Once all is removed that can be removed, that is how designs are truly in their simplest form.

  1. Refactoring Code
  2. Deprecations
  3. Migrating code
  4. Adding linters and tools
  5. Helping team members
  6. Documentation
  7. Ability to upgrade framework version for everyone
nx graph
nx import [sourceRepository] [destinationDirectory]
  • Deploy documents in a central place; we use backstage.
  • We use Slack to communicate deprecations and new initiatives with all developers.
  • Keep a dedicated channel for developers to ask questions.
  • Educate by organizing workshops.

Tools

It's always a challenge to introduce tools into a project.

  • Introducing a tool into a codebase is time-consuming.
  • Sometimes adding a tool is easy, but maintaining it is expensive.
  • Nx makes it easy to introduce and maintain a tool.
nx add @nx/cypress


nx add @nx/playwright

Add e2e framework

nx add @nx/jest


nx add @nx/vite:test

Add Unit Test Tool

nx migrate latest

Keep your monorepo and tooling updated

CI/CD

We need to build 2M lines of code, and we get 100+ Pull Requests every day, we need to run Build, Unit Test, e2e.

GitHub Actions + Nx =

Superpower 

  • Use Large Runners on GitHub Actions
  • Use Merge Queue to run end-to-end tests
  • Cache builds for faster build and tests
  • Running User Journey tests are an excellent way to avoid bugs.
  • But it's costly to run on every PR.
  • The branches can go out of sync when merging with the main branch.

Merge Queue and User Journey Tests

PR 1

PR 2

PR 3

PR 4

PR = Pull Request

P

I

P

E

L

I

N

E

PR 1

PR 2

PR 3

PR 4

PR = Pull Request

Pr-1 added to merge Queue

Failed CI

Sent to Merge Queue

PR = Pull Request

create a new branch merge-queue/pr1

base branch is main 

changes from PR 1 are merged into 

merge-queue/pr1

PR 1

PR = Pull Request

create a new branch merge-queue/pr1-pr2

base branch is merge-queue/pr1 

changes from PR 2 are merged into 

merge-queue/pr1-pr2

PR 2

PR = Pull Request

create a new branch merge-queue/pr1-pr2-pr3

base branch is merge-queue/pr1-pr2 

changes from PR 3 are merged into 

merge-queue/pr1-pr2-pr3

PR 3

PR = Pull Request

 merge-queue/pr1-pr2-pr3

 

PR 3

Run All CI/CD check and User Journey Tests

Running Affected Tasks on GitHub

App1

App2

App3

App4

App5

Lib1

Lib2

function newFeature() {
	return true;
}

Changes in your Pull Request

App1

App2

App3

App4

App5

Lib1

Lib2

nx affected 
--targets=build,lint,test

Run affected tasks

S3 bucket

Push Nx Cache

Get Nx Cache

PR 1

PR 2

PR 3

PR 4

PR = Pull Request

P

I

P

E

L

I

N

E

Release Stratgey

Releasing 40+ apps is a challenge; we need to make sure everything is well-tested before we deploy.

Create a Release Candidate

Teams execute Automated/manul tets

Postpone release to monday

Deploy Release

Success

Failure

Rollback Release

Production issue?

Create Release Candidate

Green CI/CD?

Yes

Deploy to production

No

Cancel Deployment

Thank You