Claude-skill-registry bknd-crud-update
Use when updating existing records in a Bknd entity via the SDK or REST API. Covers updateOne, updateMany, updating relations ($set, $add, $remove, $unset), partial updates, conditional updates, 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-update" ~/.claude/skills/majiayu000-claude-skill-registry-bknd-crud-update && rm -rf "$T"
skills/data/bknd-crud-update/SKILL.mdCRUD Update
Update existing records in your Bknd database using the SDK or REST API.
Prerequisites
- Bknd project running (local or deployed)
- Entity exists with records to update
- SDK configured or API endpoint known
- Record ID or filter criteria known
When to Use UI Mode
- Quick one-off edits
- Manual data corrections
- Visual verification during development
UI steps: Admin Panel > Data > Select Entity > Click record > Edit fields > Save
When to Use Code Mode
- Application logic for user edits
- Form submissions
- Bulk updates
- Automated data maintenance
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: Update Single Record
Use
updateOne(entity, id, data):
const { ok, data, error } = await api.data.updateOne("posts", 1, { title: "Updated Title", status: "published", }); if (ok) { console.log("Updated post:", data.id); } else { console.error("Failed:", error.message); }
Step 3: Handle Response
The response object:
type UpdateResponse = { ok: boolean; // Success/failure data?: { // Updated record (if ok) id: number; // ...all fields with new values }; error?: { // Error info (if !ok) message: string; code: string; }; };
Step 4: Partial Updates
Only changed fields are required - other fields remain unchanged:
// Only update title, keep everything else await api.data.updateOne("posts", 1, { title: "New Title Only", }); // Update multiple fields await api.data.updateOne("users", 5, { name: "New Name", bio: "Updated bio", updated_at: new Date().toISOString(), });
Step 5: Update Relations
Change Linked Record (Many-to-One)
// Change post author to user ID 2 await api.data.updateOne("posts", 1, { author: { $set: 2 }, });
Unlink Record (Set to NULL)
// Remove author link await api.data.updateOne("posts", 1, { author: { $unset: true }, });
Many-to-Many: Add Relations
// Add tags 4 and 5 to existing tags await api.data.updateOne("posts", 1, { tags: { $add: [4, 5] }, });
Many-to-Many: Remove Relations
// Remove tag 2 from post await api.data.updateOne("posts", 1, { tags: { $remove: [2] }, });
Many-to-Many: Replace All Relations
// Replace all tags with new set await api.data.updateOne("posts", 1, { tags: { $set: [1, 3, 5] }, });
Combined Field and Relation Update
await api.data.updateOne("posts", 1, { title: "Updated Post", status: "published", author: { $set: newAuthorId }, tags: { $add: [newTagId] }, });
Step 6: Update Multiple Records (Bulk)
Use
updateMany(entity, where, data):
// Archive all draft posts const { ok, data } = await api.data.updateMany( "posts", { status: { $eq: "draft" } }, // where clause (required) { status: "archived" }, // update values ); // data contains affected records console.log("Archived", data.length, "posts");
Important:
where clause is required to prevent accidental update-all.
// Update posts by author await api.data.updateMany( "posts", { author_id: { $eq: userId } }, { author_id: newUserId }, ); // Update old records await api.data.updateMany( "sessions", { last_active: { $lt: "2024-01-01" } }, { expired: true }, );
REST API Approach
Update One
curl -X PATCH http://localhost:7654/api/data/posts/1 \ -H "Content-Type: application/json" \ -d '{"title": "Updated Title", "status": "published"}'
Update with Auth
curl -X PATCH http://localhost:7654/api/data/posts/1 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -d '{"title": "Protected Update"}'
Update Relations
# Change author curl -X PATCH http://localhost:7654/api/data/posts/1 \ -H "Content-Type: application/json" \ -d '{"author": {"$set": 2}}' # Add tags curl -X PATCH http://localhost:7654/api/data/posts/1 \ -H "Content-Type: application/json" \ -d '{"tags": {"$add": [4, 5]}}'
Update Many
curl -X PATCH "http://localhost:7654/api/data/posts?where=%7B%22status%22%3A%22draft%22%7D" \ -H "Content-Type: application/json" \ -d '{"status": "archived"}'
React Integration
Edit Form
import { useApp } from "bknd/react"; import { useState, useEffect } from "react"; function EditPostForm({ postId }: { postId: number }) { const { api } = useApp(); const [title, setTitle] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); // Load existing data useEffect(() => { api.data.readOne("posts", postId).then(({ data }) => { if (data) setTitle(data.title); }); }, [postId]); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setLoading(true); setError(null); const { ok, error: apiError } = await api.data.updateOne("posts", postId, { title, }); setLoading(false); if (!ok) { setError(apiError.message); } } return ( <form onSubmit={handleSubmit}> <input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Post title" /> <button type="submit" disabled={loading}> {loading ? "Saving..." : "Save"} </button> {error && <p className="error">{error}</p>} </form> ); }
With SWR Revalidation
import useSWR, { mutate } from "swr"; function useUpdatePost() { const { api } = useApp(); async function updatePost(id: number, updates: object) { const { ok, data, error } = await api.data.updateOne("posts", id, updates); if (ok) { // Revalidate the post and list mutate(`posts/${id}`); mutate("posts"); } return { ok, data, error }; } return { updatePost }; }
Optimistic Update
function useOptimisticUpdate() { const { api } = useApp(); const [posts, setPosts] = useState<Post[]>([]); async function updatePost(id: number, updates: Partial<Post>) { // Optimistic: update immediately const originalPosts = [...posts]; setPosts((prev) => prev.map((p) => (p.id === id ? { ...p, ...updates } : p)) ); // Actual update const { ok } = await api.data.updateOne("posts", id, updates); if (!ok) { // Rollback on failure setPosts(originalPosts); } return { ok }; } return { posts, updatePost }; }
Full Example
import { Api } from "bknd"; const api = new Api({ host: "http://localhost:7654" }); // Authenticate await api.auth.login({ email: "user@example.com", password: "password" }); // Simple field update const { ok, data } = await api.data.updateOne("posts", 1, { title: "Updated Title", updated_at: new Date().toISOString(), }); // Update with relation changes await api.data.updateOne("posts", 1, { status: "published", published_at: new Date().toISOString(), category: { $set: 3 }, // Change category tags: { $add: [7, 8] }, // Add new tags }); // Bulk update: mark old drafts as archived await api.data.updateMany( "posts", { status: { $eq: "draft" }, created_at: { $lt: "2024-01-01" }, }, { status: "archived" } ); // Toggle boolean field const post = await api.data.readOne("posts", 1); if (post.ok) { await api.data.updateOne("posts", 1, { featured: !post.data.featured, }); }
Common Patterns
Upsert (Update or Insert)
async function upsert( api: Api, entity: string, where: object, data: object ) { const { data: existing } = await api.data.readOneBy(entity, { where }); if (existing) { return api.data.updateOne(entity, existing.id, data); } return api.data.createOne(entity, data); } // Usage await upsert( api, "settings", { key: { $eq: "theme" } }, { key: "theme", value: "dark" } );
Conditional Update
async function updateIf( api: Api, entity: string, id: number, condition: (record: any) => boolean, updates: object ) { const { data: current } = await api.data.readOne(entity, id); if (!current || !condition(current)) { return { ok: false, error: { message: "Condition not met" } }; } return api.data.updateOne(entity, id, updates); } // Only update if not published await updateIf( api, "posts", 1, (post) => post.status !== "published", { title: "New Title" } );
Increment/Decrement Field
async function increment( api: Api, entity: string, id: number, field: string, amount: number = 1 ) { const { data: current } = await api.data.readOne(entity, id); if (!current) return { ok: false }; return api.data.updateOne(entity, id, { [field]: current[field] + amount, }); } // Increment view count await increment(api, "posts", 1, "view_count"); // Decrement stock await increment(api, "products", 5, "stock", -1);
Soft Delete
async function softDelete(api: Api, entity: string, id: number) { return api.data.updateOne(entity, id, { deleted_at: new Date().toISOString(), }); } async function restore(api: Api, entity: string, id: number) { return api.data.updateOne(entity, id, { deleted_at: null, }); }
Batch Update with Progress
async function batchUpdate( api: Api, entity: string, updates: Array<{ id: number; data: object }>, onProgress?: (done: number, total: number) => void ) { const results = []; for (let i = 0; i < updates.length; i++) { const { id, data } = updates[i]; const result = await api.data.updateOne(entity, id, data); results.push(result); onProgress?.(i + 1, updates.length); } return results; } // Usage await batchUpdate( api, "products", [ { id: 1, data: { price: 19.99 } }, { id: 2, data: { price: 29.99 } }, { id: 3, data: { price: 39.99 } }, ], (done, total) => console.log(`${done}/${total}`) );
Common Pitfalls
Record Not Found
Problem: Update returns no data or error.
Fix: Verify record exists first:
const { data: existing } = await api.data.readOne("posts", id); if (!existing) { throw new Error("Post not found"); } await api.data.updateOne("posts", id, updates);
Invalid Relation ID
Problem:
FOREIGN KEY constraint failed
Fix: Verify related record exists:
// Wrong - author ID doesn't exist await api.data.updateOne("posts", 1, { author: { $set: 999 } }); // Correct - verify first const { data: author } = await api.data.readOne("users", newAuthorId); if (author) { await api.data.updateOne("posts", 1, { author: { $set: newAuthorId } }); }
Unique Constraint Violation
Problem:
UNIQUE constraint failed when updating to existing value.
Fix: Check uniqueness before update:
// Check if email already taken by another user const { data: existing } = await api.data.readOneBy("users", { where: { email: { $eq: newEmail }, id: { $ne: currentUserId }, // Exclude current user }, }); if (existing) { throw new Error("Email already in use"); } await api.data.updateOne("users", currentUserId, { email: newEmail });
Not Checking Response
Problem: Assuming success without verification.
Fix: Always check
ok:
// Wrong const { data } = await api.data.updateOne("posts", 1, updates); console.log(data.title); // data might be undefined! // Correct const { ok, data, error } = await api.data.updateOne("posts", 1, updates); if (!ok) { throw new Error(error.message); } console.log(data.title);
Updating Without Auth
Problem:
Unauthorized error.
Fix: Authenticate first:
await api.auth.login({ email, password }); // or api.updateToken(savedToken); await api.data.updateOne("posts", 1, updates);
Using Wrong Relation Operator
Problem: Replacing when intending to add.
Fix: Use correct operator:
// $set replaces ALL relations await api.data.updateOne("posts", 1, { tags: { $set: [5] } }); // Post now has ONLY tag 5 // $add keeps existing and adds new await api.data.updateOne("posts", 1, { tags: { $add: [5] } }); // Post keeps existing tags AND adds tag 5
Forgetting updateMany Where Clause
Problem: Trying to update all without where.
Fix: Always provide where clause:
// updateMany requires where clause await api.data.updateMany( "posts", { status: { $eq: "draft" } }, // Required { status: "archived" } ); // To update ALL records, use explicit condition await api.data.updateMany( "posts", { id: { $gt: 0 } }, // Match all { reviewed: true } );
Verification
After updating, verify changes:
const { ok } = await api.data.updateOne("posts", 1, { title: "New Title" }); if (ok) { const { data } = await api.data.readOne("posts", 1); console.log("Updated title:", data.title); }
Or via admin panel: Admin Panel > Data > Select Entity > Find record > Verify fields.
DOs and DON'Ts
DO:
- Check
before using response dataok - Verify record exists before updating
- Use
/$add
for incremental relation changes$remove - Handle unique constraint errors
- Authenticate before updating protected records
- Revalidate caches after updates
DON'T:
- Assume updateOne always succeeds
- Use
for relations when meaning$set$add - Update without where clause in updateMany
- Ignore validation errors
- Forget to refresh UI after updates
- Use non-existent IDs in relation operators
Related Skills
- bknd-crud-create - Create records before updating
- bknd-crud-read - Fetch records to get current values
- bknd-crud-delete - Remove records instead of updating
- bknd-define-relationship - Set up relations for linking
- bknd-bulk-operations - Large-scale update patterns