Skip to content
NestJS ns deployment 5 min read

Monorepo & Workspaces

As a system grows you rarely ship a single Nest application. You end up with an HTTP API, a background worker, maybe a WebSocket gateway, and a pile of shared code — DTOs, guards, database entities — that all of them depend on. Nest’s built-in monorepo mode turns a single repository into a workspace that hosts many apps and reusable libs behind one node_modules, one tsconfig, and one build pipeline. This page covers enabling monorepo mode, generating shared libraries, wiring path mappings, building selectively, and how all of this compares to running Nest inside an Nx workspace.

Enabling monorepo mode

A fresh nest new project is a standard (single-app) structure. Nest converts it to a monorepo automatically the first time you generate a second application or a library. You can also do it explicitly.

# Start from a standard project
nest new my-platform
cd my-platform

# Generating a second app flips the workspace into monorepo mode
nest generate app worker

After this command the layout changes. Your original src moves under apps/, a sibling app is created, and a nest-cli.json describes the workspace.

my-platform/
├── apps/
│   ├── my-platform/        # the default app
│   │   ├── src/
│   │   └── tsconfig.app.json
│   └── worker/
│       ├── src/
│       └── tsconfig.app.json
├── libs/                   # shared libraries live here
├── nest-cli.json
├── package.json            # ONE package.json for the whole workspace
└── tsconfig.json

The key idea: every app and lib shares one dependency tree and one root tsconfig.json. There is no per-package node_modules, which keeps versions consistent and installs fast.

Generating a shared library

Libraries are where reusable code lives. Generate one with the CLI:

nest generate library common

You will be prompted for an import prefix; accept the default @app. Nest scaffolds the lib and registers a TypeScript path mapping so any app can import it by name.

// libs/common/src/logging/app-logger.service.ts
import { Injectable, LoggerService } from '@nestjs/common';

@Injectable()
export class AppLogger implements LoggerService {
  log(message: string)   { this.write('LOG', message); }
  error(message: string) { this.write('ERROR', message); }
  warn(message: string)  { this.write('WARN', message); }

  private write(level: string, message: string) {
    process.stdout.write(`${new Date().toISOString()} [${level}] ${message}\n`);
  }
}

Expose it through the library’s module and barrel file:

// libs/common/src/common.module.ts
import { Module } from '@nestjs/common';
import { AppLogger } from './logging/app-logger.service';

@Module({
  providers: [AppLogger],
  exports: [AppLogger],
})
export class CommonModule {}
// libs/common/src/index.ts
export * from './common.module';
export * from './logging/app-logger.service';

Now consume it from any app with the configured alias — no relative ../../../ paths:

// apps/worker/src/app.module.ts
import { Module } from '@nestjs/common';
import { CommonModule } from '@app/common';

@Module({
  imports: [CommonModule],
})
export class AppModule {}

Path mappings

The nest generate library command edits the root tsconfig.json for you, adding a paths entry that maps the import prefix to the library source. This is what makes @app/common resolve correctly during both compilation and IDE navigation.

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@app/common": ["libs/common/src"],
      "@app/common/*": ["libs/common/src/*"]
    }
  }
}

The matching libs block appears in nest-cli.json, telling the CLI where each library’s root and tsconfig live:

{
  "monorepo": true,
  "root": "apps/my-platform",
  "sourceRoot": "apps/my-platform/src",
  "projects": {
    "my-platform": { "type": "application", "root": "apps/my-platform" },
    "worker":      { "type": "application", "root": "apps/worker" },
    "common":      { "type": "library", "root": "libs/common", "sourceRoot": "libs/common/src" }
  }
}

Always import shared code through the @app/* alias, never with deep relative paths. The alias is the contract; relative imports break when files move and confuse nest build’s dependency detection.

Building selectively

The CLI builds one project at a time. Pass the project name to nest build and nest start; omit it to act on the default project from nest-cli.json.

# Build a single app — Nest compiles only the libs it actually imports
nest build worker

# Run one app in watch mode during development
nest start worker --watch

# Build everything for a release
nest build my-platform && nest build worker

Output:

> nest build worker
✔  Compiling apps/worker + libs/common
   dist/apps/worker/main.js

Each app produces an independent bundle under dist/apps/<name>, so deployment artifacts stay isolated. For production you typically build with --webpack to tree-shake unused library code into a single lean main.js per app.

TaskStandard projectMonorepo mode
Build targetimplicit single appnest build <project>
Run targetnest startnest start <project>
Shared codenpm package or copylibs/* with path alias
node_modulesoneone (shared)
Outputdist/main.jsdist/apps/<name>/main.js

Comparison with Nx

Nest’s native monorepo is intentionally lightweight: shared node_modules, path aliases, per-project builds. Nx is a heavier build system you can layer on top for large organizations.

CapabilityNest monorepoNx workspace
Setupbuilt into the CLInpx create-nx-workspace + @nx/nest
Affected/incremental buildsnoyes (nx affected)
Build cachingnolocal + remote cache
Dependency graphbasicfull nx graph visualization
Polyglot (React, Go, etc.)Nest-onlymany plugins
Learning curveminimalsteeper

For two or three Nest apps with shared libs, the built-in mode is plenty. Reach for Nx once you need computation caching, distributed task execution, or a graph spanning frontends and backends.

# Scaffold a Nest app inside an Nx workspace instead
npx create-nx-workspace@latest acme --preset=nest

Best Practices

  • Keep one library per bounded concern (@app/auth, @app/database) rather than a single catch-all common that everything imports.
  • Export only what apps need through the library’s index.ts barrel; treat everything else as internal.
  • Always import via the @app/* alias so the CLI tracks dependencies correctly and refactors stay safe.
  • Build each app with --webpack for production to tree-shake unused library code.
  • Use nest build <project> in CI to build only the apps a change touched, keeping pipelines fast.
  • Pin all dependencies in the single root package.json so every app and lib runs the same versions.
  • Adopt Nx only when you genuinely need caching, affected-graph builds, or a polyglot workspace — not before.
Last updated June 14, 2026
Was this helpful?