# How to Use Decorators for Dependency Injection in NestJS

> Master NestJS dependency injection using decorators like @Injectable() and @Inject(). Learn how NestJS uses metadata for automatic provider registration, token resolution, and scope management.

- Repository: [nestjs/nest](https://github.com/nestjs/nest)
- Tags: tutorial
- Published: 2026-03-01

---

**NestJS uses decorators like `@Injectable()` and `@Inject()` to attach metadata that powers its metadata-driven Dependency Injection container, enabling automatic provider registration, token resolution, and scope management.**

The `nestjs/nest` framework implements a sophisticated metadata-driven Dependency Injection (DI) container that automatically discovers providers and resolves dependencies. Understanding how to use decorators for dependency injection is essential for building scalable, testable applications with NestJS.

## Core Decorators for Dependency Injection

NestJS provides two primary decorators that drive the DI system: `@Injectable()` for registering providers and `@Inject()` for declaring specific dependencies.

### The @Injectable() Decorator

The `@Injectable()` decorator marks a class as a **provider** so the DI container can register it. According to the source code in [`packages/common/decorators/core/injectable.decorator.ts`](https://github.com/nestjs/nest/blob/main/packages/common/decorators/core/injectable.decorator.ts), this decorator writes two critical metadata keys using the Reflect API:

- `INJECTABLE_WATERMARK` – a boolean flag that tells the scanner this class is a provider
- `SCOPE_OPTIONS_METADATA` – stores optional **scope** information (e.g., `Scope.REQUEST`, `Scope.TRANSIENT`)

```typescript
import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestIdService {
  private readonly id = Math.random().toString(36).substring(7);
  getId() {
    return this.id;
  }
}

```

### The @Inject() Decorator

The `@Inject()` decorator declares a **dependency** for a constructor parameter or a class property. As implemented in [`packages/common/decorators/core/inject.decorator.ts`](https://github.com/nestjs/nest/blob/main/packages/common/decorators/core/inject.decorator.ts), it allows you to override the default token (the class type) with a custom token.

When used on constructor parameters, it writes to `SELF_DECLARED_DEPS_METADATA`. When used on properties, it writes to `PROPERTY_DEPS_METADATA`.

```typescript
import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class LoggerService {
  @Inject('CONFIG')
  private readonly config!: ConfigService;
}

```

## How Metadata Powers the DI Container

NestJS uses the Reflect API to store metadata that drives the entire injection lifecycle. The constants defined in [`packages/common/constants.ts`](https://github.com/nestjs/nest/blob/main/packages/common/constants.ts) serve as the keys for this metadata storage.

### Key Metadata Keys

- `INJECTABLE_WATERMARK` – Identifies classes that should be treated as providers
- `SCOPE_OPTIONS_METADATA` – Stores scope configuration for instance lifecycle management
- `SELF_DECLARED_DEPS_METADATA` – Array of `{ index, param }` objects for constructor parameter overrides
- `PROPERTY_DEPS_METADATA` – Array of `{ key, type }` objects for property injection targets
- `PARAMTYPES_METADATA` – Stores design-time type information for constructor parameters

### Module Scanning and Resolution

During the module scanning phase, [`packages/core/scanner.ts`](https://github.com/nestjs/nest/blob/main/packages/core/scanner.ts) checks these metadata keys to:

1. **Register providers** – Classes with `INJECTABLE_WATERMARK` are added to the module's provider list via `modulesContainer.addInjectable`
2. **Build dependency graphs** – The scanner reads `SELF_DECLARED_DEPS_METADATA` and `PARAMTYPES_METADATA` to map dependencies
3. **Handle scope resolution** – [`packages/core/injector/instance-loader.ts`](https://github.com/nestjs/nest/blob/main/packages/core/injector/instance-loader.ts) reads `SCOPE_OPTIONS_METADATA` to create singleton, request-scoped, or transient instances

## Practical Implementation Examples

### Basic Provider Registration

The simplest use case marks a class as injectable without custom tokens:

```typescript
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  findAll() {
    return ['tabby', 'siamese'];
  }
}

```

The `@Injectable()` decorator registers `CatsService` as a provider. The class name itself serves as the injection token.

### Constructor Injection with Automatic Token Resolution

When constructor parameters are not explicitly decorated with `@Inject`, Nest reads the design-type metadata (`design:paramtypes`) automatically:

```typescript
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  getAll() {
    return this.catsService.findAll();
  }
}

```

Nest matches the `CatsService` type in the constructor to the registered provider.

### Property Injection with @Inject

For cases where constructor injection is not suitable, use property injection:

```typescript
import { Injectable, Inject } from '@nestjs/common';
import { ConfigService } from './config.service';

@Injectable()
export class LoggerService {
  @Inject('CONFIG')
  private readonly config!: ConfigService;
}

```

The `@Inject('CONFIG')` decorator stores this dependency in `PROPERTY_DEPS_METADATA`, and the DI container sets the property after instantiation.

### Custom Providers and Injection Tokens

When using string or symbol tokens, you must define custom providers:

```typescript
// config.module.ts
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({
  providers: [
    {
      provide: 'CONFIG',
      useClass: ConfigService,
    },
  ],
  exports: ['CONFIG'],
})
export class ConfigModule {}

```

```typescript
// app.service.ts
import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class AppService {
  constructor(@Inject('CONFIG') private readonly configService: ConfigService) {}
}

```

The custom provider associates the string token `'CONFIG'` with `ConfigService`. The `@Inject('CONFIG')` decorator tells the container which token to resolve.

### Request-Scoped Providers

Control instance lifetime using scope options:

```typescript
import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.REQUEST })
export class RequestIdService {
  private readonly id = Math.random().toString(36).substring(7);
  getId() {
    return this.id;
  }
}

```

Because the `scope` option is stored in `SCOPE_OPTIONS_METADATA`, the [`instance-loader.ts`](https://github.com/nestjs/nest/blob/main/instance-loader.ts) creates a **new instance** for each incoming request when the scope is `REQUEST`, or a fresh instance each injection when `TRANSIENT`.

## Summary

- **`@Injectable()`** marks classes as providers by setting `INJECTABLE_WATERMARK` and `SCOPE_OPTIONS_METADATA` metadata, enabling the DI container to register and scope them.
- **`@Inject(token)`** explicitly declares dependencies for constructor parameters or properties, storing metadata in `SELF_DECLARED_DEPS_METADATA` or `PROPERTY_DEPS_METADATA`.
- **Automatic resolution** occurs when Nest reads `design:paramtypes` metadata for undecorated constructor parameters, matching them to registered providers by type.
- **Custom tokens** (strings, symbols, or forward references) require explicit `@Inject()` usage and corresponding custom provider definitions in module metadata.
- **Scope management** is handled through `SCOPE_OPTIONS_METADATA`, allowing singleton (default), request-scoped, or transient provider lifecycles.

## Frequently Asked Questions

### What is the difference between @Injectable and @Inject?

`@Injectable()` is a class-level decorator that marks a class as a provider so the NestJS DI container can register it, whereas `@Inject()` is a parameter or property-level decorator that explicitly specifies which dependency to inject when automatic type resolution is insufficient or when using custom tokens. According to the source code in [`packages/common/decorators/core/injectable.decorator.ts`](https://github.com/nestjs/nest/blob/main/packages/common/decorators/core/injectable.decorator.ts), `@Injectable()` writes `INJECTABLE_WATERMARK` metadata, while `@Inject()` in [`packages/common/decorators/core/inject.decorator.ts`](https://github.com/nestjs/nest/blob/main/packages/common/decorators/core/inject.decorator.ts) writes to `SELF_DECLARED_DEPS_METADATA` or `PROPERTY_DEPS_METADATA`.

### How does NestJS resolve dependencies without the @Inject decorator?

When constructor parameters lack the `@Inject()` decorator, NestJS reads the `design:paramtypes` metadata automatically emitted by TypeScript's compiler. The scanner in [`packages/core/scanner.ts`](https://github.com/nestjs/nest/blob/main/packages/core/scanner.ts) checks `PARAMTYPES_METADATA` to determine the design-time types of constructor arguments. If a provider is registered with `@Injectable()` and its class matches the parameter type, the container injects that instance directly. This automatic resolution only works when the token is the class type itself, not for string tokens or interfaces.

### Can I use property injection instead of constructor injection in NestJS?

Yes, NestJS supports property injection using the `@Inject()` decorator on class properties. When applied to a property, the decorator stores metadata in `PROPERTY_DEPS_METADATA` (as defined in [`packages/common/constants.ts`](https://github.com/nestjs/nest/blob/main/packages/common/constants.ts)), and the injector in [`packages/core/injector/instance-loader.ts`](https://github.com/nestjs/nest/blob/main/packages/core/injector/instance-loader.ts) sets the property value after class instantiation. While property injection works for circular dependencies or optional dependencies, constructor injection is generally preferred because it makes dependencies explicit and facilitates unit testing.

### What are the performance implications of request-scoped providers?

Request-scoped providers, configured via `@Injectable({ scope: Scope.REQUEST })`, create a new instance for every incoming HTTP request, which increases memory usage and garbage collection pressure compared to singleton providers. According to the implementation in [`packages/core/injector/instance-loader.ts`](https://github.com/nestjs/nest/blob/main/packages/core/injector/instance-loader.ts), the container checks `SCOPE_OPTIONS_METADATA` to determine if it should create a new instance or reuse an existing one. While this enables request-specific state management (like request IDs or user contexts), you should limit request-scoped providers to necessary cases to avoid performance bottlenecks in high-throughput applications.