<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/nestjs-module-boundaries-that-survive-growth-bzh5p5uw6" -->

---
title: NestJS Module Boundaries That Survive Growth | daily.dev
description: A deep dive into why NestJS feature folders aren't real module boundaries unless the exports array is treated as a deliberate public API. Shows how a...
canonical: https://daily.dev/posts/nestjs-module-boundaries-that-survive-growth-bzh5p5uw6
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: NestJS Module Boundaries That Survive Growth | daily.dev
og:description: A deep dive into why NestJS feature folders aren't real module boundaries unless the exports array is treated as a deliberate public API. Shows how a...
og:url: https://daily.dev/posts/nestjs-module-boundaries-that-survive-growth-bzh5p5uw6
og:image: https://api.daily.dev/og/posts/BzH5P5uw6.png
og:image:alt: NestJS Module Boundaries That Survive Growth
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# NestJS Module Boundaries That Survive Growth

**[Medium](https://daily.dev/sources/medium_js)** · 9 min read · 2 upvotes · 2 comments

## Summary

A deep dive into why NestJS feature folders aren't real module boundaries unless the exports array is treated as a deliberate public API. Shows how a SharedModule that re-exports everything (TypeOrmModule.forRoot, MailerModule, AnalyticsModule) silently collapses twelve feature modules into one, causing test setup to boot a database and mailer just to test Orders. Walks through replacing concrete imports with Symbol-based port tokens (PAYMENTS_PORT, MAIL_ADAPTER), explains why forwardRef and ModuleRef.get() are code smells masking a missing boundary, and demonstrates an 'isolation boot' test using overrideModule/overrideProvider as the real proof that a module is decoupled.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://javascript.plainenglish.io/nestjs-module-boundaries-that-survive-growth-3c6781b2baf1>

## Questions this post answers

### why does my NestJS test suite boot a database connection just to test one module in isolation

A shared module that re-exports TypeOrmModule.forRoot, a mailer module, or an analytics client transitively pulls in everything it imports. Nest instantiates an imported module along with everything that module itself consumes, so compiling a single feature module like OrdersModule also boots the mailer and demands a DATABASE_URL if it imports a SharedModule that re-exports forRoot.

_daily.dev surfaces practical patterns like this for developers untangling NestJS module graphs._

### how do I avoid circular dependencies between two NestJS modules without using forwardRef

Extract a Symbol-based port token for the shared capability instead of importing the other module's service directly. If OrdersModule and PaymentsModule import each other, either merge them if they always change together, or define a token like PAYMENTS_PORT with useExisting so one side depends only on an interface, not the concrete module, removing the cycle that forwardRef would otherwise paper over.

_developers wiring up NestJS DI graphs can track patterns like this for shipping cleaner module boundaries._

### how can I test a NestJS module without needing a real database or SMTP server

Use createTestingModule with overrideModule and overrideProvider to replace dependencies like PaymentsModule, MAIL_ADAPTER, and TypeORM repository tokens with fakes. Overriding a provider token alone is not enough if the real module still calls TypeOrmModule.forRoot internally; swapping the whole module with a fake one that exports a stub token lets compile() finish without a live DataSource or mail host.

_daily.dev helps engineers hunting for isolation-testing techniques in dependency-heavy NestJS apps._

## Community discussion

Top comments from developers on daily.dev.

**@nark3d** · 0 upvotes

> Does the Symbol-token version actually stop a feature module from importing a concrete class, or does it just make the import look different?  A shared kernel module can still export half the app and everyone imports from there, tokens or not.  The isolation boot test passes because the test overrides what the module was going to pull in anyway.  What fails the build when someone reaches for the concrete class?  A boundary rather than a convention needs a lint rule or an import boundary check.  overrideModule proves the test can swap a provider.

**@franciscolpez87** · 0 upvotes

> This is exactly the kind of thing `nestjs-doctor` should catch. A SharedModule re-exporting everything basically turns your whole app into one giant module 😂

## Similar posts on daily.dev

- [NestJS Dependency Injection: Source Code, Type Erasure Hacks, and Building Your Own IoC Container from Scratch](https://daily.dev/posts/nestjs-dependency-injection-source-code-type-erasure-hacks-and-building-your-own-ioc-container-fr-74fnlhzd6) · Medium · 0 upvotes · 0 comments
- [How Domain Boundary Violations Slowly Destroy Your Codebase](https://daily.dev/posts/how-domain-boundary-violations-slowly-destroy-your-codebase-enay5y3sz) · Towards Dev · 16 upvotes · 0 comments
- [Module Test](https://daily.dev/posts/module-test-7cztbz9zp) · The Daily WTF · 1 upvotes · 1 comments
- [Enforce TypeScript Architecture Boundaries via AST Import Graphs](https://daily.dev/posts/enforce-typescript-architecture-boundaries-via-ast-import-graphs-3k2xiuqvu) · SitePoint · 1 upvotes · 0 comments

---

Tags: [#nodejs](https://daily.dev/tags/nodejs), [#typescript](https://daily.dev/tags/typescript), [#dependency-injection](https://daily.dev/tags/dependency-injection), [#nestjs](https://daily.dev/tags/nestjs)

[View this post on daily.dev](https://daily.dev/posts/nestjs-module-boundaries-that-survive-growth-bzh5p5uw6)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"NestJS Module Boundaries That Survive Growth","url":"https://daily.dev/posts/nestjs-module-boundaries-that-survive-growth-bzh5p5uw6","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/nestjs-module-boundaries-that-survive-growth-bzh5p5uw6"},"datePublished":"2026-10-07T14:06:24.515Z","dateModified":"2026-10-07T14:08:35.335Z","description":"A deep dive into why NestJS feature folders aren't real module boundaries unless the exports array is treated as a deliberate public API. Shows how a...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/1baaaacf2f2269794c0946af420d2c9f?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/1baaaacf2f2269794c0946af420d2c9f?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Medium","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Medium","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/medium","url":"https://daily.dev/sources/medium_js"},"commentCount":2,"discussionUrl":"https://daily.dev/posts/nestjs-module-boundaries-that-survive-growth-bzh5p5uw6","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":2}],"keywords":"nodejs,typescript,dependency-injection,nestjs","timeRequired":"PT9M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Medium","item":"https://daily.dev/sources/medium_js"},{"@type":"ListItem","position":3,"name":"NestJS Module Boundaries That Survive Growth"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/nestjs-module-boundaries-that-survive-growth-bzh5p5uw6","comment":[{"@type":"Comment","text":"Does the Symbol-token version actually stop a feature module from importing a concrete class, or does it just make the import look different?  A shared kernel module can still export half the app and everyone imports from there, tokens or not.  The isolation boot test passes because the test overrides what the module was going to pull in anyway.  What fails the build when someone reaches for the concrete class?  A boundary rather than a convention needs a lint rule or an import boundary check.  overrideModule proves the test can swap a provider.","datePublished":"2026-10-11T10:33:53.078Z","url":"https://daily.dev/posts/BzH5P5uw6#c-GqLZTAot2","author":{"@type":"Person","name":"Adam","url":"https://daily.dev/nark3d","image":"https://avatars.githubusercontent.com/u/2162621?v=4"}},{"@type":"Comment","text":"This is exactly the kind of thing nestjs-doctor should catch. A SharedModule re-exporting everything basically turns your whole app into one giant module 😂","datePublished":"2026-10-08T01:26:14.563Z","url":"https://daily.dev/posts/BzH5P5uw6#c-hZ7e1RYl8","author":{"@type":"Person","name":"Francisco López","url":"https://daily.dev/franciscolpez87","image":"https://lh3.googleusercontent.com/a/ACg8ocIpVIhKYoGj26AAZArKTB6JO_ZRs8t7j1nwDaauJTt_UQ7ACtyOJA=s96-c"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/nestjs-module-boundaries-that-survive-growth-bzh5p5uw6#faq","mainEntity":[{"@type":"Question","name":"why does my NestJS test suite boot a database connection just to test one module in isolation","acceptedAnswer":{"@type":"Answer","text":"A shared module that re-exports TypeOrmModule.forRoot, a mailer module, or an analytics client transitively pulls in everything it imports. Nest instantiates an imported module along with everything that module itself consumes, so compiling a single feature module like OrdersModule also boots the mailer and demands a DATABASE_URL if it imports a SharedModule that re-exports forRoot. daily.dev surfaces practical patterns like this for developers untangling NestJS module graphs."}},{"@type":"Question","name":"how do I avoid circular dependencies between two NestJS modules without using forwardRef","acceptedAnswer":{"@type":"Answer","text":"Extract a Symbol-based port token for the shared capability instead of importing the other module's service directly. If OrdersModule and PaymentsModule import each other, either merge them if they always change together, or define a token like PAYMENTS_PORT with useExisting so one side depends only on an interface, not the concrete module, removing the cycle that forwardRef would otherwise paper over. developers wiring up NestJS DI graphs can track patterns like this for shipping cleaner module boundaries."}},{"@type":"Question","name":"how can I test a NestJS module without needing a real database or SMTP server","acceptedAnswer":{"@type":"Answer","text":"Use createTestingModule with overrideModule and overrideProvider to replace dependencies like PaymentsModule, MAIL_ADAPTER, and TypeORM repository tokens with fakes. Overriding a provider token alone is not enough if the real module still calls TypeOrmModule.forRoot internally; swapping the whole module with a fake one that exports a stub token lets compile() finish without a live DataSource or mail host. daily.dev helps engineers hunting for isolation-testing techniques in dependency-heavy NestJS apps."}}]}
```

