Skip to main content

Cookbook

Use this page when you want complete, practical recipes instead of reading the API page by page.

Recipe 1: SQL User CRUD API

Use this when you want a standard REST API on SQL with generated model, service, and controller layers.
eloquent make:model User --with-migration
eloquent make:service User
eloquent make:controller User
eloquent make:migration --all
eloquent migrate:run --all-migrations
Model:
import { column } from "@alpha.consultings/eloquent-orm.js";
import { SqlModel, type ModelInstance } from "@alpha.consultings/eloquent-orm.js/Model";

type UserAttrs = {
  id?: number;
  name?: string;
  email?: string;
  created_at?: string | Date | null;
  updated_at?: string | Date | null;
};

export class User extends SqlModel<UserAttrs> {
  static tableName = "users";
  static connectionName = process.env.DB_CONNECTION ?? "sqlite";
  static schema = {
    id: column("increments", undefined, { primary: true }),
    name: column("string", 255),
    email: column("string", 255, { unique: true }),
    created_at: column("timestamp", undefined, { useTz: true }),
    updated_at: column("timestamp", undefined, { useTz: true }),
  };

  constructor() {
    super("users", process.env.DB_CONNECTION ?? "sqlite");
  }
}

export interface User extends ModelInstance<UserAttrs> {}
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 create(data: Record<string, unknown>) {
    return User.create(data);
  }

  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);
  }
}
Routes:
import express from "express";
import { UserController } from "./app/controllers/UserController";

const app = express();
app.use(express.json());

const controller = new UserController();
app.get("/users", controller.index.bind(controller));
app.get("/users/:id", controller.show.bind(controller));
app.post("/users", controller.store.bind(controller));
app.put("/users/:id", controller.update.bind(controller));
app.delete("/users/:id", controller.destroy.bind(controller));

Recipe 2: Blog API with Relations

Use this when you want a realistic relational example quickly.
eloquent make:scenario blog --test --controllers --services --run --force
Typical relation definitions:
posts: relation("hasMany", "Post", { foreignKey: "user_id" })
author: relation("belongsTo", "User", { foreignKey: "user_id" })
favorites: relation("belongsToMany", "Post", {
  pivotTable: "post_user_pivot",
  pivotLocalKey: "user_id",
  pivotForeignKey: "post_id",
})
comments: relation("morphMany", "Comment", { morphName: "commentable" })
commentable: relation("morphTo", "Commentable", { morphName: "commentable" })
Service query example:
async feed(limit = 20) {
  return Post.where("published", true)
    .orderBy("created_at", "desc")
    .limit(limit)
    .get();
}

Recipe 3: Mongo Document API

Use this when the app is document-first.
eloquent make:model GeoLocation --mongo
eloquent make:service GeoLocation
eloquent make:controller GeoLocation
import { column } from "@alpha.consultings/eloquent-orm.js";
import { MongoModel, type ModelInstance } from "@alpha.consultings/eloquent-orm.js/Model";

type GeoLocationAttrs = {
  id?: number;
  name?: string;
  latitude?: number;
  longitude?: number;
};

export class GeoLocation extends MongoModel<GeoLocationAttrs> {
  static tableName = "geolocations";
  static connectionName = "mongo";
  static schema = {
    id: column("increments", undefined, { primary: true }),
    name: column("string", 255),
    latitude: column("float"),
    longitude: column("float"),
  };

  constructor() {
    super("geolocations", "mongo");
  }
}

export interface GeoLocation extends ModelInstance<GeoLocationAttrs> {}
Keep the service and controller surface the same as SQL. Let the model base decide driver behavior.

Recipe 4: Soft Delete Admin Restore Flow

Use this when records should be recoverable from an admin screen. For PostgreSQL-focused apps, keep the soft-delete column explicit and timezone-aware.
eloquent make:controller User --soft
eloquent make:service User
Schema:
deleted_at: column("softDeletes", undefined, { useTz: true })
Service:
async trashed() {
  const model = new User() as User & { onlyTrashed?: () => Promise<unknown[]> };
  return model.onlyTrashed?.() ?? [];
}

async restore(id: number | string) {
  const user = await User.find(id);
  if (!user) {
    return;
  }
  await user.restore();
}
Routes:
app.get("/admin/users/trashed", async (_req, res) => {
  res.json(await service.trashed());
});

app.patch("/admin/users/:id/restore", controller.restore.bind(controller));

Recipe 5: Cache-First Dashboard Service

Use this when read endpoints are expensive and you need deterministic invalidation.
import { CacheManager, setupCache } from "@alpha.consultings/eloquent-orm.js";

setupCache();

export class DashboardService {
  async overview() {
    const key = "dashboard:overview:v1";
    const cached = await CacheManager.get(key);
    if (cached) return cached;

    const payload = {
      users: await User.where("is_active", true).get(),
      posts: await Post.orderBy("created_at", "desc").limit(10).get(),
    };

    await CacheManager.set(key, payload, 60);
    return payload;
  }

  async publishPost(id: number | string) {
    await Post.updateById(id, { published: true });
    await CacheManager.delete("dashboard:overview:v1");
  }
}

Recipe 6: Test-Only Application Surface

Use this when app and test artifacts must be isolated.
eloquent make:model User --test
eloquent make:service User --test
eloquent make:controller User --test
eloquent make:registry --test
eloquent make:migration --all --test
eloquent migrate:run --test --all-migrations
eloquent db:seed --test --class BlogScenarioSeeder
Generated paths:
  • src/test/database/models
  • src/test/services
  • src/test/controllers

Recipe 7: Mixin-Driven Service Layer

Use this when runtime behavior should go beyond plain CRUD. Practical mixin scenarios:
  • soft delete and restore
  • eager loading
  • serialization
  • casts
  • scopes
  • lifecycle hooks
  • pivot attach, detach, and sync
  • service-boundary caching
See Mixin Scenarios.

Next detailed guides