> ## Documentation Index
> Fetch the complete documentation index at: https://alphaconsultings.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Usage Guides

> End-to-end workflow patterns for runtime, migration, seeding, and tests.

# Usage Guides

## 1) Runtime setup

1. Configure `.env`.
2. Set `DB_CONNECTION`.
3. Register models at startup:

```ts theme={null}
registerModels([User, Post, ...]);
```

or generate a bootstrap helper:

```bash theme={null}
eloquent make:registry
```

Keep strict mode enabled by default unless you have migration-specific lazy bootstrap needs.

This project is intended to run with Express.js in request/route-based applications. Use Express.js without changes to run model registration and query workflows reliably.

## 2) Migration workflow

```bash theme={null}
eloquent make:migration User
eloquent make:migration --all --test
eloquent migrate:run --all-migrations
eloquent migrate:status
```

## 3) Seeding workflow

```bash theme={null}
eloquent db:seed --class UserSeeder
eloquent db:seed --test --class BlogScenarioSeeder
eloquent db:seed:fresh --class UserSeeder --force --yes
eloquent db:seed --all-connections --class UserSeeder
eloquent db:seed:precheck --all-connections
```

## 4) Multi-driver workflow

* Single target: `--mysql`, `--pg`, `--sqlite`
* All SQL connections: `--all-connections`
* Mongo target (SQL-only excluded): `--mongo`, `--mongo --test`
* For one app talking to different drivers at once, keep one `DB_CONNECTION` fallback and pin `static connectionName` on the models that must stay on a specific driver

Example mixed runtime:

```ts theme={null}
export class User extends SqlModel<UserAttrs> {
  static connectionName = "mysql";
}

export class GeoLocation extends MongoModel<GeoAttrs> {
  static connectionName = "mongo";
}
```

See the full contract in [Multi-Connection Strategy](../orm/multi-connection-strategy).

## 5) Test mode workflow

```bash theme={null}
eloquent make:model User --test
eloquent make:registry --test
eloquent make:migration --all --test
eloquent migrate:run --test --all-migrations
```

## 6) Recovery and safety

* Use `--force --yes` with production guard checks for destructive operations.
* Prefer least-privilege credentials:
  * `ELOQUENT_DB_ROLE=runtime`
  * `ELOQUENT_DB_ROLE=migration`
* Keep CLI prechecks in place before `--all-connections` seed runs.

Detailed runtime guides:

* [Common scenarios](./common-scenarios)
* [Controllers](./controllers)
* [Services](./services)

## 7) NoSQL quick path

Use explicit target flags and keep SQL operations out of implicit scopes.

```bash theme={null}
eloquent db:seed:precheck --mongo --test
eloquent make:scenario --test --mongo --run
```

## 8) Runtime querying patterns

### Important: runtime querying contract

The runtime read API is Laravel-like and should stay aligned with the public CRUD contract.

* static safe-finder helpers such as `User.where(...)`, `User.orderBy(...)`, `User.first()`, and `User.findOneBy(...)`
* static primary-key reads such as `User.find(id)`
* instance collection reads such as `new User().all()`
* eager-loading helpers such as `with(...)` and `load(...)` when explicit relation methods exist

Recommended app-level pattern:

1. use static query helpers for filtered reads
2. use `findOneBy(...)` or `first()` when you want one record or `null`
3. use `get()` when you want hydrated arrays
4. use `with(...)` only when the model exposes explicit relation methods

<details>
  <summary><strong>Single-record reads</strong></summary>

  Use these when you expect one record or no record.

  ```ts theme={null}
  const byId = await User.find(1);
  const byEmail = await User.findOneBy("email", "alice@example.com");
  const newestUser = await User.orderBy("created_at", "desc").first();
  ```

  Recommended when:

  * you need one result
  * you want a nullable contract instead of an array
  * the service should branch clearly on “found” vs “not found”
</details>

<details>
  <summary><strong>Collection reads</strong></summary>

  Use `get()` for hydrated arrays.

  ```ts theme={null}
  const allUsers = await new User().all();

  const recentActiveUsers = await User.where("is_active", true)
    .orderBy("created_at", "desc")
    .limit(20)
    .get();
  ```

  Use this when:

  * you are building list endpoints
  * you need ordered result sets
  * you want deterministic pagination-like behavior with an explicit limit
</details>

<details>
  <summary><strong>Filtering, ordering, and limits</strong></summary>

  Use the safe-finder chain to build explicit reads.

  ```ts theme={null}
  const admins = await User.where("role", "admin")
    .orderBy("created_at", "asc")
    .limit(10)
    .get();
  ```

  Rules:

  * `where(field, value)` filters by declared schema fields
  * `orderBy(field, direction)` supports only `asc` or `desc`
  * always pass the direction explicitly for predictable output
  * `limit(count)` should be used on user-facing list endpoints
</details>

<details>
  <summary><strong>Eager loading with <code>with(...)</code> and <code>load(...)</code></strong></summary>

  Use eager loading only when the model defines explicit relation methods.

  ```ts theme={null}
  const users = await User.where("is_active", true)
    .with("posts")
    .get();

  const user = await (new User()).with("posts", "profile").find(1);
  ```

  Important rules:

  * schema relation metadata alone is not enough
  * `with(...)` and `load(...)` require explicit relation methods on the model instance
  * use eager loading in services, not controllers
</details>

<details>
  <summary><strong>Scope-based query reads</strong></summary>

  Use scopes when a query pattern repeats.

  ```ts theme={null}
  const active = await User.active().first();
  const inactive = await User.inactive().get();
  const published = await Post.published().limit(10).get();
  ```

  This keeps controllers thin and avoids repeating the same `where(...)` clauses everywhere.
</details>

<details>
  <summary><strong>Recommended service-layer query pattern</strong></summary>

  ```ts theme={null}
  export class UserService {
    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 detail(id: number | string) {
      return (new User()).with("posts", "profile").find(id);
    }
  }
  ```

  This is the cleanest place to keep query composition, eager loading, and caching decisions.
</details>

See also:

* [Services](./services)
* [API Querying Reference](../api/querying)
* [Mixin Scenarios](../orm/mixin-scenarios)

## 9) Runtime CRUD patterns

### Important: runtime CRUD contract

The runtime write API is intentionally split into three layers:

* `User.create(data)`
  * create a new row or document
* loaded instance methods such as `update()`, `fill()`, `save()`, `delete()`, `restore()`, and `patch()`
  * update an already loaded model instance
* explicit low-level by-id methods such as `User.updateById(id, data)`, `User.deleteById(id)`, and `User.restoreById(id)`
  * direct writes when you already know the primary key

Recommended app-level pattern:

1. read with query helpers
2. update with `update() + save()` or `patch()` on a loaded instance
3. use `User.updateById(...)`, `User.deleteById(...)`, and `User.restoreById(...)` only when you intentionally want a direct by-id service path
4. use bulk helpers only when the service already owns a concrete list of ids or row payloads

<details>
  <summary><strong>Create: insert a new record</strong></summary>

  Use `User.create()` when you want to insert a brand new model in one call.

  ```ts theme={null}
  const created = await User.create({
    name: "Alice",
    email: "alice@example.com",
  });
  ```

  Bulk create:

  ```ts theme={null}
  const createdMany = await User.createMany([
    { name: "Alice", email: "alice@example.com" },
    { name: "Bob", email: "bob@example.com" },
  ]);
  ```

  Recommended when:

  * you already have validated request data
  * you do not need to keep a mutable instance before insert
  * you want the created record returned immediately
</details>

<details>
  <summary><strong>Read: load records before mutation</strong></summary>

  Use read helpers first when your next step depends on the current stored state.

  ```ts theme={null}
  const allUsers = await new User().all();
  const byId = await User.find(1);
  const byEmail = await User.findOneBy("email", "alice@example.com");
  ```

  Use query-builder style reads for filtered lists and sorted data:

  ```ts theme={null}
  const recentActiveUsers = await User.where("is_active", true)
    .orderBy("created_at", "desc")
    .limit(20)
    .get();

  const newestUser = await User.orderBy("created_at", "desc").first();
  ```
</details>

<details>
  <summary><strong>Update: preferred loaded-instance flow with <code>update()</code> and <code>save()</code></strong></summary>

  This is the most readable runtime update path when you first load a record and then change it.

  ```ts theme={null}
  const user = await User.findOneBy("email", "alice@example.com");
  if (user) {
    user.update({ name: "Alice Updated" });
    await user.save();
  }
  ```

  Use this when:

  * you already queried the record
  * you want dirty tracking behavior
  * you want a model-centric update flow that reads clearly in services
</details>

<details>
  <summary><strong>Patch: partial update on a persisted instance</strong></summary>

  Use `patch()` when the model instance already exists and you want to update only a subset of fields.

  ```ts theme={null}
  const user = await User.findOneBy("email", "alice@example.com");

  if (user) {
    await user.patch({ name: "Alice Patch" });
  }
  ```

  Important rules:

  * `patch()` is for a persisted instance
  * it is not a static method
  * it is the right companion to `save()` for partial updates
  * it is more consistent with the current runtime than inventing `User.update()`
</details>

<details>
  <summary><strong>Bulk create, update, patch, delete, and restore</strong></summary>

  Use bulk helpers only when the service already has an explicit target list.

  ```ts theme={null}
  const createdMany = await User.createMany([
    { name: "Alice", email: "alice@example.com" },
    { name: "Bob", email: "bob@example.com" },
  ]);

  await User.updateMany([1, 2], { status: "inactive" });

  await User.patchMany([
    { id: 1, email: "alice+1@example.com" },
    { id: 2, email: "bob+1@example.com" },
  ]);

  await User.deleteMany([1, 2]);
  await User.restoreMany([1, 2]);
  ```

  Rules:

  * `createMany(...)` returns hydrated models in input order
  * `updateMany(...)`, `deleteMany(...)`, and `restoreMany(...)` require explicit primary-key lists
  * `patchMany(...)` requires the primary key on every item
</details>

<details>
  <summary><strong>Low-level by-id helpers</strong></summary>

  Use the static by-id helpers when you already know the primary key and want a direct service-layer write.

  ```ts theme={null}
  await User.updateById(1, {
    name: "Alice Updated Directly",
  });

  await User.deleteById(1);
  await User.restoreById(1);
  ```

  Use this when:

  * the service already has the target id
  * you do not need to read the current row first
  * you want a thin CRUD service method

  This is valid in the current runtime, but for readability most application code should prefer:

  * `find(...)` then `update(...)` then `save()`
  * or `find(...)` then `patch(...)`
</details>

<details>
  <summary><strong>Delete and restore</strong></summary>

  Use loaded-instance delete and restore for the recommended public path.

  ```ts theme={null}
  const user = await User.find(1);
  if (user) {
    await user.delete();
  }

  const trashed = await User.find(1);
  if (trashed) {
    await trashed.restore();
  }
  ```
</details>

<details>
  <summary><strong>Recommended service-layer CRUD pattern</strong></summary>

  ```ts theme={null}
  export class UserService {
    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 patch(id: number | string, data: Record<string, unknown>) {
      const user = await User.find(id);
      if (!user) return null;

      await user.patch(data);
      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" });
    }
  }
  ```

  This keeps controllers thin and makes the runtime contract predictable for consumers.
</details>

See also:

* [Controllers](./controllers)
* [Services](./services)
* [Soft Deletes and Restore](../orm/soft-deletes)

## 10) Cache in read paths

Use cache at the service boundary, not in controllers.

```ts theme={null}
import { CacheManager, setupCache } from "@alpha.consultings/eloquent-orm.js";

setupCache();
```

Production cache env keys:

```env theme={null}
APP_ENV=production
MEMCACHED_HOST=127.0.0.1
MEMCACHED_PORT=11211
CACHE_DIR=.cache
```

Current runtime behavior:

* `APP_ENV=development`: memory cache
* `APP_ENV=staging`: file cache using `CACHE_DIR`
* `APP_ENV=production`: Memcached, then file cache, then memory cache

Read-through pattern:

```ts theme={null}
async function activeUsers() {
  const key = "users:active:v1";
  const cached = await CacheManager.get(key);
  if (cached) return cached;

  const users = await User.where("is_active", true)
    .orderBy("created_at", "desc")
    .limit(20)
    .get();

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

Write-through invalidation:

```ts theme={null}
async function createUser(data: Record<string, unknown>) {
  const created = await User.create(data);
  await CacheManager.delete("users:active:v1");
  return created;
}
```

Cache operations:

```bash theme={null}
eloquent cache:stats
eloquent cache:clear
```

## 11) Model implementation by driver

### SQL runtime model

```ts theme={null}
import { SqlModel, column, relation, type ModelInstance } from "@alpha.consultings/eloquent-orm.js";

type UserAttrs = { id?: number; name?: string };

export class User extends SqlModel<UserAttrs> {
  static tableName = "users";
  static connectionName = process.env.DB_CONNECTION ?? "mysql";
  static schema = {
    id: column("increments", undefined, { primary: true }),
    name: column("string", 255),
    posts: relation("hasMany", "Post", { foreignKey: "user_id" }),
  };

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

export interface User extends ModelInstance<UserAttrs> {}
```

### Mongo runtime model

```ts theme={null}
import { MongoModel, column, relation, type ModelInstance } from "@alpha.consultings/eloquent-orm.js";

type GeoAttrs = { id?: number; name?: string };

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

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

export interface GeoLocation extends ModelInstance<GeoAttrs> {}
```

Use SQL classes when you need migrations and relation constraints.
Use Mongo classes when you need document-first storage and `--mongo` flows.
