Skip to content

Services

Services are responsible for executing the core business logic of your application, validating data rules, and interacting with databases. By separating this logic from the Controllers, you make your application more modular, testable, and maintainable.

DolphJS strongly encourages separating your business logic into two classes of services:

  • High-level Services: These act as orchestrators for complex operations. They do not typically interact with the database directly; instead, they interact with other (often low-level) services to piece together complex business operations.
  • Low-level Services: These perform highly specific, isolated tasks. A classic example of a low-level service is one that solely fetches or inserts data into the database (assuming no repository pattern is used) or interacts with a specific external utility or API.

This separation of concerns makes your services highly reusable and prevents monolithic “god classes” from forming across your application.

In DolphJS, a service must extend the DolphServiceHandler class. Crucially, you must pass a unique string identifier to the super() call in the constructor. This identifier helps the Dependency Injection (DI) system map the service correctly.

import { DolphServiceHandler } from '@dolphjs/dolph/classes';
import { Dolph } from '@dolphjs/dolph/common';
import { DService } from '@dolphjs/dolph/decorators';
@DService()
export class CatsService extends DolphServiceHandler<Dolph> {
private readonly cats: any[] = [];
constructor() {
// Pass a unique identifier for this service
super('catsService');
}
async create(cat: any) {
this.cats.push(cat);
return cat;
}
async findAll() {
return this.cats;
}
}

Once your service is defined, you group it with a controller inside a @Component.

import { Component } from '@dolphjs/dolph/decorators';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Component({ controllers: [CatsController], services: [CatsService] })
export class CatsComponent {}

The DI container will now instantiate CatsService as a singleton, shared by every controller in the app that declares it — not just CatsController. See Controllers → Dependency Injection for how a controller receives that instance.

Services often need to interact with a database. DolphJS provides specialized decorators to inject database models directly into your service.

import { DolphServiceHandler } from '@dolphjs/dolph/classes';
import { Dolph } from '@dolphjs/dolph/common';
import { InjectMongo, DService } from '@dolphjs/dolph/decorators';
import { Model } from 'mongoose';
import { catModel, ICatModel } from './cat.model';
// Inject the model into the 'catModel' property of this class
@InjectMongo('catModel', catModel)
@DService()
export class CatsService extends DolphServiceHandler<Dolph> {
// Declare the property so TypeScript knows about it
catModel!: Model<ICatModel>;
constructor() {
super('catsService');
}
async findAll() {
// Use the injected model to query the database
return await this.catModel.find({});
}
}

Similarly, if you are using MySQL (via Sequelize or similar), you can use @InjectMySQL.

import { DolphServiceHandler } from '@dolphjs/dolph/classes';
import { Dolph } from '@dolphjs/dolph/common';
import { InjectMySQL, DService } from '@dolphjs/dolph/decorators';
import { ModelStatic, Model as SqlModel } from 'sequelize';
import { catModel, ICatModel } from './cat.model';
@InjectMySQL('catModel', catModel)
@DService()
export class CatsService extends DolphServiceHandler<Dolph> {
catModel!: ModelStatic<SqlModel<any, any>>;
constructor() {
super('catsService');
}
async create(data: any) {
return await this.catModel.create(data);
}
}

Services are plain classes — no framework runtime is needed to unit test one. Construct it directly with new and hand in mocked collaborators in place of an injected model or dependency. See Testing → Tier 1 for a full example.