Home About Skills Products
Work
Projects Services Experience
Learn
Tutorials Courses Blogs Resources
Contact
Blog · NestJS

NestJS Modules, Providers, and Dependency Injection Explained

Advertisement

Modules, providers, and dependency injection are the three concepts that make NestJS feel different from plain Express — and once they click, the framework's structure stops feeling like ceremony and starts feeling like exactly the organization a growing API needs.

Modules: Grouping Related Code

A module bundles controllers, providers, and related feature code into one cohesive unit. Every Nest app has at least a root AppModule, and every feature typically gets its own:

@Module({
  controllers: [PostsController],
  providers: [PostsService],
  exports: [PostsService],
})
export class PostsModule {}

exports is what makes PostsService usable from OTHER modules that import PostsModule — without it, the service stays private to this module.

Providers: Anything Nest Can Inject

A provider is just a class Nest knows how to construct and hand to whoever asks for it — services, repositories, factories, even plain configuration objects. The @Injectable() decorator marks a class as available for injection:

@Injectable()
export class PostsService {
  constructor(private prisma: PrismaService) {}
}

Dependency Injection: Why It Matters

Instead of PostsService creating its own database connection with new PrismaService(), Nest's container creates ONE instance of PrismaService and hands the same instance to every class that asks for it. This has two huge practical benefits:

  • Testability — in a unit test, you can inject a fake/mock PrismaService instead of a real database connection, with zero changes to PostsService itself.
  • Single source of truth — expensive resources (database pools, HTTP clients) get created once, not once per class that needs them.

Scopes: Singleton by Default

By default every provider is a singleton — one instance for the entire application lifetime. You can opt into REQUEST scope for a provider that needs fresh state per incoming request, though this has a real performance cost and should be the exception, not the default.

Importing Between Modules

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

This is the whole system: modules group things, providers are the things, and dependency injection is how they find each other — without any class ever needing to know HOW to construct its own dependencies.

Getting Started with NestJS: Your First API in TypeScript

Getting Started with NestJS: Your First API in TypeScript

Installing Nest, generating a CRUD resource, and understanding controllers, services, and DTO validation.

Advertisement
Esc