名称: 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 - 设置本地后端