NestJS — Enterprise Node.js

Providers & Services

16 min Lesson 5 of 30

Providers & Services

If controllers are the "how requests come in," providers are the "where the work happens." A provider is any class NestJS can inject as a dependency — most commonly a service that holds business logic. This lesson is where dependency injection, the heart of NestJS, finally clicks.

Defining a service

A service is a class marked with the @Injectable() decorator. That decorator tells NestJS, "this class can be managed and injected":

import { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private users = [{ id: 1, name: 'Sara' }]; findAll() { return this.users; } create(name: string) { const user = { id: Date.now(), name }; this.users.push(user); return user; } }

Injecting a service into a controller

You never call new UsersService() yourself. Instead you declare it as a constructor parameter and NestJS supplies the instance — this is dependency injection:

import { Controller, Get, Post, Body } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Get() findAll() { return this.usersService.findAll(); } @Post() create(@Body('name') name: string) { return this.usersService.create(name); } }

The private readonly usersService shorthand both declares and assigns the dependency in one line. NestJS sees the type UsersService and injects the right instance.

How does NestJS know what to inject? It reads the constructor's parameter types (thanks to TypeScript metadata) and looks them up in the module's provider list. Match found → instance injected.

Registering the provider

For injection to work, the service must be listed in a module's providers array:

import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ controllers: [UsersController], providers: [UsersService], }) export class UsersModule {}

Singletons by default

By default every provider is a singleton — NestJS creates one instance and shares it across the entire application. Inject UsersService into ten controllers and all ten share the same object and its state.

Why this matters: singletons make services perfect for shared resources like a database connection or an in-memory cache — created once, reused everywhere, no wasteful duplication.

Why dependency injection wins

  • Testability: in a test you inject a fake/mock service instead of the real one — no code change needed.
  • Loose coupling: classes depend on abstractions the container provides, not on objects they build themselves.
  • Single responsibility: controllers route, services compute, each stays small.
"Nest can't resolve dependencies of the X controller." This very common startup error almost always means a provider is missing from the providers array, or the module that exports it was not imported. Read the bracketed hint in the message — it names the missing dependency.

Summary

Providers — usually services marked @Injectable() — hold your business logic and are supplied to controllers through constructor injection. Register them in a module's providers array; they are singletons by default. Dependency injection is what keeps NestJS apps testable, loosely coupled, and easy to grow. This completes Phase 1: you can now build a structured, working NestJS API.