Skip to content
NestJS ns providers 4 min read

Providers & Services

Providers are the heart of a NestJS application. They are plain classes that NestJS can inject as dependencies, letting you push business logic, data access, and reusable helpers out of your controllers and into focused, testable units. Almost everything that isn’t a controller — services, repositories, factories, gateways, and helpers — is a provider, and understanding them is the gateway to mastering Nest’s dependency injection system.

What is a provider?

A provider is any class that the Nest IoC (Inversion of Control) container knows how to construct and hand out to other classes that ask for it. The most common kind of provider is a service: a class that encapsulates a slice of your domain logic so it can be reused across controllers, scheduled jobs, or other services.

The defining feature of a provider is that it is injectable. Instead of a class creating its own dependencies with new, it declares them in its constructor and lets Nest supply fully-constructed instances. This decoupling is what makes Nest applications easy to test, swap, and reason about.

// cats/cat.interface.ts
export interface Cat {
  id: number;
  name: string;
  breed: string;
}

The @Injectable decorator

You mark a class as a provider with the @Injectable() decorator. This tells Nest that the class participates in the DI system and that its own constructor dependencies should be resolved by the container.

// cats/cats.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Cat } from './cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];
  private nextId = 1;

  create(name: string, breed: string): Cat {
    const cat: Cat = { id: this.nextId++, name, breed };
    this.cats.push(cat);
    return cat;
  }

  findAll(): Cat[] {
    return this.cats;
  }

  findOne(id: number): Cat {
    const cat = this.cats.find((c) => c.id === id);
    if (!cat) {
      throw new NotFoundException(`Cat #${id} not found`);
    }
    return cat;
  }
}

@Injectable() attaches metadata that Nest reads at startup. Without it, the container cannot reliably resolve the class’s own constructor parameters, so always decorate providers even when they have no dependencies of their own.

Tip: @Injectable() is required on the class doing the injecting so that TypeScript emits the constructor parameter types Nest relies on. A class only consumed (never injecting anything itself) can technically work without it, but decorating consistently avoids subtle resolution bugs.

Injecting a service

Once a class is a provider, any controller or other provider can request it by typing a constructor parameter. Nest reads the parameter’s type, looks it up in the container, and passes in a singleton instance.

// cats/cats.controller.ts
import { Body, Controller, Get, Param, Post, ParseIntPipe } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './cat.interface';

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

  @Post()
  create(@Body() body: { name: string; breed: string }): Cat {
    return this.catsService.create(body.name, body.breed);
  }

  @Get()
  findAll(): Cat[] {
    return this.catsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number): Cat {
    return this.catsService.findOne(id);
  }
}

The private readonly catsService: CatsService shorthand declares the parameter as a class property and asks Nest to inject it in one line — a TypeScript feature called parameter properties.

Registering providers in a module

A provider only exists in the container once a module declares it. You list providers in the providers array of a module’s @Module() metadata. Controllers go in controllers, and anything you want other modules to consume goes in exports.

// cats/cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {}

The shorthand providers: [CatsService] is sugar for the full provider object, which is useful when you need more control:

providers: [
  {
    provide: CatsService,   // the injection token
    useClass: CatsService,  // what to instantiate
  },
],
PropertyPurpose
provideThe token consumers inject by (often the class itself)
useClassClass to instantiate for that token
useValueA ready-made constant or mock to supply
useFactoryA function that builds the value, with its own injected deps
scopeLifetime: DEFAULT (singleton), REQUEST, or TRANSIENT

By default every provider is a singleton — Nest creates one instance and shares it across the whole application, so state like the in-memory cats array above is preserved between requests.

Output:

$ curl -s -X POST localhost:3000/cats -H 'Content-Type: application/json' \
    -d '{"name":"Misty","breed":"Tabby"}'
{"id":1,"name":"Misty","breed":"Tabby"}

$ curl -s localhost:3000/cats
[{"id":1,"name":"Misty","breed":"Tabby"}]

Best practices

  • Keep controllers thin: route, validate, and delegate — put real logic in @Injectable() services.
  • Decorate every provider with @Injectable() consistently, even dependency-free ones, to avoid metadata-resolution surprises.
  • Inject through the constructor with private readonly parameter properties rather than instantiating dependencies manually.
  • Export a provider only when another module genuinely needs it; keep the rest encapsulated.
  • Depend on abstractions where it helps testing — swap useClass for useValue mocks in specs.
  • Give each service a single, clear responsibility so it stays easy to test and reuse.
  • Reach for non-singleton scopes only when you truly need per-request state; singletons are faster and simpler.
Last updated June 14, 2026
Was this helpful?