NestJS Providers and Services

Providers are one of the most important concepts in NestJS. Almost everything that is not a controller or a module is a provider — services, repositories, factories, helpers. The most common type of provider is a service. This topic explains what providers are, how services work, and how NestJS manages them behind the scenes.

What Is a Provider?

A provider is any class that NestJS can create and inject into other classes automatically. The word "provider" describes the role: this class provides something — a feature, a piece of data, a utility — to other parts of the application.

Think of a power socket on a wall. The socket provides electricity. Anything that needs electricity — a lamp, a laptop, a fan — plugs into the socket. The lamp does not generate its own electricity. It receives it. In NestJS, a service is the socket. A controller is the lamp. The controller plugs in and receives what it needs.

What Is a Service?

A service is a provider whose job is to hold business logic. When a controller needs to find a user, validate a password, send an email, or process an order, it calls a service method. The service does the actual work.

A service is a plain TypeScript class decorated with @Injectable(). This decorator tells NestJS that this class can be injected into other classes.

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

@Injectable()
export class UsersService {

  private users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ];

  findAll() {
    return this.users;
  }

  findOne(id: number) {
    return this.users.find(user => user.id === id);
  }

  create(name: string) {
    const newUser = { id: this.users.length + 1, name };
    this.users.push(newUser);
    return newUser;
  }

}

How a Service Connects to a Controller

Request: GET /users/1
           |
           v
     UsersController
       findOne(@Param('id') id)
           |
           | calls
           v
     UsersService
       findOne(1) → { id: 1, name: 'Alice' }
           |
           v
     Response: { "id": 1, "name": "Alice" }

The controller focuses on routing. The service focuses on logic. Neither crosses into the other's territory.

Registering a Service in a Module

For NestJS to inject a service, you register it in the module's providers array:

@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

When the CLI generates a service with nest g service users, it registers the service in the module automatically. You do not need to add it manually.

The @Injectable Decorator

The @Injectable() decorator marks a class as a candidate for dependency injection. Without it, NestJS cannot inject the class into other components.

Every time you create a service manually (not via CLI), add @Injectable() at the top of the class. Forgetting this decorator causes a runtime error where NestJS cannot find the provider.

Types of Providers

Services are the most common providers, but NestJS supports other types too:

Provider Type   | Purpose
----------------|----------------------------------------------
Service         | Business logic (most common)
Repository      | Database access layer
Factory         | Dynamically creates other providers
Value Provider  | Injects a plain value or constant
Class Provider  | Swaps one class for another (useful in testing)

Value and Factory Providers

You can inject non-class values using the object syntax in the providers array:

// Injecting a constant value
providers: [
  {
    provide: 'API_KEY',
    useValue: 'abc-123-xyz',
  }
]

// Injecting it in a service
constructor(@Inject('API_KEY') private apiKey: string) {}

Factory providers let you run code to determine the provider value at runtime — useful when you need to set up a connection or read an environment variable before creating the provider.

Scope of a Provider

By default, NestJS creates one instance of each provider per module and reuses it for every request — this is the singleton scope. Two controllers in the same module share the exact same service instance.

For advanced use cases, you can change the scope:

Scope           | Instance Created
----------------|---------------------------------------------
DEFAULT         | Once per application (singleton) — default
REQUEST         | Once per incoming HTTP request
TRANSIENT       | A fresh instance every time it is injected

Singleton scope suits most applications. Request scope works well when a provider must hold request-specific data like the current logged-in user.

Exporting Providers

A provider registered in one module is private to that module by default. To share it with other modules, export it from the exports array:

@Module({
  providers: [UsersService],
  exports: [UsersService],     ← makes it available to importing modules
})
export class UsersModule {}

Any module that imports UsersModule can then inject UsersService without re-registering it.

Services are the workhorses of a NestJS application. Every meaningful operation — reading from a database, processing a payment, sending a notification — belongs in a service. Keeping this logic out of controllers ensures each part of your code has one clear purpose and remains easy to test independently.

Leave a Comment

Your email address will not be published. Required fields are marked *