Claude-skill-registry bknd-crud-create
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.
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-create" ~/.claude/skills/majiayu000-claude-skill-registry-bknd-crud-create && rm -rf "$T"
skills/data/bknd-crud-create/SKILL.mdCRUD Create
Insert new records into your Bknd database using the SDK or REST API.
Prerequisites
- Bknd project running (local or deployed)
- Entity exists (use
first)bknd-create-entity - SDK configured or API endpoint known
When to Use UI Mode
- Quick one-off data entry
- Manual testing during development
- Non-technical users adding records
UI steps: Admin Panel > Data > Select Entity > Click "+" or "Add" > Fill form > Save
When to Use Code Mode
- Application logic for user-generated content
- Form submissions
- API integrations
- Automated record creation
Code Approach
Step 1: Set Up SDK Client
import { Api } from "bknd"; const api = new Api({ host: "http://localhost:7654", // Your Bknd server }); // If auth required: api.updateToken("your-jwt-token");
Step 2: Create Single Record
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); }
Step 3: Handle Response
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; }; };
Step 4: Create with Relations
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] }, });
Step 5: Create Multiple Records (Bulk)
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");
REST API Approach
Create One
curl -X POST http://localhost:7654/api/data/posts \ -H "Content-Type: application/json" \ -d '{"title": "New Post", "content": "Hello!"}'
Create with Auth
curl -X POST http://localhost:7654/api/data/posts \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -d '{"title": "Protected Post"}'
Create Many
curl -X POST http://localhost:7654/api/data/tags \ -H "Content-Type: application/json" \ -d '[{"name": "tag1"}, {"name": "tag2"}]'
Create with Relations
curl -X POST http://localhost:7654/api/data/posts \ -H "Content-Type: application/json" \ -d '{"title": "Post", "author": {"$set": 1}}'
React Integration
Basic Form Submit
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> ); }
With SWR Revalidation
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> ); }
Full Example
import { Api } from "bknd"; const api = new Api({ host: "http://localhost:7654" }); // Authenticate first (if required) await api.auth.login({ email: "user@example.com", password: "password" }); // Create a user const { data: user } = await api.data.createOne("users", { email: "newuser@example.com", 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);
Field Default Handling
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 // }
Validation Handling
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: "existing@example.com", // Already exists }); // ok: false, error: { message: "UNIQUE constraint failed" }
Common Patterns
Create or Find Existing
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: "user@example.com", name: "User" }, "email" );
Create with Optimistic UI
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 }; }
Batch Create with Progress
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}`); });
Common Pitfalls
Missing Required Fields
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: "test@example.com", name: "Test" });
Invalid Relation ID
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 } }); }
Unique Constraint Violation
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"); }
Not Handling Response
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);
Creating Without Auth
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 });
Verification
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.
DOs and DON'Ts
DO:
- Check the
field before usingokdata - Include all required fields
- Verify related records exist before using
$set - Handle unique constraint errors gracefully
- Authenticate before creating protected records
DON'T:
- Assume createOne always succeeds
- Use non-existent IDs in
$set - Ignore validation errors
- Create without auth when permissions require it
- Forget to revalidate caches after create
Related Skills
- bknd-seed-data - Bulk initial data via seed function (server-side)
- bknd-crud-read - Query records after creation
- bknd-crud-update - Modify created records
- bknd-define-relationship - Set up relations for
linking$set - bknd-bulk-operations - Large-scale record creation