Skip to main content

Service Usage Guide

Services are the main application boundary for CRUD, relation loading, soft-delete restore flows, and cache behavior.

Generate a service

eloquent make:service User
eloquent make:service User --test

Generated methods

  • all()
  • find(id)
  • create(data)
  • createMany(rows)
  • update(id, data)
  • delete(id)
  • restore(id)

Extend the generated service

import { User } from "../models/User";

export class UserService {
  async all() {
    return new User().all();
  }

  async find(id: number | string) {
    return User.find(id);
  }

  async findByEmail(email: string) {
    return User.findOneBy("email", email);
  }

  async recentActive(limit = 20) {
    return User.where("is_active", true)
      .orderBy("created_at", "desc")
      .limit(limit)
      .get();
  }

  async findWithPosts(id: number | string) {
    const user = await User.find(id);
    if (!user) return null;
    await (user as { load?: (...relations: string[]) => Promise<unknown> }).load?.("posts");
    return user;
  }

  async create(data: Record<string, unknown>) {
    return User.create(data);
  }

  async createMany(rows: Record<string, unknown>[]) {
    return User.createMany(rows);
  }

  async update(id: number | string, data: Record<string, unknown>) {
    const user = await User.find(id);
    if (!user) return null;
    user.update(data);
    await user.save();
    return user;
  }

  async delete(id: number | string) {
    return User.deleteById(id);
  }

  async restore(id: number | string) {
    return User.restoreById(id);
  }

  async deactivateMany(ids: Array<number | string>) {
    await User.updateMany(ids, { status: "inactive" });
  }
}
Eager-loading note:
  • load() and with() require explicit relation methods on the model instance
  • schema relation metadata alone is not enough for eager-loading calls

Mixin-aware service patterns

SoftDeletesMixin

  • restore(id) via User.restoreById(id) or loaded-instance restore flows
  • withTrashed()
  • onlyTrashed()
  • forceDelete(id)
Keep these flows in the service so controllers stay HTTP-only.

EagerLoadingMixin

Load relations in service methods, then return hydrated records. Example model method:
posts() {
  return this.hasMany(Post as any, "user_id");
}

SerializeMixin

If you use hidden/appended fields, return toObject() from the service for API-safe payloads.

CastsMixin

Define casts in the model. Service reads then consume already-normalized values.

ScopeMixin

Global scopes affect all() and find() automatically. Current behavior is in-memory post-fetch filtering.

HooksMixin

Services do not call hooks manually. Model hooks fire through User.create(...), loaded-instance update(...); save(), and delete paths.

PivotHelperMixin

Use attach, detach, and sync from services for many-to-many workflows.

Cache

For consumer-facing code, prefer CacheManager at the service boundary:
import { CacheManager, setupCache } from "@alpha.consultings/eloquent-orm.js";

setupCache();
Wrap read methods with cache lookups and invalidate keys after writes.

Driver note

The service surface stays the same for SQL and Mongo models. Keep ids as number | string and let model choice control driver behavior.