| name | bknd-crud-update |
| description | 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. |
CRUD 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",
});
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;
data?: {
id: number;
};
error?: {
message: string;
code: string;
};
};
Step 4: Partial Updates
Only changed fields are required - other fields remain unchanged:
await api.data.updateOne("posts", 1, {
title: "New Title Only",
});
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)
await api.data.updateOne("posts", 1, {
author: { $set: 2 },
});
Unlink Record (Set to NULL)
await api.data.updateOne("posts", 1, {
author: { $unset: true },
});
Many-to-Many: Add Relations
await api.data.updateOne("posts", 1, {
tags: { $add: [4, 5] },
});
Many-to-Many: Remove Relations
await api.data.updateOne("posts", 1, {
tags: { $remove: [2] },
});
Many-to-Many: Replace All Relations
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):
const { ok, data } = await api.data.updateMany(
"posts",
{ status: { $eq: "draft" } },
{ status: "archived" },
);
console.log("Archived", data.length, "posts");
Important: where clause is required to prevent accidental update-all.
await api.data.updateMany(
"posts",
{ author_id: { $eq: userId } },
{ author_id: newUserId },
);
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
curl -X PATCH http://localhost:7654/api/data/posts/1 \
-H "Content-Type: application/json" \
-d '{"author": {"$set": 2}}'
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);
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) {
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>) {
const originalPosts = [...posts];
setPosts((prev) =>
prev.map((p) => (p.id === id ? { ...p, ...updates } : p))
);
const { ok } = await api.data.updateOne("posts", id, updates);
if (!ok) {
setPosts(originalPosts);
}
return { ok };
}
return { posts, updatePost };
}
Full Example
import { Api } from "bknd";
const api = new Api({ host: "http://localhost:7654" });
await api.auth.login({ email: "user@example.com", password: "password" });
const { ok, data } = await api.data.updateOne("posts", 1, {
title: "Updated Title",
updated_at: new Date().toISOString(),
});
await api.data.updateOne("posts", 1, {
status: "published",
published_at: new Date().toISOString(),
category: { $set: 3 },
tags: { $add: [7, 8] },
});
await api.data.updateMany(
"posts",
{
status: { $eq: "draft" },
created_at: { $lt: "2024-01-01" },
},
{ status: "archived" }
);
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);
}
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);
}
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,
});
}
await increment(api, "posts", 1, "view_count");
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;
}
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:
await api.data.updateOne("posts", 1, { author: { $set: 999 } });
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:
const { data: existing } = await api.data.readOneBy("users", {
where: {
email: { $eq: newEmail },
id: { $ne: currentUserId },
},
});
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:
const { data } = await api.data.updateOne("posts", 1, updates);
console.log(data.title);
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 });
api.updateToken(savedToken);
await api.data.updateOne("posts", 1, updates);
Using Wrong Relation Operator
Problem: Replacing when intending to add.
Fix: Use correct operator:
await api.data.updateOne("posts", 1, { tags: { $set: [5] } });
await api.data.updateOne("posts", 1, { tags: { $add: [5] } });
Forgetting updateMany Where Clause
Problem: Trying to update all without where.
Fix: Always provide where clause:
await api.data.updateMany(
"posts",
{ status: { $eq: "draft" } },
{ status: "archived" }
);
await api.data.updateMany(
"posts",
{ id: { $gt: 0 } },
{ 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
ok before using response data
- Verify record exists before updating
- Use
$add/$remove for incremental relation changes
- Handle unique constraint errors
- Authenticate before updating protected records
- Revalidate caches after updates
DON'T:
- Assume updateOne always succeeds
- Use
$set for relations when meaning $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