Chapter 4: Providers & Services
Providers are the backbone of NestJS’s dependency injection system. They enable you to write modular, testable, and maintainable code. The most common provider is a service, which contains business logic and data access code. Think of providers as the “workers” in your application—they do the heavy lifting behind the scenes.
4.1 What is a Provider?
In NestJS, a provider is any class annotated with@Injectable(). Providers can be injected into controllers, other providers, or modules. They can be services, repositories, factories, or even values.
Types of Providers
Services:- Contain business logic
- Most common type of provider
- Stateless (usually)
- Orchestrate data flow
- Abstract data access
- Encapsulate database operations
- Make testing easier
- Can swap implementations
- Create instances dynamically
- Handle complex initialization
- Can be async
- Configuration objects
- Constants
- Third-party library instances
Provider Characteristics
All providers share these characteristics:- Decorated with
@Injectable() - Registered in a module’s
providersarray - Can be injected via constructor
- Managed by NestJS DI container
- Have a lifecycle (singleton, request-scoped, transient)
4.2 The Service Layer
The service layer encapsulates business logic and orchestrates data flow between controllers and repositories. Services should be focused on business rules, not HTTP or database details.Service Responsibilities
Services handle:- Business Logic: Core application rules and workflows
- Data Orchestration: Coordinate between multiple repositories
- Validation: Business-level validation (beyond DTO validation)
- Transformation: Transform data between layers
- Integration: Call external services, APIs, etc.
- HTTP concerns (controllers do this)
- Database queries (repositories do this)
- Request/response formatting (controllers/interceptors do this)
If your app is a restaurant, the service is the chef (prepares the meal), the controller is the waiter (takes orders, serves food), and the repository is the pantry (stores ingredients).
Basic Service Structure
Stateless Services
Services should be stateless when possible:- Easier to test
- Thread-safe
- Can be shared across requests
- No side effects between calls
4.3 Repository Pattern
Repositories abstract data access, making it easy to swap databases or mock for tests. This pattern keeps your business logic decoupled from the database implementation.Why Use Repositories?
Benefits:- Abstraction: Business logic doesn’t depend on database
- Testability: Easy to mock repositories in tests
- Flexibility: Can swap database implementations
- Separation of Concerns: Data access is isolated
- Reusability: Repository methods can be reused
Basic Repository
Repository with TypeORM
Repository Interface Pattern
Define interfaces for better abstraction:4.4 Domain-Driven Design (DDD)
For complex applications, consider using Domain-Driven Design principles. DDD helps you model your business domain and organize code around business concepts.DDD Building Blocks
Entities:- Objects with unique identity
- Can change over time
- Example: User, Order, Product
- Immutable objects with no identity
- Defined by their attributes
- Example: Email, Money, Address
- Groups of related entities
- Have a root entity (aggregate root)
- Maintain consistency boundaries
- Business logic that doesn’t fit in entities
- Stateless operations
- Coordinate between multiple entities
- Abstract data access
- Work with aggregates
- Provide domain-friendly interface
4.5 Dependency Injection in Services
Services can depend on other services, repositories, or configuration providers. Use constructor injection for clarity and testability.Injecting Dependencies
Service Depending on Service
Optional Dependencies
Property Injection (Not Recommended)
- Dependencies are explicit
- Easier to test (can see all dependencies)
- TypeScript can infer types
- Fail fast (errors at construction time)
4.6 Real-World Example: User Registration
Let’s see how services and repositories work together in a real-world scenario:Complete Registration Flow
Supporting Services
4.7 Service Composition
Services can be composed to build complex workflows:Orchestration Service
4.8 Error Handling in Services
Services should throw domain-specific exceptions:4.9 Best Practices
Following best practices ensures your services are maintainable and testable.Keep Services Stateless
Use Repositories for Data Access
Favor Constructor Injection
Write Unit Tests
Use Interfaces for Testability
Avoid Circular Dependencies
Separate Business Logic from Data Access
4.10 Summary
You’ve learned how to structure business logic using providers, services, and repositories: Key Concepts:- Providers: Injectable classes that provide functionality
- Services: Contain business logic and orchestrate workflows
- Repositories: Abstract data access from business logic
- DDD: Domain-Driven Design patterns for complex applications
- Dependency Injection: Services depend on other services/repositories
- Keep services stateless
- Use repositories for all data access
- Favor constructor injection
- Write unit tests for services
- Use interfaces for testability
- Avoid circular dependencies
- Separate business logic from data access