Learn NestJS - Providers & Dependency Injection
Episode 5 of 24

Learn NestJS - Providers & Dependency Injection

This episode covers providers as the core of NestJS dependency injection: creating services, injecting them into controllers, understanding singleton and request-scoped scopes, custom providers, factory providers, alias providers, and module boundaries and sharing providers between modules.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

If a controller is the gateway, then providers are the engine behind it. Providers contain the application's business logic, and NestJS manages their lifecycle through dependency injection. Understanding providers is the key to writing structured, easily testable applications.

Episode 5 covers providers from declaration to sharing between modules: basic services, scopes, custom providers, and module boundaries.

Creating a Service Provider

The @Injectable Decorator

Providers are marked with the @Injectable decorator. The most common way to create a service is through the Nest CLI:

Generate service users
nest g service users

This command creates users.service.ts and registers it in the related module.

JSService dasar dengan @Injectable
import { Injectable } from "@nestjs/common";
 
@Injectable()
export class UsersService {
  private readonly users: string[] = ["Arman", "Budi"];
 
  findAll(): string[] {
    return this.users;
  }
}

The @Injectable decorator marks this class as one that can be injected into other classes by the DI container.

Registering the Provider in a Module

A provider must be registered in the module's providers array so the container recognizes it:

JSMendaftarkan provider di module
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
 
@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

Dependency Injection

Constructor Injection

Now the service can be injected into the controller through its constructor:

JSMenyuntikkan service ke controller
@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
 
  @Get()
  findAll(): string[] {
    return this.usersService.findAll();
  }
}

You don't need to write new UsersService() — the container creates and injects it. This is called constructor injection.

Scoped Providers: Singleton vs Request-Scoped

By default, a provider is a singleton: one instance shared across the whole application. For per-request needs, use a scope:

JSProvider dengan scope request
import { Injectable, Scope } from "@nestjs/common";
 
@Injectable({ scope: Scope.REQUEST })
export class RequestLogService {
  private readonly requestId: string;
 
  constructor() {
    this.requestId = Math.random().toString(36);
  }
 
  getRequestId(): string {
    return this.requestId;
  }
}

With Scope.REQUEST, every request gets a new instance. This is useful for per-request data like a request id, but it's heavier on performance — use it only when necessary.

Custom Providers

useValue Providers

A provider doesn't have to be a class — it can also be a static value:

JSProvider dengan useValue
const CONFIG = {
  provide: "APP_NAME",
  useValue: "Belajar NestJS",
};

The APP_NAME token is a string used as the key. To consume it, use @Inject:

JSMengonsumsi provider token
import { Inject, Injectable } from "@nestjs/common";
 
@Injectable()
export class AppService {
  constructor(@Inject("APP_NAME") private readonly appName: string) {}
}

useFactory Providers

For values created dynamically or depending on other providers:

JSProvider dengan useFactory
const DB_PROVIDER = {
  provide: "DATABASE_URL",
  useFactory: (config: ConfigService) => {
    return config.get("DATABASE_URL");
  },
  inject: [ConfigService],
};

useFactory runs the function when the container initializes, and inject determines the dependencies injected into that function. This pattern is useful when the provider value needs to be computed from configuration or another provider.

Module Boundaries and Sharing Providers

Exporting Providers

Providers are private to their module. To use a provider in another module, export it first:

JSMengekspor provider
@Module({
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

Other modules that import UsersModule can now inject UsersService. This pattern preserves boundaries: modules can't see each other's internal providers, only the exported ones.

Conclusion

Episode 5 explains how providers form the foundation of NestJS dependency injection. You now understand service declaration, constructor injection, scopes, custom providers, and how to share providers between modules.

Key takeaways:

  • Providers are marked @Injectable and registered in the providers array.
  • Constructor injection is the primary way to inject dependencies.
  • Singleton is the default scope; Scope.REQUEST for per-request.
  • useValue provides static values; useFactory dynamic values.
  • @Inject uses string tokens for custom providers.
  • Providers must be exported to be usable by other modules.

In the next episode 6 we'll discuss data access and persistence — database integration with TypeORM, Sequelize, or Prisma, defining entities and migrations, the repository pattern and query builder, and using in-memory databases for development and testing.