BkndCRUD创建Skill bknd-crud-create

这个技能用于在 Bknd 数据库中创建新记录,通过 SDK 或 REST API 实现单条和批量创建,支持关联关系设置、响应和错误处理。适用于应用程序开发中的数据库操作,方便集成和自动化,关键词包括 Bknd、CRUD、数据库创建、API、SDK、后端开发、低代码。

后端开发 0 次安装 0 次浏览 更新于 3/9/2026

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 - 大规模记录创建