Services & Dependency Injection
ជំពូកនេះមិនទាន់ត្រូវបានបកប្រែជាភាសាខ្មែរនៅឡើយទេ — សូមអានជាភាសាអង់គ្លេសខាងក្រោម។
Table of Contents
- What Is a Service?
- Why Logic Doesn't Belong in the Controller
- Dependency Injection, Concretely
- Moving Real Logic Into
TasksService - Beginner Pitfalls
1. What Is a Service?
A service is a class decorated with @Injectable() that holds
business logic and data access — everything a controller shouldn't have
to know about. @Injectable() marks the class as something Nest's
dependency injection container can create and hand to other classes.
2. Why Logic Doesn't Belong in the Controller
The controller's job is to translate HTTP ↔ application calls: read the
request, call a method, shape the response. If a PATCH handler starts
containing if statements about business rules, two problems show up
fast:
- The same logic can't be reused from anywhere except that one HTTP route (a background job, a CLI script, a test — none of them can call it without going through HTTP).
- Testing the logic means spinning up HTTP requests, instead of just calling a plain method.
Keeping logic in the service keeps the controller thin and the logic reusable and independently testable.
3. Dependency Injection, Concretely
In Part 3, TasksController
already declared this:
constructor(private readonly tasksService: TasksService) {}
Nobody ever writes new TasksService(). Because TasksService is
@Injectable() and listed in TasksModule's providers, Nest
constructs one instance of it and passes it into TasksController's
constructor automatically when the app boots. This is dependency
injection: classes declare what they need, and the framework provides
it — you don't wire it by hand.
Note: by default, Nest creates a single shared instance of a provider (a singleton) reused across every class that injects it.
4. Moving Real Logic Into TasksService
Replace the placeholder methods from Part 3 with an in-memory implementation — real persistence arrives in Part 6, but the shape of the service won't change, only what's inside these methods:
// src/tasks/tasks.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
export interface Task {
id: number;
title: string;
description?: string;
done: boolean;
}
@Injectable()
export class TasksService {
private tasks: Task[] = [];
private nextId = 1;
findAll(): Task[] {
return this.tasks;
}
findOne(id: number): Task {
const task = this.tasks.find((t) => t.id === id);
if (!task) throw new NotFoundException(`Task ${id} not found`);
return task;
}
create(data: { title: string; description?: string }): Task {
const task: Task = { id: this.nextId++, done: false, ...data };
this.tasks.push(task);
return task;
}
update(id: number, data: Partial<Pick<Task, 'title' | 'description' | 'done'>>): Task {
const task = this.findOne(id);
Object.assign(task, data);
return task;
}
remove(id: number): { removed: true } {
const index = this.tasks.findIndex((t) => t.id === id);
if (index === -1) throw new NotFoundException(`Task ${id} not found`);
this.tasks.splice(index, 1);
return { removed: true };
}
}
TasksController doesn't need to change at all — it already just calls
this.tasksService.*. That's the payoff of the controller/service split:
the HTTP layer was done in Part 3
and stays untouched while the real logic gets built underneath it.
5. Beginner Pitfalls
- Injecting a class that isn't
@Injectable(). Nest can only construct and inject providers it knows about — a plain class without the decorator (or not listed in a module'sproviders) throws at startup. - Assuming a new instance per request. Providers are singletons by
default — don't store per-request state on
thisin a service, or two concurrent requests will stomp on each other's data. - Reaching into another module's service directly. If
OrdersServiceneedsTasksService, it must be exported fromTasksModuleand imported intoOrdersModule— see Part 2.
Official docs:
ទិញកាហ្វេជូនខ្ញុំ
ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!