Claude-skill-registry bknd-crud-delete
Use when deleting records from a Bknd entity via the SDK or REST API. Covers deleteOne, deleteMany, soft delete patterns, cascade considerations, response handling, and common patterns.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/bknd-crud-delete" ~/.claude/skills/majiayu000-claude-skill-registry-bknd-crud-delete && rm -rf "$T"
skills/data/bknd-crud-delete/SKILL.mdCRUD Delete
Delete records from your Bknd database using the SDK or REST API.
Prerequisites
- Bknd project running (local or deployed)
- Entity exists with records to delete
- SDK configured or API endpoint known
- Record ID or filter criteria known
- Understanding of any relationships/dependencies
When to Use UI Mode
- Quick one-off deletions
- Manual data cleanup
- Visual verification of what's being deleted
UI steps: Admin Panel > Data > Select Entity > Click record > Delete button > Confirm
When to Use Code Mode
- Application logic for user-initiated deletes
- Automated data cleanup/maintenance
- Bulk deletions
- Soft delete implementations
Code Approach
Step 1: Set Up SDK Client
import { Api } from "bknd"; const api = new Api({ host: "http://localhost:7654", }); // If auth required: api.updateToken("your-jwt-token");
Step 2: Delete Single Record
Use
deleteOne(entity, id):
const { ok, data, error } = await api.data.deleteOne("posts", 1); if (ok) { console.log("Deleted post:", data.id); } else { console.error("Failed:", error.message); }
Step 3: Handle Response
The response object:
type DeleteResponse = { ok: boolean; // Success/failure data?: { // Deleted record (if ok) id: number; // ...all fields of deleted record }; error?: { // Error info (if !ok) message: string; code: string; }; };
Step 4: Delete Multiple Records (Bulk)
Use
deleteMany(entity, where):
// Delete all archived posts const { ok, data } = await api.data.deleteMany("posts", { status: { $eq: "archived" }, }); // data contains deleted records console.log("Deleted", data.length, "posts");
Important:
where clause is required to prevent accidental delete-all.
// Delete old sessions await api.data.deleteMany("sessions", { last_active: { $lt: "2024-01-01" }, }); // Delete by multiple conditions await api.data.deleteMany("logs", { level: { $eq: "debug" }, created_at: { $lt: "2024-06-01" }, });
Step 5: Verify Before Delete
Always verify record exists or check count before deleting:
// Check record exists const { data: existing } = await api.data.readOne("posts", id); if (!existing) { throw new Error("Post not found"); } await api.data.deleteOne("posts", id); // Check count before bulk delete const { data: countResult } = await api.data.count("logs", { level: { $eq: "debug" }, }); console.log(`About to delete ${countResult.count} records`);
REST API Approach
Delete One
curl -X DELETE http://localhost:7654/api/data/posts/1
Delete with Auth
curl -X DELETE http://localhost:7654/api/data/posts/1 \ -H "Authorization: Bearer YOUR_JWT_TOKEN"
Delete Many
# Delete all archived posts curl -X DELETE "http://localhost:7654/api/data/posts?where=%7B%22status%22%3A%22archived%22%7D" # URL-decoded where: {"status":"archived"}
React Integration
Delete Button
import { useApp } from "bknd/react"; import { useState } from "react"; function DeleteButton({ postId, onDeleted }: { postId: number; onDeleted?: () => void }) { const { api } = useApp(); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); async function handleDelete() { if (!confirm("Are you sure you want to delete this post?")) { return; } setLoading(true); setError(null); const { ok, error: apiError } = await api.data.deleteOne("posts", postId); setLoading(false); if (ok) { onDeleted?.(); } else { setError(apiError.message); } } return ( <> <button onClick={handleDelete} disabled={loading}> {loading ? "Deleting..." : "Delete"} </button> {error && <p className="error">{error}</p>} </> ); }
With SWR Revalidation
import { mutate } from "swr"; function useDeletePost() { const { api } = useApp(); async function deletePost(id: number) { const { ok, error } = await api.data.deleteOne("posts", id); if (ok) { // Revalidate list mutate("posts"); } return { ok, error }; } return { deletePost }; }
Optimistic Delete
function useOptimisticDelete() { const { api } = useApp(); const [posts, setPosts] = useState<Post[]>([]); async function deletePost(id: number) { // Optimistic: remove immediately const originalPosts = [...posts]; setPosts((prev) => prev.filter((p) => p.id !== id)); // Actual delete const { ok } = await api.data.deleteOne("posts", id); if (!ok) { // Rollback on failure setPosts(originalPosts); } return { ok }; } return { posts, deletePost }; }
Full Example
import { Api } from "bknd"; const api = new Api({ host: "http://localhost:7654" }); // Authenticate await api.auth.login({ email: "admin@example.com", password: "password" }); // Simple delete const { ok, data } = await api.data.deleteOne("posts", 1); if (ok) { console.log("Deleted:", data.title); } // Delete with verification const postId = 5; const { data: post } = await api.data.readOne("posts", postId); if (post) { await api.data.deleteOne("posts", postId); } // Bulk delete: remove old archived posts const { data: deleted } = await api.data.deleteMany("posts", { status: { $eq: "archived" }, created_at: { $lt: "2023-01-01" }, }); console.log("Deleted", deleted.length, "old archived posts"); // Cleanup expired sessions await api.data.deleteMany("sessions", { expires_at: { $lt: new Date().toISOString() }, });
Common Patterns
Soft Delete (Recommended for User Data)
Instead of permanent deletion, mark as deleted:
// Soft delete: set timestamp async function softDelete(api: Api, entity: string, id: number) { return api.data.updateOne(entity, id, { deleted_at: new Date().toISOString(), }); } // Restore soft-deleted record async function restore(api: Api, entity: string, id: number) { return api.data.updateOne(entity, id, { deleted_at: null, }); } // Query non-deleted records async function findActive(api: Api, entity: string, query = {}) { return api.data.readMany(entity, { ...query, where: { ...query.where, deleted_at: { $isnull: true }, }, }); } // Permanently delete soft-deleted records older than 30 days async function purgeDeleted(api: Api, entity: string) { const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); return api.data.deleteMany(entity, { deleted_at: { $lt: thirtyDaysAgo.toISOString() }, }); }
Delete with Confirmation
async function deleteWithConfirmation( api: Api, entity: string, id: number, confirm: () => Promise<boolean> ) { const { data } = await api.data.readOne(entity, id); if (!data) { return { ok: false, error: { message: "Record not found" } }; } const confirmed = await confirm(); if (!confirmed) { return { ok: false, error: { message: "Cancelled by user" } }; } return api.data.deleteOne(entity, id); }
Cascade Delete (Manual)
When Bknd doesn't auto-cascade, delete children first:
async function cascadeDelete(api: Api, userId: number) { // Delete children first await api.data.deleteMany("posts", { author_id: { $eq: userId } }); await api.data.deleteMany("comments", { user_id: { $eq: userId } }); await api.data.deleteMany("likes", { user_id: { $eq: userId } }); // Then delete parent return api.data.deleteOne("users", userId); }
Batch Delete with Progress
async function batchDelete( api: Api, entity: string, ids: number[], onProgress?: (done: number, total: number) => void ) { const results = []; for (let i = 0; i < ids.length; i++) { const result = await api.data.deleteOne(entity, ids[i]); results.push(result); onProgress?.(i + 1, ids.length); } return results; } // Usage const idsToDelete = [1, 5, 12, 23]; await batchDelete(api, "posts", idsToDelete, (done, total) => { console.log(`Deleted ${done}/${total}`); });
Archive Before Delete
async function archiveAndDelete( api: Api, sourceEntity: string, archiveEntity: string, id: number ) { // Read current record const { data: record } = await api.data.readOne(sourceEntity, id); if (!record) { return { ok: false, error: { message: "Record not found" } }; } // Create archive copy await api.data.createOne(archiveEntity, { ...record, original_id: record.id, archived_at: new Date().toISOString(), }); // Delete original return api.data.deleteOne(sourceEntity, id); }
Conditional Delete
async function deleteIf( api: Api, entity: string, id: number, condition: (record: any) => boolean ) { const { data } = await api.data.readOne(entity, id); if (!data) { return { ok: false, error: { message: "Record not found" } }; } if (!condition(data)) { return { ok: false, error: { message: "Condition not met" } }; } return api.data.deleteOne(entity, id); } // Only delete if draft await deleteIf(api, "posts", 1, (post) => post.status === "draft");
Common Pitfalls
Record Not Found
Problem: Delete returns no data or error.
Fix: Check if record exists first:
const { data: existing } = await api.data.readOne("posts", id); if (!existing) { console.error("Post not found"); return; } await api.data.deleteOne("posts", id);
Foreign Key Constraint
Problem:
FOREIGN KEY constraint failed when deleting parent.
Fix: Delete or unlink children first:
// Option 1: Delete children await api.data.deleteMany("comments", { post_id: { $eq: postId } }); await api.data.deleteOne("posts", postId); // Option 2: Unlink children (if nullable FK) await api.data.updateMany( "comments", { post_id: { $eq: postId } }, { post_id: null } ); await api.data.deleteOne("posts", postId);
Not Checking Response
Problem: Assuming success without verification.
Fix: Always check
ok:
// Wrong await api.data.deleteOne("posts", id); console.log("Deleted!"); // Might have failed! // Correct const { ok, error } = await api.data.deleteOne("posts", id); if (!ok) { console.error("Delete failed:", error.message); return; } console.log("Deleted!");
Accidental Mass Delete
Problem: Deleting more records than intended.
Fix: Always use specific where clause and verify count:
// Dangerous - might delete more than expected await api.data.deleteMany("posts", { status: { $eq: "draft" } }); // Safer - check count first const { data: count } = await api.data.count("posts", { status: { $eq: "draft" } }); console.log(`About to delete ${count.count} posts`); if (count.count > 100) { throw new Error("Too many records - aborting"); }
Missing Auth
Problem:
Unauthorized error.
Fix: Authenticate before deleting:
await api.auth.login({ email, password }); // or api.updateToken(savedToken); await api.data.deleteOne("posts", id);
No Undo for Hard Delete
Problem: Accidentally deleted important data.
Fix: Use soft delete for recoverable data:
// Instead of hard delete await api.data.deleteOne("posts", id); // Use soft delete await api.data.updateOne("posts", id, { deleted_at: new Date().toISOString(), });
Deleting Without Confirmation
Problem: Users accidentally delete data.
Fix: Always confirm destructive actions:
// In frontend function handleDelete(id: number) { if (!confirm("Delete this post? This cannot be undone.")) { return; } api.data.deleteOne("posts", id); }
Verification
After deleting, verify the record is gone:
const { ok } = await api.data.deleteOne("posts", 1); if (ok) { const { data } = await api.data.readOne("posts", 1); console.log("Record exists:", data !== null); // Should be false }
Or via admin panel: Admin Panel > Data > Select Entity > Search for deleted record.
DOs and DON'Ts
DO:
- Check
before assuming successok - Verify record exists before deleting
- Use soft delete for user data
- Handle foreign key constraints
- Confirm with user before destructive actions
- Check count before bulk deletes
- Consider archiving before permanent delete
DON'T:
- Assume deleteOne always succeeds
- Delete parent before children with FK constraints
- Hard delete user data without confirmation
- Forget to revalidate caches after delete
- Use deleteMany without specific where clause
- Delete without authentication on protected entities
Related Skills
- bknd-crud-read - Verify records before deleting
- bknd-crud-update - Update instead of delete (soft delete)
- bknd-crud-create - Recreate accidentally deleted records
- bknd-define-relationship - Understand FK constraints
- bknd-bulk-operations - Large-scale delete patterns