> ## 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.

# del

> Delete records

`del` is a method that deletes multiple records at once by ID array. It executes safely within a transaction and requires admin permissions by default.

<Info>
  `del` is not defined in BaseModelClass. It's a **standard pattern** automatically generated by the
  Syncer in each Model class when you create an Entity.
</Info>

## Type Signature

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

## Auto-Generated Code

Sonamu automatically generates the following code based on your Entity:

```typescript theme={null}
// src/application/user/user.model.ts (auto-generated)
class UserModelClass extends BaseModelClass {
  @api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"], guards: ["admin"] })
  async del(ids: number[]): Promise<number> {
    const wdb = this.getPuri("w");

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

    return ids.length;
  }
}
```

**How it works:**

1. **getPuri("w")**: Gets write Puri from BaseModelClass method
2. **transaction()**: Starts transaction
3. **table().whereIn().delete()**: Executes delete using Knex methods
4. **Returns ids.length**: Returns count of IDs requested for deletion

## Parameters

### ids

Array of IDs for records to delete.

**Type:** `number[]`

```typescript theme={null}
// Delete single record
await UserModel.del([123]);

// Delete multiple records
await UserModel.del([1, 2, 3, 4, 5]);

// Empty array (deletes nothing)
await UserModel.del([]);
```

## Return Value

**Type:** `Promise<number>`

Returns the length of the passed ID array (`ids.length`). This is not the number of rows actually deleted from the database.

```typescript theme={null}
// Pass 3 IDs
const count = await UserModel.del([1, 2, 3]);
console.log(`${count} ids requested`); // "3 ids requested"

// Including non-existent IDs - return value is still ids.length
const count = await UserModel.del([1, 999, 1000]);
console.log(`${count} ids passed`); // "3 ids passed" (3 even though IDs 999, 1000 don't exist)
```

<Warning>
  The return value is not the number of rows actually deleted from the database. Non-existent IDs
  are silently ignored without errors, and the return value is always `ids.length`.
</Warning>

## Basic Usage

### Delete Single Record

```typescript theme={null}
import { UserModel } from "./user/user.model";

class UserService {
  async deleteUser(userId: number) {
    // del() returns ids.length, so check existence separately.
    await UserModel.findById("A", userId); // throws NotFoundException if not found
    await UserModel.del([userId]);

    return { success: true };
  }
}
```

### Delete Multiple Records

```typescript theme={null}
async deleteUsers(userIds: number[]) {
  const count = await UserModel.del(userIds);

  return {
    requested: userIds.length,
    deleted: count
  };
}
```

### Conditional Delete

```typescript theme={null}
async deleteInactiveUsers() {
  // Query inactive users
  const { rows } = await UserModel.findMany("A", {
    status: "inactive",
    num: 0
  });

  // Extract IDs and delete
  const ids = rows.map(user => user.id);
  const count = await UserModel.del(ids);

  return { deleted: count };
}
```

## Transactions

`del` automatically executes within a transaction.

```typescript theme={null}
async del(ids: number[]): Promise<number> {
  const wdb = this.getPuri("w");

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

  return ids.length;
}
```

### With @transactional

```typescript theme={null}
import { api, transactional } from "sonamu";

class UserFrame {
  @api({ httpMethod: "POST" })
  @transactional()
  async deleteUserAndRelated(userId: number) {
    // Delete related data
    await PostModel.del(
      (
        await PostModel.findMany("A", {
          user_id: userId,
          num: 0,
        })
      ).rows.map((p) => p.id),
    );

    // Delete user
    await UserModel.del([userId]);

    // All succeed or all fail
    return { success: true };
  }
}
```

## Permission Check

By default, `del` requires admin permissions.

```typescript theme={null}
@api({
  httpMethod: "POST",
  clients: ["axios", "tanstack-mutation"],
  guards: ["admin"]  // Default setting
})
async del(ids: number[]): Promise<number> {
  // ...
}
```

### Delete Own Data

```typescript theme={null}
import { Sonamu, api, UnauthorizedException } from "sonamu";

class PostFrame {
  @api({ httpMethod: "POST" })
  async deleteMyPost(postId: number) {
    const { user } = Sonamu.getContext();

    // Check post ownership
    const post = await PostModel.findById("A", postId);

    if (post.user_id !== user.id) {
      throw new UnauthorizedException("You can only delete your own posts");
    }

    await PostModel.del([postId]);

    return { success: true };
  }
}
```

## Practical Examples

<Tabs>
  <Tab title="Account Deletion" icon="user-xmark">
    ```typescript theme={null}
    import { UserModel, PostModel, CommentModel } from "./models";
    import { Sonamu, api, transactional } from "sonamu";

    class UserFrame {
      @api({ httpMethod: "POST" })
      @transactional()
      async deleteAccount() {
        const { user } = Sonamu.getContext();

        // Delete all user comments
        const { rows: comments } = await CommentModel.findMany("A", {
          user_id: user.id,
          num: 0
        });
        if (comments.length > 0) {
          await CommentModel.del(comments.map(c => c.id));
        }

        // Delete all user posts
        const { rows: posts } = await PostModel.findMany("A", {
          user_id: user.id,
          num: 0
        });
        if (posts.length > 0) {
          await PostModel.del(posts.map(p => p.id));
        }

        // Delete user
        await UserModel.del([user.id]);

        // Session termination is handled by better-auth's HTTP endpoint (/api/auth/sign-out).
        return { success: true };
      }
    }
    ```
  </Tab>

  <Tab title="Delete Post" icon="trash">
    ```typescript theme={null}
    import { PostModel, CommentModel, LikeModel } from "./models";
    import { Sonamu, api, transactional, UnauthorizedException } from "sonamu";

    class PostFrame {
      @api({ httpMethod: "POST" })
      @transactional()
      async deletePost(postId: number) {
        const { user } = Sonamu.getContext();

        // Check post ownership
        const post = await PostModel.findById("A", postId);

        if (post.user_id !== user.id) {
          throw new UnauthorizedException("Permission denied");
        }

        // Delete all likes on post
        const { rows: likes } = await LikeModel.findMany("A", {
          post_id: postId,
          num: 0
        });
        if (likes.length > 0) {
          await LikeModel.del(likes.map(l => l.id));
        }

        // Delete all comments on post
        const { rows: comments } = await CommentModel.findMany("A", {
          post_id: postId,
          num: 0
        });
        if (comments.length > 0) {
          await CommentModel.del(comments.map(c => c.id));
        }

        // Delete post
        await PostModel.del([postId]);

        return { success: true };
      }
    }
    ```
  </Tab>

  <Tab title="Bulk Delete" icon="eraser">
    ```typescript theme={null}
    import { UserModel } from "./user/user.model";
    import { api, transactional } from "sonamu";

    class AdminFrame {
      @api({ httpMethod: "POST", guards: ["admin"] })
      @transactional()
      async bulkDeleteUsers(params: {
        status?: string;
        inactive_days?: number;
      }) {
        // Query deletion targets
        let conditions: any = {};

        if (params.status) {
          conditions.status = params.status;
        }

        if (params.inactive_days) {
          const cutoffDate = new Date();
          cutoffDate.setDate(cutoffDate.getDate() - params.inactive_days);
          conditions.last_login_before = cutoffDate.toISOString();
        }

        const { rows } = await UserModel.findMany("A", {
          ...conditions,
          num: 0
        });

        if (rows.length === 0) {
          return { deleted: 0 };
        }

        // Extract IDs
        const ids = rows.map(user => user.id);

        // Delete in batches of 500
        let totalDeleted = 0;
        for (let i = 0; i < ids.length; i += 500) {
          const batch = ids.slice(i, i + 500);
          const count = await UserModel.del(batch);
          totalDeleted += count;
        }

        return {
          requested: ids.length,
          deleted: totalDeleted
        };
      }
    }
    ```
  </Tab>

  <Tab title="Soft Delete" icon="eye-slash">
    ```typescript theme={null}
    import { UserModel } from "./user/user.model";
    import { api } from "sonamu";

    class UserFrame {
      // Soft delete by changing status instead of del
      @api({ httpMethod: "POST" })
      async softDeleteUser(userId: number) {
        // Change status to 'deleted'
        await UserModel.save([
          {
            id: userId,
            status: "deleted",
            deleted_at: new Date()
          }
        ]);

        return { success: true };
      }

      // Permanent delete (admin only)
      @api({ httpMethod: "POST", guards: ["admin"] })
      async hardDeleteUser(userId: number) {
        // Actually delete from DB
        await UserModel.del([userId]);

        return { success: true };
      }

      // Restore
      @api({ httpMethod: "POST", guards: ["admin"] })
      async restoreUser(userId: number) {
        await UserModel.save([
          {
            id: userId,
            status: "active",
            deleted_at: null
          }
        ]);

        return { success: true };
      }
    }
    ```
  </Tab>
</Tabs>

## API Usage

### Auto-Generated del API

```typescript theme={null}
// Model class
class UserModelClass extends BaseModelClass {
  @api({
    httpMethod: "POST",
    clients: ["axios", "tanstack-mutation"],
    guards: ["admin"],
  })
  async del(ids: number[]): Promise<number> {
    // Auto-generated code
  }
}
```

### Client Code

```typescript theme={null}
import { UserService } from "@/services/UserService";

// Delete users
const count = await UserService.del([1, 2, 3]);
console.log(`${count} users deleted`);
```

### React (TanStack Query)

```typescript theme={null}
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { UserService } from "@/services/UserService";

function DeleteUserButton({ userId }: { userId: number }) {
  const queryClient = useQueryClient();

  const deleteUser = useMutation({
    mutationFn: (id: number) => UserService.del([id]),
    onSuccess: () => {
      // Invalidate cache
      queryClient.invalidateQueries({ queryKey: ["users"] });
    }
  });

  const handleDelete = () => {
    if (confirm("Are you sure you want to delete?")) {
      deleteUser.mutate(userId);
    }
  };

  return (
    <button
      onClick={handleDelete}
      disabled={deleteUser.isPending}
    >
      {deleteUser.isPending ? "Deleting..." : "Delete"}
    </button>
  );
}
```

## Foreign Key Constraints

### CASCADE Setting

```sql theme={null}
-- Automatically delete child records when parent is deleted
CREATE TABLE comments (
  id SERIAL PRIMARY KEY,
  post_id INTEGER NOT NULL,
  content TEXT NOT NULL,
  FOREIGN KEY (post_id)
    REFERENCES posts(id)
    ON DELETE CASCADE
);
```

```typescript theme={null}
// Comments automatically deleted when post is deleted
await PostModel.del([1]);
```

### RESTRICT Setting

```sql theme={null}
-- Cannot delete parent record if child records exist
CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL,
  FOREIGN KEY (user_id)
    REFERENCES users(id)
    ON DELETE RESTRICT
);
```

```typescript theme={null}
// Error when deleting user with posts
try {
  await UserModel.del([1]);
} catch (error) {
  // Foreign key violation
  console.error("Cannot delete user with posts");
}
```

### Manual Handling

```typescript theme={null}
@transactional()
async deleteUserWithPosts(userId: number) {
  // Delete posts first
  const { rows: posts } = await PostModel.findMany("A", {
    user_id: userId,
    num: 0
  });

  if (posts.length > 0) {
    await PostModel.del(posts.map(p => p.id));
  }

  // Then delete user
  await UserModel.del([userId]);

  return { success: true };
}
```

## Deletion Verification

### Check Existence

```typescript theme={null}
async deleteUserSafely(userId: number) {
  // Check existence (throws NotFoundException if not found)
  await UserModel.findById("A", userId);

  // Delete
  await UserModel.del([userId]);

  return { success: true };
}
```

### Verify After Delete

```typescript theme={null}
async deleteAndVerify(userId: number) {
  // Delete
  const count = await UserModel.del([userId]);

  // Verify actually deleted
  const deleted = await UserModel.findOne("A", { id: userId });

  return {
    deleteCount: count,
    verified: deleted === null
  };
}
```

## Performance Optimization

### Batch Delete

Split large deletions into batches.

```typescript theme={null}
async deleteManyUsers(ids: number[]) {
  const batchSize = 500;
  let totalDeleted = 0;

  for (let i = 0; i < ids.length; i += batchSize) {
    const batch = ids.slice(i, i + batchSize);
    const count = await UserModel.del(batch);
    totalDeleted += count;
  }

  return { total: totalDeleted };
}
```

### Index Utilization

Add indexes on foreign keys that are frequently deleted.

```sql theme={null}
-- If deleting by user_id frequently
CREATE INDEX idx_posts_user_id ON posts(user_id);
```

## Cautions

### 1. Pass as Array

`del` **must receive an array**.

```typescript theme={null}
// ❌ Wrong
await UserModel.del(123);

// ✅ Correct
await UserModel.del([123]);
```

### 2. Return Value is ids.length

```typescript theme={null}
const count = await UserModel.del([1, 2, 3]);
console.log(count); // 3 (length of passed ID array)
```

### 3. CASCADE Caution

CASCADE settings can delete unintended data.

```typescript theme={null}
// ❌ Dangerous: all comments on posts also deleted
await PostModel.del([1, 2, 3]);
```

### 4. Transactions Recommended

Use transactions when deleting from multiple related tables.

```typescript theme={null}
// ✅ Safe: all succeed or all fail
@transactional()
async deleteUserAndRelated(userId: number) {
  await CommentModel.del([...]);
  await PostModel.del([...]);
  await UserModel.del([userId]);
}
```

### 5. Permission Check

Be careful when changing default `guards: ["admin"]` setting.

```typescript theme={null}
// ⚠️ Warning: anyone can delete
@api({ guards: [] })
async del(ids: number[]) {
  // Dangerous!
}
```

## Soft Delete vs Hard Delete

### Hard Delete (del)

```typescript theme={null}
// Permanently remove from DB
await UserModel.del([1]);
```

**Pros:**

* Saves disk space
* Simple structure

**Cons:**

* Cannot recover
* History lost

### Soft Delete (save)

```typescript theme={null}
// Use status or deleted_at column
await UserModel.save([
  {
    id: 1,
    status: "deleted",
    deleted_at: new Date(),
  },
]);
```

**Pros:**

* Recoverable
* Preserves history
* Audit trail

**Cons:**

* Increases disk usage
* Query complexity increases

## Next Steps

<CardGroup cols={2}>
  <Card title="save" icon="floppy-disk" href="/en/api-reference/base-model/save">
    Save and update records
  </Card>

  <Card title="findMany" icon="list" href="/en/api-reference/base-model/find-many">
    Query records to delete
  </Card>

  <Card title="@transactional" icon="arrows-rotate" href="/en/api-reference/decorators/transactional">
    Safe deletion with transactions
  </Card>

  <Card title="Foreign Keys" icon="link" href="/en/database/migrations/foreign-keys">
    Configure foreign key constraints
  </Card>
</CardGroup>
