Suggest an editImprove this articleRefine the answer for “What are providers and how does dependency injection work in NestJS?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**A NestJS provider** is any class the Nest IoC container builds and injects for you, instead of you calling `new`. The container reads TypeScript constructor types through `reflect-metadata`, matches them to the module's `providers` array, and passes a cached instance in. Services are the most common shape, but `useValue`, `useClass`, `useFactory`, and `useExisting` let you register anything. ```typescript @Injectable() export class UsersService { findAll() { return [{ id: 1 }]; } } @Controller('users') export class UsersController { // Nest reads the type and injects UsersService automatically constructor(private readonly users: UsersService) {} } ``` **Key point:** providers are recipes cached by token inside the IoC container. By default you get one instance per application unless you opt into `Scope.REQUEST` or `Scope.TRANSIENT`.Shown above the full answer for quick recall.Answer (EN)Image**A NestJS provider** is any class the Nest IoC container builds and injects for you, instead of you writing `new` by hand. Services, repositories, factories, even plain config values - all of them can be providers. The container reads constructor parameter types through `reflect-metadata` at bootstrap, finds the matching entry in the module's `providers` array, and passes the cached instance in before any request reaches your controller. ## Theory ### TL;DR - Providers let the container handle object creation; controllers declare what they need, not how to build it - Analogy: the IoC container is a kitchen that takes orders (constructor parameters) and delivers assembled dishes (instances) - controllers never touch the ingredients - Main difference: `new UsersService()` ties classes together; a provider decouples them so swapping one service updates every consumer automatically - Three requirements: `@Injectable()` on the class, the class in the module's `providers` array, and `emitDecoratorMetadata: true` in `tsconfig.json` - Default scope is singleton per application; opt into `Scope.REQUEST` or `Scope.TRANSIENT` when you need per-request or per-injection instances ### Quick example ```typescript // users.service.ts import { Injectable } from '@nestjs/common'; @Injectable() // marks this class as a container-managed provider export class UsersService { findAll() { return [{ id: 1, name: 'Alice' }]; } } // users.controller.ts import { Controller, Get } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private readonly users: UsersService) {} // container injects here @Get() findAll() { return this.users.findAll(); // no new UsersService() anywhere } } // users.module.ts @Module({ controllers: [UsersController], providers: [UsersService], // register with the container }) export class UsersModule {} ``` The controller never calls `new`. The module registers `UsersService` as a recipe, the container builds it once at bootstrap, and injects the cached instance into any constructor that declares the type. ### Why `new` breaks Imagine `UsersController` calling `new UsersService()` directly. Works once. Then you need to mock the service in tests, share one instance across two controllers, or swap it for a `MockUsersService` on staging, and every place that wrote `new UsersService()` has to change at the same time. Dependency injection (DI) fixes this. You declare what you need, the container decides how to deliver it. In Nest, that container manages classes called providers. This is the answer the interviewer wants at a junior NestJS interview - not "a class with `@Injectable`", but an explanation of why `new` fails and what the container does instead. ### How the container resolves constructor types When you write `constructor(private users: UsersService)`, the TypeScript compiler stores the parameter types in compiled output that `reflect-metadata` can read. Nest reads that metadata array at module load, looks up each type in the module's `providers` registry, and builds the dependency graph from the bottom up, leaf dependencies first. Three things must be true simultaneously for injection to work: - The class has `@Injectable()` - The class is in the `providers` array of a module the app sees - `tsconfig.json` has both `emitDecoratorMetadata` and `experimentalDecorators` set to `true` Break any one of these and you get "Nest can't resolve dependencies of X" at startup. ### What happens at bootstrap Here is the exact sequence every Nest app goes through when `NestFactory.create()` runs: 1. Nest scans every `@Module()` and collects its `providers`, `controllers`, and `imports` 2. For each provider, Nest reads the constructor parameter types via `reflect-metadata` 3. Nest builds a directed acyclic graph of providers and resolves deeper dependencies first 4. Nest constructs one instance per provider in the right order and caches it by token 5. When an HTTP request arrives, the cached controller already holds the cached service, so the handler runs with no additional wiring Step 3 is where circular dependencies break things. If `UsersService` needs `AuthService` and `AuthService` needs `UsersService`, there is a cycle and the container cannot decide which to build first. The fix is `forwardRef(() => OtherService)` on both sides, which tells the container to resolve the reference lazily. ### When to use providers - **Shared business logic** - a `UsersService` used by multiple controllers belongs as a provider - **Testability** - inject a mock via `useClass` or `useValue` without touching source code - **External libraries** - wrap a database client in a provider with `useFactory` for async initialization - **Config values** - `useValue` or `useFactory` providers for environment-driven settings - **One-off calculation** - a local function is fine; no need to register a provider for something used in exactly one place ### Custom providers: useValue, useClass, useFactory, useExisting Nine out of ten providers are just classes. The tenth one needs a custom provider - a small object that tells the container how to build the value rather than having it call `new` directly. Knowing when to pick which form comes up as a follow-up in senior interviews. ```typescript // useValue: hand the container an already-constructed value { provide: 'APP_CONFIG', useValue: { maxRetries: 3, region: 'eu' } } // useClass: swap one class for another with the same shape { provide: MailerService, useClass: MockMailerService } // useFactory: run code at bootstrap (can be async) to produce the instance { provide: 'DB_CONNECTION', useFactory: async (config: ConfigService) => createPool(config.get('DB_URL')), inject: [ConfigService], // container resolves this before running the factory } // useExisting: alias one provider token to another already registered { provide: 'LOGGER', useExisting: PinoLoggerService } ``` The token (`provide:`) is how the container identifies a provider. For class providers the token is the class itself, which is why `private users: UsersService` works without any extra annotation. For string or symbol tokens, pull the value in with `@Inject('DB_CONNECTION') private db: Pool`. Think of each shape as a recipe variant: `useValue` is a recipe with the result already baked in, `useClass` points to a different set of instructions, `useFactory` runs the recipe yourself, and `useExisting` is an alias to a recipe already in the container. ### Scope: singleton, request, transient Providers are singletons by default. One instance per application, shared across every controller that injects it. `Scope.REQUEST` creates a fresh instance per HTTP request. `Scope.TRANSIENT` creates a fresh instance every time something injects the provider. The singleton default is what makes two controllers share the same `UsersService` object in memory - a user created by one controller is immediately visible to the other with no extra work, because they hold a reference to the exact same object. Change the scope and that guarantee disappears. One scope bug that trips people regularly: inject a `Scope.REQUEST` provider into a default-scoped service and the singleton captures the first request's instance and holds it forever. On a Nest 10 project we injected a request-scoped `TenantContext` into a default-scoped `BillingService`, and every request started sharing the first tenant's context because the outer singleton never released it. The fix was to make the whole dependency chain request-scoped, not just the leaf provider. The Nest docs warn about this, but most people find that page after the bug, not before. ### Common mistakes **Forgetting `@Injectable()`** ```typescript // Wrong - no decorator export class UsersService { findAll() { return []; } } // Nest skips this class during provider resolution; controller receives undefined ``` Add `@Injectable()` and the container recognizes the class. **Listing a provider in the wrong module** ```typescript // Wrong - UsersService declared in AuthModule but UsersController is in UsersModule @Module({ providers: [UsersService] }) export class AuthModule {} @Module({ controllers: [UsersController] }) // no imports: [AuthModule] export class UsersModule {} // Error: Nest can't resolve dependencies of UsersController ``` `UsersModule` must either declare `UsersService` in its own `providers`, or `AuthModule` must export it and `UsersModule` must import `AuthModule`. Forgetting the `exports` line is the most common reason this error fires on a fresh setup. **Circular dependency without forwardRef** ```typescript // UsersService injects AuthService, AuthService injects UsersService // Without forwardRef: bootstrap crashes with a circular dependency error // With forwardRef: @Injectable() export class UsersService { constructor( @Inject(forwardRef(() => AuthService)) private auth: AuthService ) {} } ``` **Scope mismatch: request-scoped inside a singleton** ```typescript // Wrong: TenantContext is REQUEST-scoped, BillingService is singleton @Injectable() export class BillingService { constructor(private tenant: TenantContext) {} // captures first request's tenant forever } // Fix: make BillingService request-scoped too @Injectable({ scope: Scope.REQUEST }) export class BillingService { constructor(private tenant: TenantContext) {} // fresh instance per request } ``` ### Real-world usage - **NestJS CLI apps** - every generated service (`AuthService`, `UsersService`) is a provider by default - **Prisma** - `PrismaService extends PrismaClient` registered as a provider and injected into repositories - **TypeORM** - entity repos via `@InjectRepository(User)`, which is a custom provider under the hood - **BullMQ** - queue processors as providers injected into controllers or other services - **ConfigService** - global config provider loaded once at bootstrap, injected anywhere via constructor - **Testing** - `Test.createTestingModule({ providers: [...] }).overrideProvider(UsersService).useValue(mockService)` swaps a real service for a mock without touching source code ### Follow-up questions **Q:** How does Nest know which class to inject for a constructor parameter? **A:** TypeScript emits the parameter types into compiled output when `emitDecoratorMetadata` is true. Nest reads that metadata via `reflect-metadata`, matches each type to a token in the module's provider registry, and resolves the instance. Without the flag, every parameter shows up as `Object` and injection fails. **Q:** What is the difference between singleton, request, and transient scope? **A:** Singleton (default) creates one instance per application shared everywhere. `Scope.REQUEST` creates a new instance per HTTP request. `Scope.TRANSIENT` creates a new instance for every injection point. Use `@Injectable({ scope: Scope.REQUEST })` to opt out of the singleton default. **Q:** When would you use `useFactory` instead of `useClass`? **A:** When the provider needs async initialization or arguments the constructor cannot receive from the container. Database connection pools, Redis clients, and config-driven factory functions are all `useFactory` cases. `useClass` is for synchronous class substitution only. **Q:** How do you mock a provider in tests without `jest.mock()`? **A:** Create a `TestingModule` and call `.overrideProvider(UsersService).useValue(mockService)` before `.compile()`. This swaps the provider in the container graph without touching global module state, which makes test isolation cleaner than module-level mocking. **Q:** Can NestJS DI work without TypeScript? **A:** Class-based injection relies on `emitDecoratorMetadata`, so plain JavaScript cannot use it directly. The workaround is custom providers with explicit `inject` arrays, which tell the container exactly what to pass in without relying on reflected types. **Q:** Is a service the same thing as a provider? **A:** A service is a provider, but a provider is not always a service. Repositories, factories, value bindings, and connection pools are all providers in the IoC container even though none of them fit the "service" label. The container does not care what you call the class. ## Examples ### Basic: controller injecting a service ```typescript // users.service.ts import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private list = ['Alice', 'Bob']; findAll() { return this.list; } findOne(name: string) { return this.list.find(u => u === name) ?? null; } } // users.controller.ts import { Controller, Get, Param } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private readonly users: UsersService) {} @Get() findAll() { return this.users.findAll(); } @Get(':name') findOne(@Param('name') name: string) { return this.users.findOne(name); } } // GET /users -> ["Alice", "Bob"] // GET /users/Alice -> "Alice" // GET /users/Charlie -> null ``` The controller has no idea how `UsersService` is built. It declares the type in the constructor and the container handles the rest. No factory, no `new`, no manual wiring. ### Intermediate: custom provider for repository injection This pattern appears in e-commerce or SaaS backends where you want to swap the database layer for a mock in tests without changing `AuthService`. ```typescript // users.repository.ts export interface UsersRepository { findByEmail(email: string): Promise<{ id: number; email: string } | null>; } // prisma-users.repository.ts @Injectable() export class PrismaUsersRepository implements UsersRepository { constructor(private prisma: PrismaService) {} async findByEmail(email: string) { return this.prisma.user.findUnique({ where: { email } }); } } // auth.module.ts @Module({ providers: [ AuthService, { provide: 'USER_REPO', // string token for the interface useClass: PrismaUsersRepository, // swap to MockUsersRepository in tests }, ], controllers: [AuthController], }) export class AuthModule {} // auth.service.ts @Injectable() export class AuthService { constructor( @Inject('USER_REPO') private usersRepo: UsersRepository, ) {} async validateUser(email: string) { const user = await this.usersRepo.findByEmail(email); return user ? { id: user.id, email: user.email } : null; } } // validateUser('alice@example.com') -> { id: 1, email: 'alice@example.com' } // validateUser('nobody@example.com') -> null ``` The string token `'USER_REPO'` decouples `AuthService` from the concrete class. In the test module, replace `useClass: PrismaUsersRepository` with `useValue: { findByEmail: jest.fn() }` and the service never knows the difference. ### Senior: async factory provider for a database pool ```typescript // database.module.ts import { Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Pool } from 'pg'; @Module({ providers: [ { provide: 'DB_POOL', useFactory: async (config: ConfigService): Promise<Pool> => { const pool = new Pool({ connectionString: config.get('DATABASE_URL') }); await pool.connect(); // verify connection at bootstrap, not at first request return pool; }, inject: [ConfigService], // container resolves ConfigService first, then runs factory }, ], exports: ['DB_POOL'], }) export class DatabaseModule {} // orders.service.ts import { Inject, Injectable } from '@nestjs/common'; import { Pool } from 'pg'; @Injectable() export class OrdersService { constructor(@Inject('DB_POOL') private db: Pool) {} async findByUser(userId: number) { const { rows } = await this.db.query( 'SELECT * FROM orders WHERE user_id = $1', [userId] ); return rows; } } ``` The factory runs once during `NestFactory.create()`. If the database URL is wrong or the server is unreachable, the app crashes at startup instead of failing silently on the first request. That is exactly the point of async factory providers.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.