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.
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.
Type Signature
async del ( ids : number []): Promise < number >
Auto-Generated Code
Sonamu automatically generates the following code based on your Entity:
// 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:
getPuri(βwβ) : Gets write Puri from BaseModelClass method
transaction() : Starts transaction
table().whereIn().delete() : Executes delete using Knex methods
Returns ids.length : Returns count of IDs requested for deletion
Parameters
Array of IDs for records to delete.
Type: number[]
// 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 count of deleted records.
// Delete 3 records
const count = await UserModel . del ([ 1 , 2 , 3 ]);
console . log ( ` ${ count } users deleted` ); // "3 users deleted"
// Including non-existent IDs
const count = await UserModel . del ([ 1 , 999 , 1000 ]);
console . log ( ` ${ count } users deleted` ); // "1 users deleted" (only ID 1 exists)
Non-existent IDs are ignored and donβt throw errors.
Basic Usage
Delete Single Record
import { UserModel } from "./user/user.model" ;
class UserService {
async deleteUser ( userId : number ) {
const count = await UserModel . del ([ userId ]);
if ( count === 0 ) {
throw new Error ( "User not found" );
}
return { success: true };
}
}
Delete Multiple Records
async deleteUsers ( userIds : number []) {
const count = await UserModel . del ( userIds );
return {
requested: userIds . length ,
deleted: count
};
}
Conditional Delete
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.
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
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.
@ api ({
httpMethod: "POST" ,
clients: [ "axios" , "tanstack-mutation" ],
guards: [ "admin" ] // Default setting
})
async del ( ids : number []): Promise < number > {
// ...
}
Delete Own Data
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
Account Deletion
Delete Post
Bulk Delete
Soft Delete
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 ]);
// End session
Sonamu . getContext (). passport . logout ();
return { success: true };
}
}
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 };
}
}
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
};
}
}
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 };
}
}
API Usage
Auto-Generated del API
// 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
import { UserService } from "@/services/UserService" ;
// Delete users
const count = await UserService . del ([ 1 , 2 , 3 ]);
console . log ( ` ${ count } users deleted` );
React (TanStack Query)
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
-- 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
);
// Comments automatically deleted when post is deleted
await PostModel . del ([ 1 ]);
RESTRICT Setting
-- 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
);
// 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
@ 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
async deleteUserSafely ( userId : number ) {
// Check user exists
try {
await UserModel . findById ( "A" , userId );
} catch ( error ) {
if ( error instanceof NotFoundException ) {
throw new BadRequestException ( "User not found" );
}
throw error ;
}
// Delete
const count = await UserModel . del ([ userId ]);
return { success: count > 0 };
}
Verify After Delete
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
};
}
Batch Delete
Split large deletions into batches.
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.
-- 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 .
// β Wrong
await UserModel . del ( 123 );
// β
Correct
await UserModel . del ([ 123 ]);
2. Return Value is Count
const count = await UserModel . del ([ 1 , 2 , 3 ]);
console . log ( count ); // 3 (or actual deleted count)
3. CASCADE Caution
CASCADE settings can delete unintended data.
// β Dangerous: all comments on posts also deleted
await PostModel . del ([ 1 , 2 , 3 ]);
4. Transactions Recommended
Use transactions when deleting from multiple related tables.
// β
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.
// β οΈ Warning: anyone can delete
@ api ({ guards: [] })
async del ( ids : number []) {
// Dangerous!
}
Soft Delete vs Hard Delete
Hard Delete (del)
// Permanently remove from DB
await UserModel . del ([ 1 ]);
Pros:
Saves disk space
Simple structure
Cons:
Cannot recover
History lost
Soft Delete (save)
// 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