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.

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.
Providers are marked with the @Injectable decorator. The most common way to create a service is through the Nest CLI:
nest g service usersThis command creates users.service.ts and registers it in the related module.
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.
A provider must be registered in the module's providers array so the container recognizes it:
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}Now the service can be injected into the controller through its constructor:
@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.
By default, a provider is a singleton: one instance shared across the whole application. For per-request needs, use a scope:
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.
A provider doesn't have to be a class — it can also be a static value:
const CONFIG = {
provide: "APP_NAME",
useValue: "Belajar NestJS",
};The APP_NAME token is a string used as the key. To consume it, use @Inject:
import { Inject, Injectable } from "@nestjs/common";
@Injectable()
export class AppService {
constructor(@Inject("APP_NAME") private readonly appName: string) {}
}For values created dynamically or depending on other providers:
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.
Providers are private to their module. To use a provider in another module, export it first:
@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.
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:
@Injectable and registered in the providers array.Scope.REQUEST for per-request.useValue provides static values; useFactory dynamic values.@Inject uses string tokens for custom providers.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.