name: bknd-crud-create description: 使用 SDK 或 REST API 将新记录插入到 Bknd 数据库中。覆盖 createOne、createMany、使用关系创建($set)、响应处理、错误处理,以及客户端记录创建的常见模式。
CRUD 创建
使用 SDK 或 REST API 向您的 Bknd 数据库中插入新记录。
先决条件
- Bknd 项目正在运行(本地或部署)
- 实体存在(首先使用
bknd-create-entity) - SDK 已配置或 API 端点已知
何时使用 UI 模式
- 快速一次性数据录入
- 开发过程中的手动测试
- 非技术用户添加记录
UI 步骤: 管理员面板 > 数据 > 选择实体 > 点击 “+” 或 “添加” > 填写表单 > 保存
何时使用代码模式
- 用户生成内容的应用程序逻辑
- 表单提交
- API 集成
- 自动化记录创建
代码方法
步骤 1:设置 SDK 客户端
import { Api } from "bknd";
const api = new Api({
host: "http://localhost:7654", // 您的 Bknd 服务器
});
// 如果需要身份验证:
api.updateToken("your-jwt-token");
步骤 2:创建单个记录
使用 createOne(entity, data):
const { ok, data, error } = await api.data.createOne("posts", {
title: "我的第一篇帖子",
content: "你好世界!",
published: false,
});
if (ok) {
console.log("创建的帖子:", data.id);
} else {
console.error("失败:", error.message);
}
步骤 3:处理响应
响应对象:
type CreateResponse = {
ok: boolean; // 成功/失败
data?: { // 创建的记录(如果 ok)
id: number; // 自动生成的 ID
// ...所有字段应用了默认值
};
error?: { // 错误信息(如果 !ok)
message: string;
code: string;
};
};
步骤 4:使用关系创建
使用 $set 链接到现有相关记录:
// 链接到单个相关记录(多对一)
const { data } = await api.data.createOne("posts", {
title: "新帖子",
author: { $set: 1 }, // 链接到 ID 为 1 的用户
});
// 链接到多个相关记录(多对多)
const { data } = await api.data.createOne("posts", {
title: "带标签的帖子",
tags: { $set: [1, 2, 3] }, // 链接到标签 ID 1, 2, 3
});
// 组合两者
const { data } = await api.data.createOne("posts", {
title: "完整帖子",
content: "完整内容在此",
author: { $set: userId },
category: { $set: categoryId },
tags: { $set: [tagId1, tagId2] },
});
步骤 5:创建多个记录(批量)
使用 createMany(entity, data[]):
const { ok, data } = await api.data.createMany("tags", [
{ name: "javascript" },
{ name: "typescript" },
{ name: "bknd" },
]);
// data 是已创建记录的数组
console.log("创建了", data.length, "个标签");
REST API 方法
创建一个
curl -X POST http://localhost:7654/api/data/posts \
-H "Content-Type: application/json" \
-d '{"title": "新帖子", "content": "你好!"}'
带身份验证创建
curl -X POST http://localhost:7654/api/data/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"title": "受保护的帖子"}'
创建多个
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": "帖子", "author": {"$set": 1}}'
React 集成
基本表单提交
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("创建了:", data.id);
setTitle("");
} else {
setError(apiError.message);
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="帖子标题"
required
/>
<button type="submit" disabled={loading}>
{loading ? "创建中..." : "创建帖子"}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
使用 SWR 重新验证
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) {
// 重新验证帖子列表
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" });
// 首先进行身份验证(如果需要)
await api.auth.login({ email: "user@example.com", password: "password" });
// 创建一个用户
const { data: user } = await api.data.createOne("users", {
email: "newuser@example.com",
name: "新用户",
role: "author",
});
// 创建一个链接到用户的帖子
const { data: post } = await api.data.createOne("posts", {
title: "我的第一篇博客帖子",
content: "这是我的帖子内容。",
published: true,
author: { $set: user.id },
});
// 创建标签
const { data: tags } = await api.data.createMany("tags", [
{ name: "介绍" },
{ name: "教程" },
]);
// 将标签链接到帖子(创建后更新)
await api.data.updateOne("posts", post.id, {
tags: { $set: tags.map((t) => t.id) },
});
console.log("创建的帖子:", post.id, "带标签数:", tags.length);
字段默认处理
Bknd 对省略的字段应用默认值:
// 实体定义
entity("posts", {
title: text().required(),
status: text({ default_value: "draft" }),
view_count: number({ default_value: 0 }),
created_at: date({ default_value: "now" }),
});
// 使用最小数据创建
const { data } = await api.data.createOne("posts", {
title: "仅标题", // 仅必需字段
});
// 结果包括默认值
console.log(data);
// {
// id: 1,
// title: "仅标题",
// status: "draft", // 应用了默认值
// view_count: 0, // 应用了默认值
// created_at: "2025-01-20T..." // 应用了默认值
// }
验证处理
Bknd 根据模式验证数据:
// 带约束的实体
entity("users", {
email: text().required().unique(),
name: text(),
});
// 缺少必需字段
const { ok, error } = await api.data.createOne("users", {
name: "无邮箱",
});
// ok: false, error: { message: "email 是必需的" }
// 重复唯一字段
const { ok, error } = await api.data.createOne("users", {
email: "existing@example.com", // 已存在
});
// ok: false, error: { message: "UNIQUE 约束失败" }
常见模式
创建或查找现有
async function createOrFind(
api: Api,
entity: string,
data: object,
uniqueField: string
) {
// 尝试查找现有
const { data: existing } = await api.data.readOneBy(entity, {
where: { [uniqueField]: { $eq: data[uniqueField] } },
});
if (existing) {
return { created: false, data: existing };
}
// 创建新的
const { data: created } = await api.data.createOne(entity, data);
return { created: true, data: created };
}
// 用法
const { created, data } = await createOrFind(
api,
"users",
{ email: "user@example.com", name: "用户" },
"email"
);
使用乐观 UI 创建
function useCreatePost() {
const { api } = useApp();
const [posts, setPosts] = useState<Post[]>([]);
async function createPost(title: string) {
// 乐观:立即添加临时帖子
const tempId = `temp-${Date.now()}`;
const tempPost = { id: tempId, title, status: "creating" };
setPosts((prev) => [...prev, tempPost]);
// 实际创建
const { ok, data } = await api.data.createOne("posts", { title });
if (ok) {
// 用真实数据替换临时数据
setPosts((prev) =>
prev.map((p) => (p.id === tempId ? data : p))
);
} else {
// 失败时移除临时数据
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;
}
// 用法
const items = generateItems(500);
await batchCreate(api, "products", items, (done, total) => {
console.log(`进度:${done}/${total}`);
});
常见陷阱
缺少必需字段
问题: NOT NULL 约束失败
修复: 包含所有必需字段:
// 实体:email 是必需的
entity("users", { email: text().required(), name: text() });
// 错误 - 缺少邮箱
await api.data.createOne("users", { name: "测试" });
// 正确
await api.data.createOne("users", { email: "test@example.com", name: "测试" });
无效关系 ID
问题: FOREIGN KEY 约束失败
修复: 确保相关记录存在:
// 错误 - 作者 ID 不存在
await api.data.createOne("posts", { title: "帖子", author: { $set: 999 } });
// 正确 - 首先验证或处理错误
const { data: author } = await api.data.readOne("users", authorId);
if (author) {
await api.data.createOne("posts", { title: "帖子", author: { $set: authorId } });
}
唯一约束违反
问题: UNIQUE 约束失败
修复: 创建前检查或处理错误:
// 选项 1:先检查
const { data: exists } = await api.data.exists("users", {
email: { $eq: email },
});
if (exists.exists) {
throw new Error("邮箱已注册");
}
await api.data.createOne("users", { email, name });
// 选项 2:处理错误
const { ok, error } = await api.data.createOne("users", { email, name });
if (!ok && error.message.includes("UNIQUE")) {
throw new Error("邮箱已注册");
}
未处理响应
问题: 未检查就假设成功。
修复: 始终检查 ok:
// 错误 - 假设成功
const { data } = await api.data.createOne("posts", { title });
console.log(data.id); // data 可能未定义!
// 正确
const { ok, data, error } = await api.data.createOne("posts", { title });
if (!ok) {
throw new Error(error.message);
}
console.log(data.id);
未进行身份验证创建
问题: 未授权 错误。
修复: 首先进行身份验证或检查权限:
// 先登录
await api.auth.login({ email, password });
// 或设置令牌
api.updateToken(savedToken);
// 然后创建
await api.data.createOne("posts", { title });
验证
创建后,验证记录:
const { ok, data } = await api.data.createOne("posts", { title: "测试" });
if (ok) {
// 重新读取以验证
const { data: fetched } = await api.data.readOne("posts", data.id);
console.log("创建并验证:", fetched);
}
或通过管理员面板:管理员面板 > 数据 > 选择实体 > 查找新记录。
应该和不应该做的事情
应该:
- 在使用
data前检查ok字段 - 包含所有必需字段
- 在使用
$set前验证相关记录存在 - 优雅地处理唯一约束错误
- 创建受保护记录前进行身份验证
不应该:
- 假设 createOne 总是成功
- 在
$set中使用不存在的 ID - 忽略验证错误
- 在需要权限时未进行身份验证创建
- 创建后忘记重新验证缓存
相关技能
- bknd-seed-data - 通过种子函数批量初始数据(服务器端)
- bknd-crud-read - 创建后查询记录
- bknd-crud-update - 修改创建的记录
- bknd-define-relationship - 设置
$set链接的关系 - bknd-bulk-operations - 大规模记录创建