Table of Contents
- Introduction to Services
- Why Use Services?
- Creating Your First Service
- Understanding Dependency Injection in NestJS
- Service Scope: Default, Transient, and Request-Scoped
- Injecting Services into Controllers
- Using Interfaces for Service Contracts
- Dependency Injection Tokens
- Global Services and Singleton Behavior
- Testing Services with Mocks and Providers
- Best Practices for Writing Maintainable Services
- Summary and What’s Next
1. Introduction to Services
In NestJS, services are fundamental building blocks that encapsulate business logic and communicate with repositories, external APIs, or other services. By keeping business logic in services and separating it from controllers, your code becomes cleaner, more testable, and more modular.
2. Why Use Services?
Services are important for:
- Encapsulation of logic
- Code reuse across modules and controllers
- Better separation of concerns
- Easier testing and mocking
For instance, if you’re building a blog API, the logic for fetching, creating, updating, or deleting posts should reside in a PostsService
, not directly in the controller.
3. Creating Your First Service
NestJS provides the CLI command to create services easily:
bashCopyEditnest generate service users
# or shorthand
nest g s users
This will generate a file like:
tsCopyEdit@Injectable()
export class UsersService {
private users = [{ id: 1, name: 'Alice' }];
findAll() {
return this.users;
}
findOne(id: number) {
return this.users.find(user => user.id === id);
}
}
4. Understanding Dependency Injection in NestJS
NestJS uses a dependency injection (DI) container, powered by TypeScript decorators and metadata reflection, to manage the lifecycle of providers (classes that can be injected).
When you add @Injectable()
to a class, you’re telling NestJS that this class can be injected into other classes via its constructor.
Example of injecting UsersService
into a controller:
tsCopyEdit@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
getAllUsers() {
return this.usersService.findAll();
}
}
5. Service Scope: Default, Transient, and Request-Scoped
Services can be singleton (default), transient, or request-scoped.
- Singleton (default): One instance shared across all consumers
- Transient: A new instance is created each time it’s injected
- Request-scoped: A new instance per HTTP request
tsCopyEdit@Injectable({ scope: Scope.REQUEST })
export class ScopedService {
// scoped logic
}
6. Injecting Services into Controllers
The controller constructor is where services are injected. NestJS’s DI system resolves all dependencies recursively.
Example:
tsCopyEdit@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Get()
getPosts() {
return this.postsService.findAll();
}
}
7. Using Interfaces for Service Contracts
It’s good practice to define interfaces for your services. This allows better decoupling, especially for testing or when using multiple implementations.
tsCopyEditexport interface IUserService {
findAll(): User[];
}
Then you can implement it in a class:
tsCopyEdit@Injectable()
export class UsersService implements IUserService {
findAll(): User[] {
return [{ id: 1, name: 'Alice' }];
}
}
8. Dependency Injection Tokens
In some cases, especially with dynamic providers or multiple implementations, you’ll need to use DI tokens:
tsCopyEdit{
provide: 'CustomService',
useClass: CustomService,
}
You then inject it with:
tsCopyEditconstructor(@Inject('CustomService') private readonly service: CustomService) {}
9. Global Services and Singleton Behavior
Services are singleton by default and shared across your application. If you define a service in a module, it’s available only within that module unless exported and imported elsewhere.
If you want a global service:
tsCopyEdit@Global()
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}
10. Testing Services with Mocks and Providers
Services are very testable. You can use NestJS’s testing utilities and dependency injection to provide mock services.
tsCopyEditdescribe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should return users', () => {
expect(service.findAll()).toEqual([{ id: 1, name: 'Alice' }]);
});
});
11. Best Practices for Writing Maintainable Services
- Keep logic atomic and focused
- Avoid bloated services — split by domain
- Use interfaces for abstraction
- Use scopes only when necessary
- Reuse services via shared modules
12. Summary and What’s Next
Services in NestJS are the core units for business logic and are designed to work seamlessly with Nest’s DI system. Mastering them helps you build clean, scalable, and testable apps.