Services
Services contain the business logic of the application, orchestrating between controllers and repositories.
Service Structure
Services live in src/services/ and are injected into controllers.
typescript
import { Inject, Singleton } from "typescript-ioc"
@Singleton
export class BookService {
constructor(
@Inject private bookRepository: BookRepository,
@Inject private graphRepository: BookGraphRepository,
) {}
async findAll(): Promise<Book[]> {
return this.bookRepository.findAll()
}
async create(data: CreateBookRequest): Promise<Book> {
const book = await this.bookRepository.create(data)
await this.graphRepository.createNode(book)
return book
}
}Dependency Injection
Services use typescript-ioc for dependency injection:
typescript
// Mark as singleton for single instance
@Singleton
export class MyService {
constructor(
@Inject private repo: MyRepository,
@Inject private otherService: OtherService,
) {}
}Service Responsibilities
Do
- Business logic and validation
- Orchestrate multiple repositories
- Handle transactions
- Emit domain events
- Transform data between layers
Don't
- Handle HTTP concerns (headers, status codes)
- Access request/response objects
- Perform direct database queries
Transactions
Use TypeORM's transaction manager for multi-step operations:
typescript
async transferBook(fromUserId: string, toUserId: string, bookId: string) {
return this.dataSource.transaction(async (manager) => {
const bookRepo = manager.getRepository(Book)
const book = await bookRepo.findOneBy({ id: bookId })
book.ownerId = toUserId
await bookRepo.save(book)
// Additional operations in same transaction
})
}Error Handling
Throw typed errors that controllers can handle:
typescript
import { NotFoundError, ValidationError } from "../errors"
async findById(id: string): Promise<Book> {
const book = await this.bookRepository.findById(id)
if (!book) {
throw new NotFoundError("Book not found")
}
return book
}Testing Services
Services are the primary unit testing target:
typescript
describe("BookService", () => {
let service: BookService
let mockRepository: MockBookRepository
beforeEach(() => {
mockRepository = new MockBookRepository()
service = new BookService(mockRepository)
})
it("should find all books", async () => {
mockRepository.books = [{ id: "1", title: "Test" }]
const result = await service.findAll()
expect(result).toHaveLength(1)
})
})