Nx is a powerful build system for monorepos that provides intelligent caching, task orchestration, and code generators. It makes managing multiple projects in a single repository fast, scalable, and developer-friendly.
What Are Monorepos? π€
Imagine you have multiple projects, an Angular app, a NestJS backend, a few shared libraries, and everything lives in a single Git repository. That's a monorepo. Instead of maintaining a separate repo for each project (polyrepo), all projects share the same codebase, the same dependencies, and the same CI/CD pipelines.
Sounds chaotic? That's exactly where Nx comes in.
Nx as a Build System ποΈ
Nx isn't a framework, it's a build system and monorepo management tool. It ensures that only what has actually changed gets built, tested, and linted. Nx supports not just Angular, but also React, NestJS, Node.js, and many other technologies.
The core features of Nx are:
- Intelligent Caching, Tasks are executed only once, then cached
- Task Orchestration, Nx knows which tasks can run in parallel
- Code Generators, Boilerplate code is created automatically
- Affected Commands, Only changed projects are processed

Setting Up a Workspace π
Creating a new Nx workspace is quick. All you need is Node.js and npm:
# Create a new Nx workspace
npx create-nx-workspace@latest my-monorepo
# You'll be asked:
# - Which preset? (angular, react, nest, etc.)
# - Standalone or integrated?
# - CI setup?
After installation, you'll have a folder structure that looks something like this:
my-monorepo/
βββ apps/
β βββ frontend/ # Angular App
β βββ backend/ # NestJS API
βββ libs/
β βββ shared/ # Shared libraries
β βββ ui/ # UI components
βββ nx.json # Nx configuration
βββ package.json
βββ tsconfig.base.json
Generators: Code at the Push of a Button π§©
Generators are one of Nx's most powerful features. Instead of manually creating files and configurations, Nx generates everything for you:
# Create a new Angular app
npx nx generate @nx/angular:application --name=dashboard --directory=apps/dashboard
# Create a new shared library
npx nx generate @nx/js:library --name=utils --directory=libs/shared/utils
# Create a new NestJS API
npx nx generate @nx/nest:application --name=api --directory=apps/api
# Angular component in a library
npx nx generate @nx/angular:component --name=button --project=ui
Each generated project automatically gets a project.json that defines all available targets:
{
"name": "dashboard",
"sourceRoot": "apps/dashboard/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/apps/dashboard"
}
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server"
},
"test": {
"executor": "@nx/jest:jest",
"options": {
"jestConfig": "apps/dashboard/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}
Executors: Define and Run Tasks βοΈ
Executors are the counterpart to generators, they run tasks like build, test, or lint. Nx comes with many built-in executors, but you can also write your own:
# Build an app
npx nx build dashboard
# Serve an app
npx nx serve dashboard
# Run tests
npx nx test dashboard
# Run linting
npx nx lint dashboard
# Multiple tasks at once
npx nx run-many --target=build --projects=dashboard,api
Caching: Never Build Twice π¨
Nx's caching is an absolute game changer. When you build a project and nothing changes afterward, Nx delivers the result directly from cache on the next build, in milliseconds instead of minutes.
# First build - takes e.g. 45 seconds
npx nx build dashboard
# Output: "Successfully built dashboard (45s)"
# Second build - from cache
npx nx build dashboard
# Output: "Nx read the output from the cache (42ms)"
Nx caches not just build output, but also test results and lint checks. You can configure caching in nx.json:
{
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"test": {
"cache": true,
"inputs": ["default", "^production"]
},
"lint": {
"cache": true,
"inputs": ["default"]
}
}
}
Affected Commands: Only Do What's Necessary π―
In a large monorepo, you don't want to rebuild everything for every small change. Nx analyzes the dependency graph and runs tasks only for affected projects:
# Build only changed projects
npx nx affected --target=build
# Test only changed projects
npx nx affected --target=test
# Lint only changed projects
npx nx affected --target=lint
# Show graph of affected projects
npx nx affected:graph
For example, if you only change a shared library, Nx automatically detects which apps use that library and rebuilds only those. This saves enormous time in your CI/CD pipeline.
Shared Libraries: Share Code Like a Pro π
The biggest advantage of a monorepo is easy code sharing. In Nx, you create libraries for this:
// libs/shared/utils/src/lib/format-date.ts
export function formatDate(date: Date): string {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
// libs/shared/types/src/lib/user.interface.ts
export interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
You can then import these libraries in any app, without publishing them separately:
// apps/dashboard/src/app/app.component.ts
import { formatDate } from '@my-monorepo/shared/utils';
import { User } from '@my-monorepo/shared/types';
// No npm install needed - everything is in the same repo!
The path aliases are automatically configured in tsconfig.base.json:
{
"compilerOptions": {
"paths": {
"@my-monorepo/shared/utils": ["libs/shared/utils/src/index.ts"],
"@my-monorepo/shared/types": ["libs/shared/types/src/index.ts"],
"@my-monorepo/ui": ["libs/ui/src/index.ts"]
}
}
}
Nx Cloud: Distributed Caching βοΈ
Local caching is great, but what about your team? If your colleague has already built the project, why should you have to build it again? That's exactly what Nx Cloud is for.
# Enable Nx Cloud
npx nx connect
# After that, the cache is automatically shared
# When one team member builds, everyone else benefits
Nx Cloud offers:
- Remote Caching, Cache is shared across the team
- Distributed Task Execution (DTE), Tasks are distributed across multiple CI agents
- CI Dashboard, Overview of all builds and their performance
Especially in the CI/CD pipeline with GitHub Actions, Nx Cloud makes a huge difference:
Monorepo vs. Polyrepo: When to Use Which? π€·
Not every project needs a monorepo. Here's a comparison:
| Criterion | Monorepo | Polyrepo |
|---|---|---|
| Code Sharing | Easy via libraries | Via npm packages |
| Dependencies | One package.json | Separate per project |
| CI/CD | Centralized, with Affected | Separate per repo |
| Team Size | Great for 2+ projects | Good for independent teams |
| Onboarding | Clone one repo | Clone multiple repos |
| Versioning | Everything in sync | Independent |
Monorepo works well when:
- You have multiple related projects (e.g., frontend + backend + shared libs)
- Your team works on the codebase together
- You want to share code between projects
- You need consistent tooling and dependency versions
Polyrepo works better when:
- Projects are completely independent of each other
- Different teams have different release cycles
- Projects use different tech stacks
Practical Tips for Getting Started π‘
- Start small, Begin with one app and one or two libraries
- Define boundaries, Use Nx Module Boundaries to enforce dependency rules
- Use Nx Console, The VS Code extension makes everything easier
- View the dependency graph,
npx nx graphvisualizes your project structure - Cache from the start, Enable Nx Cloud early so your team benefits immediately
# Open dependency graph
npx nx graph
# Check module boundaries
npx nx lint --all
Conclusion π¬
Nx makes monorepos not just feasible, but genuinely enjoyable. Intelligent caching, code generators, and affected commands save you time every day. If you have multiple related projects, an Nx monorepo is definitely worth a look.