SDK
The React SDK gives you a typed client, auth helpers, and hooks to connect your frontend to Based.
Install
bun add @weirdscience/based-client
# or: npm install @weirdscience/based-client
# or: pnpm add @weirdscience/based-clientCreate a client
import { createClient } from "@weirdscience/based-client";
export const based = createClient({
url: process.env.NEXT_PUBLIC_BASED_URL!,
anonKey: process.env.NEXT_PUBLIC_BASED_ANON_KEY!,
});Wrap your app
BasedProvider makes the client available to all hooks in the component tree.
import { BasedProvider } from "@weirdscience/based-client";
import { based } from "@/lib/based";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<BasedProvider client={based}>
{children}
</BasedProvider>
);
}Type Safety
The SDK supports end-to-end type safety through generated TypeScript interfaces. Run based typegen to generate a based.d.ts file containing typed interfaces for all your tables.
based typegenThis produces a file like the following with interfaces matching your database schema:
export interface Tables {
posts: {
id: string;
title: string;
content: string | null;
status: string;
createdAt: number;
updatedAt: number;
};
}
export type TableName = keyof Tables;
export type Row<T extends TableName> = Tables[T];Pass the Tables type and a table name as generics to useQuery and useMutation for full type inference on filters, return data, and mutation payloads.
import type { Tables } from "./based.d.ts";
import { useQuery, useMutation } from "@weirdscience/based-client";
// data is typed as Tables["posts"][]
const { data } = useQuery<Tables, "posts">("posts", {
filter: { status: "published" },
});
// mutate() accepts Omit<Tables["posts"], "id" | "createdAt" | "updatedAt">
const { mutate } = useMutation<Tables, "posts">("posts", "create");
await mutate({ title: "Hello world", content: "My first post", status: "draft" });Re-run based typegen after any schema change to keep your types in sync.
Auth methods
The client exposes auth methods directly on client.auth.
// Sign up a new user
await based.auth.signUp("user@example.com", "password123");
// Sign in
await based.auth.signIn("user@example.com", "password123");
// Sign out (invalidates current session)
await based.auth.signOut();
// Fetch the current user via /auth/me
const user = await based.auth.getUser();
// Manually refresh the session
await based.auth.refreshSession();The SDK automatically refreshes tokens when a request returns a 401. You do not need to call refreshSession manually in most cases.
Session persistence
Sessions persist across page reloads via localStorage by default. On mount, the client restores the saved tokens and validates them by calling /auth/me.
Use useUser().isLoading or client.ready() to avoid flashing a logged-out UI during hydration:
const { user, isLoading } = useUser();
if (isLoading) return <Spinner />;
if (!user) return <LoginForm />;
return <Dashboard user={user} />;Opt out, or use a custom storage adapter (cookies, IndexedDB, React Native AsyncStorage, etc.):
// Disable persistence entirely (in-memory only)
createClient({ url, anonKey, storage: false });
// Custom storage adapter — getItem/setItem/removeItem can return promises
createClient({
url,
anonKey,
storage: {
getItem: (k) => AsyncStorage.getItem(k),
setItem: (k, v) => AsyncStorage.setItem(k, v),
removeItem: (k) => AsyncStorage.removeItem(k),
},
});