Bknd前端SDK配置Skill bknd-client-setup

这个技能用于在前端应用程序中设置和配置Bknd TypeScript SDK,包括Api类初始化、身份验证令牌管理、与React等框架集成,以及TypeScript类型注册。适用于开发人员连接Bknd后端服务,提升前端开发效率。关键词:Bknd SDK, 前端开发, 身份验证, React集成, TypeScript, 客户端配置。

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

名称: bknd-client-setup 描述: 用于在前端应用程序中设置Bknd SDK。涵盖Api类初始化、令牌存储、身份验证状态处理、React集成与BkndBrowserApp和useApp钩子、框架特定设置(Vite、Next.js、独立)以及TypeScript类型注册。

客户端设置

在您的前端应用程序中设置Bknd TypeScript SDK。

先决条件

  • Bknd后端正在运行(本地或部署)
  • 前端项目已初始化(React、Vue、原生JS等)
  • 已安装bknd包:npm install bknd

何时使用UI模式

不适用 - 客户端设置仅为代码模式。

何时使用代码模式

  • 始终 - SDK设置需要代码配置
  • 根据架构选择方法:
    • 独立API客户端 - 连接到单独的Bknd后端
    • 嵌入式(BkndBrowserApp) - Bknd在浏览器中运行(Vite/React)
    • 框架适配器 - Next.js、Astro等

方法1:独立API客户端

当连接到单独的Bknd后端服务器时使用。

步骤1:基本设置

import { Api } from "bknd";

const api = new Api({
  host: "https://api.example.com",  // 您的Bknd后端URL
});

// 发送请求
const { ok, data } = await api.data.readMany("posts");

步骤2:添加令牌持久化

跨页面刷新存储身份验证令牌:

import { Api } from "bknd";

const api = new Api({
  host: "https://api.example.com",
  storage: localStorage,  // 将令牌持久化为"auth"键
});

自定义存储键:

const api = new Api({
  host: "https://api.example.com",
  storage: localStorage,
  key: "myapp_auth",  // 自定义键,而不是"auth"
});

步骤3:处理身份验证状态变化

响应登录/注销事件:

const api = new Api({
  host: "https://api.example.com",
  storage: localStorage,
  onAuthStateChange: (state) => {
    console.log("身份验证状态:", state);
    // state.user - 当前用户或未定义
    // state.token - JWT或未定义
    // state.verified - 令牌是否已通过服务器验证
  },
});

步骤4:基于Cookie的身份验证(SSR)

用于服务器端渲染与Cookie:

const api = new Api({
  host: "https://api.example.com",
  credentials: "include",  // 跨域发送Cookie
});

步骤5:完整配置

import { Api } from "bknd";

const api = new Api({
  // 必需
  host: "https://api.example.com",

  // 身份验证持久化
  storage: localStorage,
  key: "auth",

  // 身份验证事件
  onAuthStateChange: (state) => {
    if (state.user) {
      console.log("已登录:", state.user.email);
    } else {
      console.log("已注销");
    }
  },

  // 请求选项
  credentials: "include",  // 用于Cookie
  verbose: true,           // 记录请求(仅开发)

  // 数据API默认值
  data: {
    defaultQuery: { limit: 20 },
  },
});

export { api };

方法2:React与BkndBrowserApp(嵌入式)

当Bknd完全在浏览器中运行时使用(Vite + React)。

步骤1:定义模式

// bknd.config.ts
import { boolean, em, entity, text } from "bknd";

export const schema = em({
  todos: entity("todos", {
    title: text(),
    done: boolean(),
  }),
});

// 类型注册以实现自动完成
type Database = (typeof schema)["DB"];
declare module "bknd" {
  interface DB extends Database {}
}

步骤2:配置BkndBrowserApp

// App.tsx
import { BkndBrowserApp, type BrowserBkndConfig } from "bknd/adapter/browser";
import { schema } from "./bknd.config";

const config = {
  config: {
    data: schema.toJSON(),
    auth: {
      enabled: true,
      jwt: {
        secret: "your-secret-key",  // 在生产中使用环境变量
      },
    },
  },
  options: {
    seed: async (ctx) => {
      // 初始数据(在空数据库上运行一次)
      await ctx.em.mutator("todos").insertMany([
        { title: "学习bknd", done: false },
      ]);
    },
  },
} satisfies BrowserBkndConfig;

export default function App() {
  return (
    <BkndBrowserApp {...config}>
      <YourRoutes />
    </BkndBrowserApp>
  );
}

步骤3:使用useApp钩子

import { useApp } from "bknd/adapter/browser";

function TodoList() {
  const { api, app, user, isLoading } = useApp();

  if (isLoading) return <div>加载中...</div>;

  // api - 用于数据/身份验证/媒体的Api实例
  // app - 完整的App实例
  // user - 当前用户或null
  // isLoading - 初始化时为True

  const [todos, setTodos] = useState([]);

  useEffect(() => {
    api.data.readMany("todos").then(({ data }) => setTodos(data));
  }, [api]);

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

方法3:React独立(外部后端)

当React连接到单独的Bknd服务器时使用。

步骤1:创建API实例

// lib/api.ts
import { Api } from "bknd";

export const api = new Api({
  host: import.meta.env.VITE_BKND_URL || "http://localhost:7654",
  storage: localStorage,
});

步骤2:创建React上下文

// context/BkndContext.tsx
import { createContext, useContext, useEffect, useState, ReactNode } from "react";
import { Api, type TApiUser } from "bknd";
import { api } from "../lib/api";

type BkndContextType = {
  api: Api;
  user: TApiUser | null;
  isLoading: boolean;
};

const BkndContext = createContext<BkndContextType | null>(null);

export function BkndProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<TApiUser | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // 监听身份验证变化
    api.options.onAuthStateChange = (state) => {
      setUser(state.user ?? null);
    };

    // 在挂载时验证现有令牌
    api.verifyAuth().finally(() => {
      setUser(api.getUser());
      setIsLoading(false);
    });

    return () => {
      api.options.onAuthStateChange = undefined;
    };
  }, []);

  return (
    <BkndContext.Provider value={{ api, user, isLoading }}>
      {children}
    </BkndContext.Provider>
  );
}

export function useBknd() {
  const ctx = useContext(BkndContext);
  if (!ctx) throw new Error("useBknd必须在BkndProvider内使用");
  return ctx;
}

步骤3:包装应用

// main.tsx
import { BkndProvider } from "./context/BkndContext";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <BkndProvider>
    <App />
  </BkndProvider>
);

步骤4:在组件中使用

function Profile() {
  const { api, user, isLoading } = useBknd();

  if (isLoading) return <div>加载中...</div>;
  if (!user) return <div>未登录</div>;

  return <div>你好, {user.email}</div>;
}

方法4:Next.js集成

步骤1:创建API实用程序

// lib/bknd.ts
import { Api } from "bknd";

// 客户端单例
let clientApi: Api | null = null;

export function getClientApi() {
  if (typeof window === "undefined") {
    throw new Error("getClientApi只能在客户端使用");
  }

  if (!clientApi) {
    clientApi = new Api({
      host: process.env.NEXT_PUBLIC_BKND_URL!,
      storage: localStorage,
    });
  }

  return clientApi;
}

// 服务器端(每个请求)
export function getServerApi(request?: Request) {
  return new Api({
    host: process.env.BKND_URL!,
    request,  // 从Cookie/头中提取令牌
  });
}

步骤2:客户端组件

"use client";

import { useEffect, useState } from "react";
import { getClientApi } from "@/lib/bknd";

export function PostList() {
  const [posts, setPosts] = useState([]);
  const api = getClientApi();

  useEffect(() => {
    api.data.readMany("posts").then(({ data }) => setPosts(data));
  }, []);

  return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}

步骤3:服务器组件

// app/posts/page.tsx
import { getServerApi } from "@/lib/bknd";

export default async function PostsPage() {
  const api = getServerApi();
  const { data: posts } = await api.data.readMany("posts");

  return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}

TypeScript类型注册

获取实体名称和字段的自动完成:

// types/bknd.d.ts
import { em, entity, text, number } from "bknd";

// 定义您的模式
const schema = em({
  posts: entity("posts", {
    title: text(),
    views: number(),
  }),
  users: entity("users", {
    email: text(),
    name: text(),
  }),
});

// 全局注册类型
type Database = (typeof schema)["DB"];
declare module "bknd" {
  interface DB extends Database {}
}

现在 api.data.readMany("posts") 返回类型化的 Post[]

Api类方法参考

// 身份验证状态
api.getUser()           // 当前用户或null
api.isAuthenticated()   // 是否有有效令牌
api.isAuthVerified()    // 令牌已通过服务器验证
api.verifyAuth()        // 验证令牌(异步)
api.getAuthState()      // { token, user, verified }

// 模块API
api.data.readMany(...)  // CRUD操作
api.auth.login(...)     // 身份验证
api.media.upload(...)   // 文件上传
api.system.health()     // 系统检查

// 令牌管理
api.updateToken(token)  // 手动设置令牌
api.token_transport     // "header" | "cookie" | "none"

环境变量

# .env.local (Next.js)
NEXT_PUBLIC_BKND_URL=http://localhost:7654
BKND_URL=http://localhost:7654

# .env (Vite)
VITE_BKND_URL=http://localhost:7654

常见陷阱

令牌未持久化

问题: 用户刷新后注销

修复: 提供存储选项:

// 错误 - 无持久化
const api = new Api({ host: "..." });

// 正确
const api = new Api({
  host: "...",
  storage: localStorage,
});

CORS错误

问题: 浏览器阻止向后端发送请求

修复: 在后端配置CORS:

// bknd.config.ts (服务器)
const app = new App({
  server: {
    cors: {
      origin: ["http://localhost:3000"],
      credentials: true,
    },
  },
});

身份验证状态未更新

问题: UI未反映登录/注销

修复: 使用 onAuthStateChange

const api = new Api({
  host: "...",
  onAuthStateChange: (state) => {
    // 在此处更新您的UI状态
    setUser(state.user ?? null);
  },
});

SSR水合不匹配

问题: 服务器/客户端渲染不同内容

修复: 仅在客户端检查身份验证:

function AuthStatus() {
  const [user, setUser] = useState(null);
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
    setUser(api.getUser());
  }, []);

  if (!mounted) return null;  // 避免水合不匹配

  return user ? <span>{user.email}</span> : <span>访客</span>;
}

使用错误的导入路径

问题: 导入错误

修复: 使用正确的子路径:

// 独立API
import { Api } from "bknd";

// React浏览器适配器
import { BkndBrowserApp, useApp } from "bknd/adapter/browser";

// Next.js适配器
import type { NextjsBkndConfig } from "bknd/adapter/nextjs";

多个Api实例

问题: 应用中的身份验证状态不一致

修复: 使用单例模式:

// lib/api.ts
let api: Api | null = null;

export function getApi() {
  if (!api) {
    api = new Api({ host: "...", storage: localStorage });
  }
  return api;
}

验证

测试您的设置:

import { api } from "./lib/api";

async function test() {
  // 1. 检查连接
  const { ok } = await api.data.readMany("posts", { limit: 1 });
  console.log("API连接:", ok);

  // 2. 检查身份验证
  console.log("已认证:", api.isAuthenticated());
  console.log("用户:", api.getUser());

  // 3. 测试登录
  const { ok: loginOk } = await api.auth.login("password", {
    email: "test@example.com",
    password: "password123",
  });
  console.log("登录:", loginOk);
}

注意事项

应做:

  • 使用 storage: localStorage 持久化令牌
  • 处理 onAuthStateChange 以实现响应式UI
  • 使用单例模式处理Api实例
  • 在应用启动时调用 verifyAuth()
  • 使用环境变量获取主机URL

不应做:

  • 创建多个Api实例
  • 忘记在后端配置CORS
  • 在SSR中直接使用Api而不提供请求上下文
  • 硬编码后端URL
  • 忽略UI中的身份验证状态变化

相关技能

  • bknd-api-discovery - 探索可用端点
  • bknd-login-flow - 实现身份验证
  • bknd-crud-read - 使用SDK查询数据
  • bknd-session-handling - 管理用户会话
  • bknd-local-setup - 设置本地后端