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

# Creating Models

> Creating Sonamu Model files and using Scaffold

Models can be auto-generated through Scaffold after Entity definition or written manually. Using Scaffold generates a Model template with basic structure and CRUD methods included.

## Model Creation Process

<Steps>
  <Step title="Define Entity">
    First, you need to define an Entity.

    ```json title="user.entity.json" theme={null}
    {
      "id": "User",
      "table": "users",
      "props": [
        { "name": "id", "type": "integer" },
        { "name": "email", "type": "string", "length": 255 },
        { "name": "username", "type": "string", "length": 255 }
      ],
      "subsets": {
        "A": ["id", "email", "username"],
        "SS": ["id", "email"]
      }
    }
    ```
  </Step>

  <Step title="Auto Type Generation">
    Type files are automatically generated when you save the Entity.

    Generated files:

    * `user.types.ts` - TypeScript types and Zod schemas
    * `sonamu.generated.ts` - Base schemas and Enums
    * `sonamu.generated.sso.ts` - Subset query functions
  </Step>

  <Step title="Generate Model Scaffold">
    Use Sonamu CLI to generate a Model template.

    ```bash theme={null}
    pnpm sonamu generate model --entity User
    ```

    Or generate from the **Scaffolding** tab in Sonamu UI.
  </Step>

  <Step title="Customize Model">
    Add business logic to the generated Model.

    ```typescript theme={null}
    // Add methods to generated template
    @api({ httpMethod: "POST" })
    async login(params: LoginParams) {
      // Implement login logic
    }
    ```
  </Step>
</Steps>

## Creating Models with Scaffold

### Using CLI

<CodeGroup>
  ```bash title="Basic usage" theme={null}
  # Specify Entity name
  pnpm sonamu generate model --entity User

  # Generate multiple Entities at once
  pnpm sonamu generate model --entity User,Post,Comment
  ```

  ```bash title="Options" theme={null}
  # Overwrite existing file
  pnpm sonamu generate model --entity User --overwrite

  # Generate in specific directory
  pnpm sonamu generate model --entity User --output ./custom/path
  ```
</CodeGroup>

### Using Sonamu UI

<Steps>
  <Step title="Open Scaffolding Tab">
    Click the **Scaffolding** tab in Sonamu UI (`http://localhost:34900/sonamu-ui`).
  </Step>

  <Step title="Select Model Template">
    Select the **"Model"** template and specify the Entity.
  </Step>

  <Step title="Configure Options">
    * **Default Search Field**: Default search field (e.g., `email`)
    * **Default Order By**: Default sorting (e.g., `id-desc`)
    * **Overwrite**: Whether to overwrite existing files
  </Step>

  <Step title="Execute Generation">
    Click the **"Generate"** button to create the Model file.
  </Step>
</Steps>

<Frame caption="📸 Needed: Scaffolding tab in Sonamu UI - Model generation screen" />

## Generated Model Structure

Basic structure of a Model generated by Scaffold:

```typescript title="user.model.ts" theme={null}
import {
  api,
  asArray,
  BadRequestException,
  BaseModelClass,
  exhaustive,
  type ListResult,
  NotFoundException,
} from "sonamu";
import type { UserSubsetKey, UserSubsetMapping } from "../sonamu.generated";
import { userLoaderQueries, userSubsetQueries } from "../sonamu.generated.sso";
import type { UserListParams, UserSaveParams } from "./user.types";

/*
  User Model
*/

class UserModelClass extends BaseModelClass<
  UserSubsetKey,
  UserSubsetMapping,
  typeof userSubsetQueries,
  typeof userLoaderQueries
> {
  constructor() {
    super("User", userSubsetQueries, userLoaderQueries);
  }

  @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "User" })
  async findById<T extends UserSubsetKey>(
    subset: T,
    id: number
  ): Promise<UserSubsetMapping[T]> {
    const { rows } = await this.findMany(subset, {
      id,
      num: 1,
      page: 1,
    });
    if (!rows[0]) {
      throw new NotFoundException(`User ID ${id} does not exist`);
    }
    return rows[0];
  }

  async findOne<T extends UserSubsetKey>(
    subset: T,
    listParams: UserListParams
  ): Promise<UserSubsetMapping[T] | null> {
    const { rows } = await this.findMany(subset, {
      ...listParams,
      num: 1,
      page: 1,
    });

    return rows[0] ?? null;
  }

  @api({
    httpMethod: "GET",
    clients: ["axios", "tanstack-query"],
    resourceName: "Users",
  })
  async findMany<T extends UserSubsetKey, LP extends UserListParams>(
    subset: T,
    rawParams?: LP
  ): Promise<ListResult<LP, UserSubsetMapping[T]>> {
    // params with defaults
    const params = {
      num: 24,
      page: 1,
      search: "id" as const,
      orderBy: "id-desc" as const,
      ...rawParams,
    };

    const { qb, onSubset } = this.getSubsetQueries(subset);

    // id
    if (params.id) {
      qb.whereIn("users.id", asArray(params.id));
    }

    // search-keyword
    if (params.search && params.keyword && params.keyword.length > 0) {
      if (params.search === "id") {
        qb.where("users.id", Number(params.keyword));
      } else {
        exhaustive(params.search);
      }
    }

    // orderBy
    if (params.orderBy) {
      if (params.orderBy === "id-desc") {
        qb.orderBy("users.id", "desc");
      } else {
        exhaustive(params.orderBy);
      }
    }

    const enhancers = this.createEnhancers({
      A: (row) => row,
      SS: (row) => row,
    });

    return this.executeSubsetQuery({
      subset,
      qb,
      params,
      enhancers,
    });
  }

  @api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"] })
  async save(spa: UserSaveParams[]): Promise<number[]> {
    const wdb = this.getPuri("w");

    // register
    spa.forEach((sp) => {
      wdb.ubRegister("users", sp);
    });

    // transaction
    return wdb.transaction(async (trx) => {
      const ids = await trx.ubUpsert("users");
      return ids;
    });
  }

  @api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"], guards: ["admin"] })
  async del(ids: number[]): Promise<number> {
    const wdb = this.getPuri("w");

    await wdb.transaction(async (trx) => {
      return trx.table("users").whereIn("users.id", ids).delete();
    });

    return ids.length;
  }
}

export const UserModel = new UserModelClass();
```

## Generated Methods Explanation

Default methods generated by Scaffold:

### 1. findById

Retrieves a single record by ID.

```typescript theme={null}
async findById<T extends UserSubsetKey>(
  subset: T,
  id: number
): Promise<UserSubsetMapping[T]>
```

**Features**:

* Receives Subset as parameter to query only needed fields
* Throws `NotFoundException` if record doesn't exist
* Creates HTTP endpoint with `@api` decorator

### 2. findOne

Retrieves the first record matching conditions.

```typescript theme={null}
async findOne<T extends UserSubsetKey>(
  subset: T,
  listParams: UserListParams
): Promise<UserSubsetMapping[T] | null>
```

**Features**:

* Returns `null` if record doesn't exist (no exception thrown)
* Internally calls `findMany` with `num: 1, page: 1`

### 3. findMany

Retrieves multiple records based on conditions.

```typescript theme={null}
async findMany<T extends UserSubsetKey, LP extends UserListParams>(
  subset: T,
  rawParams?: LP
): Promise<ListResult<LP, UserSubsetMapping[T]>>
```

**Features**:

* Supports pagination (`num`, `page`)
* Search/filtering (`search`, `keyword`)
* Sorting (`orderBy`)
* Returns `{ rows, total }` with `ListResult` type

### 4. save

Creates or updates records.

```typescript theme={null}
async save(spa: UserSaveParams[]): Promise<number[]>
```

**Features**:

* Uses Upsert Builder (Insert or Update)
* Processes multiple records at once with array
* Executes wrapped in transaction
* Returns array of created/updated IDs

### 5. del

Deletes records.

```typescript theme={null}
async del(ids: number[]): Promise<number>
```

**Features**:

* Receives multiple IDs as array
* Executes in transaction
* Only admins can delete with `guards: ["admin"]`
* Returns number of deleted records

## Creating Models Manually

You can also write manually without using Scaffold.

### Minimum Structure

<CodeGroup>
  ```typescript title="user.model.ts" theme={null}
  import { BaseModelClass } from "sonamu";
  import type { UserSubsetKey, UserSubsetMapping } from "../sonamu.generated";
  import { userLoaderQueries, userSubsetQueries } from "../sonamu.generated.sso";

  class UserModelClass extends BaseModelClass<
    UserSubsetKey,
    UserSubsetMapping,
    typeof userSubsetQueries,
    typeof userLoaderQueries
  > {
    constructor() {
      super("User", userSubsetQueries, userLoaderQueries);
    }

    // Add methods...
  }

  export const UserModel = new UserModelClass();
  ```

  ```typescript title="Required imports" theme={null}
  // BaseModelClass: Base class for Models
  import { BaseModelClass } from "sonamu";

  // Auto-generated types
  import type { 
    UserSubsetKey,      // "A" | "P" | "SS"
    UserSubsetMapping   // Type for each subset
  } from "../sonamu.generated";

  // Auto-generated query functions
  import { 
    userLoaderQueries,  // Loader queries
    userSubsetQueries   // Subset queries
  } from "../sonamu.generated.sso";
  ```
</CodeGroup>

### Naming Conventions

| Category    | Pattern              | Example          |
| ----------- | -------------------- | ---------------- |
| Class name  | `{Entity}ModelClass` | `UserModelClass` |
| Export name | `{Entity}Model`      | `UserModel`      |
| File name   | `{entity}.model.ts`  | `user.model.ts`  |

<Warning>
  Class name must end with `ModelClass`. Export must end with `Model`.

  ```typescript theme={null}
  // ✅ Correct naming
  class UserModelClass extends BaseModelClass { }
  export const UserModel = new UserModelClass();

  // ❌ Wrong naming
  class UserModel extends BaseModelClass { }  // X
  export const User = new UserModelClass();   // X
  ```
</Warning>

## Model File Location

Model files are located in the same directory as the Entity:

<FileTree>
  <FileTree.Folder name="api" defaultOpen>
    <FileTree.Folder name="src" defaultOpen>
      <FileTree.Folder name="application" defaultOpen>
        <FileTree.Folder name="user" defaultOpen>
          <FileTree.File name="user.entity.json" />

          <FileTree.File name="user.model.ts" highlight />

          <FileTree.File name="user.types.ts" />
        </FileTree.Folder>

        <FileTree.File name="sonamu.generated.ts" />

        <FileTree.File name="sonamu.generated.sso.ts" />
      </FileTree.Folder>
    </FileTree.Folder>
  </FileTree.Folder>
</FileTree>

## Writing Types File

Define parameter types to use with Model:

```typescript title="user.types.ts" theme={null}
import { z } from "zod";
import { UserOrderBy, UserSearchField } from "../sonamu.generated";

// List parameters
export const UserListParams = z.object({
  num: z.number().optional(),
  page: z.number().optional(),
  search: UserSearchField.optional(),
  keyword: z.string().optional(),
  orderBy: UserOrderBy.optional(),
  id: z.union([z.number(), z.array(z.number())]).optional(),
});
export type UserListParams = z.infer<typeof UserListParams>;

// Save parameters
export const UserSaveParams = z.object({
  id: z.number().optional(),
  email: z.string().email(),
  username: z.string().min(2).max(50),
  password: z.string().min(8),
});
export type UserSaveParams = z.infer<typeof UserSaveParams>;

// Login parameters
export const UserLoginParams = z.object({
  email: z.string().email(),
  password: z.string(),
});
export type UserLoginParams = z.infer<typeof UserLoginParams>;
```

**Types file patterns**:

* `{Entity}ListParams`: For list queries
* `{Entity}SaveParams`: For create/update
* `{Entity}{Action}Params`: For specific actions (e.g., `LoginParams`)

## Verifying Model Registration

Verify that the generated Model loads properly:

```typescript title="api/src/application/sonamu.generated.ts" theme={null}
// Model imports (auto-generated)
export * from "./user/user.model";

// All Models are exported here
```

<Tip>
  `sonamu.generated.ts` is automatically updated when you save Entities. When you add a Model, it's automatically added to this file too.
</Tip>

## Next Steps

After creating a Model, learn the following topics:

<CardGroup cols={2}>
  <Card title="API Decorator" icon="at" href="/en/core-concepts/model/api-decorator">
    Creating HTTP APIs with @api decorator
  </Card>

  <Card title="Business Logic" icon="lightbulb" href="/en/core-concepts/model/business-logic">
    Learning business logic writing patterns
  </Card>

  <Card title="Puri Query Builder" icon="database" href="/en/database/puri/basic-queries">
    Writing type-safe queries
  </Card>

  <Card title="Testing" icon="flask" href="/en/testing/model-testing/unit-tests">
    Writing Model tests
  </Card>
</CardGroup>
