.agents/skills/bknd-crud-create/SKILL.md
Use when inserting new records into a Bknd entity via the SDK or REST API. Covers createOne, createMany, creating with relations ($set), response handling, error handling, and common patterns for client-side record creation.
npx skillsauth add cameronapak/freedom-stack-v3 bknd-crud-createInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Insert new records into your Bknd database using the SDK or REST API.
bknd-create-entity first)UI steps: Admin Panel > Data > Select Entity > Click "+" or "Add" > Fill form > Save
import { Api } from "bknd";
const api = new Api({
host: "http://localhost:7654", // Your Bknd server
});
// If auth required:
api.updateToken("your-jwt-token");
Use createOne(entity, data):
const { ok, data, error } = await api.data.createOne("posts", {
title: "My First Post",
content: "Hello world!",
published: false,
});
if (ok) {
console.log("Created post:", data.id);
} else {
console.error("Failed:", error.message);
}
The response object:
type CreateResponse = {
ok: boolean; // Success/failure
data?: { // Created record (if ok)
id: number; // Auto-generated ID
// ...all fields with defaults applied
};
error?: { // Error info (if !ok)
message: string;
code: string;
};
};
Link to existing related records using $set:
// Link to single related record (many-to-one)
const { data } = await api.data.createOne("posts", {
title: "New Post",
author: { $set: 1 }, // Link to user with ID 1
});
// Link to multiple related records (many-to-many)
const { data } = await api.data.createOne("posts", {
title: "Tagged Post",
tags: { $set: [1, 2, 3] }, // Link to tag IDs 1, 2, 3
});
// Combine both
const { data } = await api.data.createOne("posts", {
title: "Complete Post",
content: "Full content here",
author: { $set: userId },
category: { $set: categoryId },
tags: { $set: [tagId1, tagId2] },
});
Use createMany(entity, data[]):
const { ok, data } = await api.data.createMany("tags", [
{ name: "javascript" },
{ name: "typescript" },
{ name: "bknd" },
]);
// data is array of created records
console.log("Created", data.length, "tags");
curl -X POST http://localhost:7654/api/data/posts \
-H "Content-Type: application/json" \
-d '{"title": "New Post", "content": "Hello!"}'
curl -X POST http://localhost:7654/api/data/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"title": "Protected Post"}'
curl -X POST http://localhost:7654/api/data/tags \
-H "Content-Type: application/json" \
-d '[{"name": "tag1"}, {"name": "tag2"}]'
curl -X POST http://localhost:7654/api/data/posts \
-H "Content-Type: application/json" \
-d '{"title": "Post", "author": {"$set": 1}}'
import { useApp } from "bknd/react";
function CreatePostForm() {
const { api } = useApp();
const [title, setTitle] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
const { ok, data, error: apiError } = await api.data.createOne("posts", {
title,
published: false,
});
setLoading(false);
if (ok) {
console.log("Created:", data.id);
setTitle("");
} else {
setError(apiError.message);
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Post title"
required
/>
<button type="submit" disabled={loading}>
{loading ? "Creating..." : "Create Post"}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
import { useApp } from "bknd/react";
import useSWR, { mutate } from "swr";
function PostsList() {
const { api } = useApp();
const { data: posts } = useSWR("posts", () =>
api.data.readMany("posts").then((r) => r.data)
);
async function createPost(title: string) {
const { ok, data } = await api.data.createOne("posts", { title });
if (ok) {
// Revalidate the posts list
mutate("posts");
}
return { ok, data };
}
return (
<div>
<CreateForm onCreate={createPost} />
<ul>
{posts?.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
import { Api } from "bknd";
const api = new Api({ host: "http://localhost:7654" });
// Authenticate first (if required)
await api.auth.login({ email: "[email protected]", password: "password" });
// Create a user
const { data: user } = await api.data.createOne("users", {
email: "[email protected]",
name: "New User",
role: "author",
});
// Create a post linked to user
const { data: post } = await api.data.createOne("posts", {
title: "My First Blog Post",
content: "This is the content of my post.",
published: true,
author: { $set: user.id },
});
// Create tags
const { data: tags } = await api.data.createMany("tags", [
{ name: "intro" },
{ name: "tutorial" },
]);
// Link tags to post (update after creation)
await api.data.updateOne("posts", post.id, {
tags: { $set: tags.map((t) => t.id) },
});
console.log("Created post:", post.id, "with tags:", tags.length);
Bknd applies defaults for omitted fields:
// Entity definition
entity("posts", {
title: text().required(),
status: text({ default_value: "draft" }),
view_count: number({ default_value: 0 }),
created_at: date({ default_value: "now" }),
});
// Create with minimal data
const { data } = await api.data.createOne("posts", {
title: "Just a title", // Only required field
});
// Result includes defaults
console.log(data);
// {
// id: 1,
// title: "Just a title",
// status: "draft", // default applied
// view_count: 0, // default applied
// created_at: "2025-01-20T..." // default applied
// }
Bknd validates data against schema:
// Entity with constraints
entity("users", {
email: text().required().unique(),
name: text(),
});
// Missing required field
const { ok, error } = await api.data.createOne("users", {
name: "No Email",
});
// ok: false, error: { message: "email is required" }
// Duplicate unique field
const { ok, error } = await api.data.createOne("users", {
email: "[email protected]", // Already exists
});
// ok: false, error: { message: "UNIQUE constraint failed" }
async function createOrFind(
api: Api,
entity: string,
data: object,
uniqueField: string
) {
// Try to find existing
const { data: existing } = await api.data.readOneBy(entity, {
where: { [uniqueField]: { $eq: data[uniqueField] } },
});
if (existing) {
return { created: false, data: existing };
}
// Create new
const { data: created } = await api.data.createOne(entity, data);
return { created: true, data: created };
}
// Usage
const { created, data } = await createOrFind(
api,
"users",
{ email: "[email protected]", name: "User" },
"email"
);
function useCreatePost() {
const { api } = useApp();
const [posts, setPosts] = useState<Post[]>([]);
async function createPost(title: string) {
// Optimistic: add temp post immediately
const tempId = `temp-${Date.now()}`;
const tempPost = { id: tempId, title, status: "creating" };
setPosts((prev) => [...prev, tempPost]);
// Actual create
const { ok, data } = await api.data.createOne("posts", { title });
if (ok) {
// Replace temp with real
setPosts((prev) =>
prev.map((p) => (p.id === tempId ? data : p))
);
} else {
// Remove temp on failure
setPosts((prev) => prev.filter((p) => p.id !== tempId));
}
return { ok, data };
}
return { posts, createPost };
}
async function batchCreate(
api: Api,
entity: string,
items: object[],
onProgress?: (done: number, total: number) => void
) {
const results = [];
const batchSize = 100;
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const { data } = await api.data.createMany(entity, batch);
results.push(...data);
onProgress?.(Math.min(i + batchSize, items.length), items.length);
}
return results;
}
// Usage
const items = generateItems(500);
await batchCreate(api, "products", items, (done, total) => {
console.log(`Progress: ${done}/${total}`);
});
Problem: NOT NULL constraint failed
Fix: Include all required fields:
// Entity: email is required
entity("users", { email: text().required(), name: text() });
// Wrong - missing email
await api.data.createOne("users", { name: "Test" });
// Correct
await api.data.createOne("users", { email: "[email protected]", name: "Test" });
Problem: FOREIGN KEY constraint failed
Fix: Ensure related record exists:
// Wrong - author ID doesn't exist
await api.data.createOne("posts", { title: "Post", author: { $set: 999 } });
// Correct - verify first or handle error
const { data: author } = await api.data.readOne("users", authorId);
if (author) {
await api.data.createOne("posts", { title: "Post", author: { $set: authorId } });
}
Problem: UNIQUE constraint failed
Fix: Check before create or handle error:
// Option 1: Check first
const { data: exists } = await api.data.exists("users", {
email: { $eq: email },
});
if (exists.exists) {
throw new Error("Email already registered");
}
await api.data.createOne("users", { email, name });
// Option 2: Handle error
const { ok, error } = await api.data.createOne("users", { email, name });
if (!ok && error.message.includes("UNIQUE")) {
throw new Error("Email already registered");
}
Problem: Assuming success without checking.
Fix: Always check ok:
// Wrong - assumes success
const { data } = await api.data.createOne("posts", { title });
console.log(data.id); // data might be undefined!
// Correct
const { ok, data, error } = await api.data.createOne("posts", { title });
if (!ok) {
throw new Error(error.message);
}
console.log(data.id);
Problem: Unauthorized error.
Fix: Authenticate first or check permissions:
// Login first
await api.auth.login({ email, password });
// Or set token
api.updateToken(savedToken);
// Then create
await api.data.createOne("posts", { title });
After creating, verify the record:
const { ok, data } = await api.data.createOne("posts", { title: "Test" });
if (ok) {
// Read back to verify
const { data: fetched } = await api.data.readOne("posts", data.id);
console.log("Created and verified:", fetched);
}
Or via admin panel: Admin Panel > Data > Select Entity > Find new record.
DO:
ok field before using data$setDON'T:
$set$set linkingdevelopment
Use btca (Better Context App) to efficiently query and learn from the bknd backend framework. Use when working with bknd for (1) Understanding data module and schema definitions, (2) Implementing authentication and authorization, (3) Setting up media file handling, (4) Configuring adapters (Node, Cloudflare, etc.), (5) Learning from bknd source code and examples, (6) Debugging bknd-specific issues
development
Use when configuring webhook integrations in Bknd. Covers receiving incoming webhooks via HTTP triggers, sending outgoing webhooks with FetchTask, event-triggered webhooks on data changes, signature verification, retry patterns, and async processing.
development
Use when encountering Bknd errors, getting error messages, something not working, or needing quick fixes. Covers error code reference, quick solutions, and common mistake patterns.
tools
Use when writing tests for Bknd applications, setting up test infrastructure, creating unit/integration tests, or testing API endpoints. Covers in-memory database setup, test helpers, mocking, and test patterns.