Better Auth Integration
Better Auth Integration
Section titled “Better Auth Integration”Better Auth is a framework-agnostic authentication library for TypeScript. This guide shows how to protect your Tabula Lens endpoint with Better Auth and pass session credentials from the frontend.
Prerequisites
Section titled “Prerequisites”Install the required packages.
Backend:
npm i better-authpnpm add better-authyarn add better-authFrontend:
npm i better-authpnpm add better-authyarn add better-authSet the required environment variables:
# Generate with: openssl rand -base64 32BETTER_AUTH_SECRET=your-secret-key-minimum-32-charactersBETTER_AUTH_URL=http://localhost:3000Auth instance
Section titled “Auth instance”Create a shared auth instance. Better Auth supports multiple database adapters — the example below uses a generic placeholder; refer to the Better Auth docs for your specific adapter.
import { betterAuth } from 'better-auth';
export const auth = betterAuth({ database: { /* your DB adapter */ }, emailAndPassword: { enabled: true }, // Optional: social providers socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }, },});Backend Setup
Section titled “Backend Setup”The integration pattern is the same for all frameworks: validate the session before passing the request to the Tabula Lens handler.
Next.js App Router
Section titled “Next.js App Router”Mount the Better Auth handler and add session validation to the Tabula Lens route:
import { auth } from '@/lib/auth';import { toNextJsHandler } from 'better-auth/next-js';
export const { GET, POST } = toNextJsHandler(auth);import { auth } from '@/lib/auth';import { headers } from 'next/headers';import { TabulaLens, createNextRouteHandler } from '@tabula-lens/node';
const tabulaLens = new TabulaLens(process.env.DATABASE_URL!);const handler = createNextRouteHandler(tabulaLens);
export async function GET(request: Request) { const session = await auth.api.getSession({ headers: await headers(), });
if (!session) { return new Response('Unauthorized', { status: 401 }); }
return handler(request);}Express
Section titled “Express”Mount the Better Auth handler and add an auth middleware before the Tabula Lens adapter:
import express from 'express';import { fromNodeHeaders, toNodeHandler } from 'better-auth/node';import { auth } from './lib/auth';import { TabulaLens, expressAdapter } from '@tabula-lens/node';
const app = express();const tabulaLens = new TabulaLens(process.env.DATABASE_URL!);
// Mount the Better Auth handler (Express v5 syntax)// For Express v4, use '/api/auth/*' insteadapp.all('/api/auth/{*any}', toNodeHandler(auth));
// Auth middlewareasync function requireAuth(req, res, next) { const session = await auth.api.getSession({ headers: fromNodeHeaders(req.headers), });
if (!session?.user) { return res.status(401).json({ error: 'Unauthorized' }); }
(req as any).user = session.user; next();}
// Protect the Tabula Lens endpointapp.use('/api/tabula-lens', requireAuth, expressAdapter(tabulaLens));Frontend Integration
Section titled “Frontend Integration”Create an auth client and use the session state to gate the viewer. Better Auth uses session cookies by default, so no token needs to be forwarded for same-origin requests — the browser sends the cookie automatically.
import { createAuthClient } from 'better-auth/react';
export const authClient = createAuthClient({ baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL!,});'use client';import { authClient } from '@/lib/auth-client';import { DatabaseViewer } from '@tabula-lens/react';
export function TabulaLensViewer() { const { data: session, isPending } = authClient.useSession();
if (isPending) return <div>Loading...</div>; if (!session) return <div>Sign in to view data</div>;
return ( <DatabaseViewer path="/api/tabula-lens" getAuthHeaders={async () => ({})} // Session cookie is sent automatically for same-origin requests /> );}Important Notes
Section titled “Important Notes”- Cookie-based sessions — Better Auth sends session data via
HttpOnlycookies. Same-origin API routes receive the cookie automatically;getAuthHeaderscan return an empty object in that case. - Cross-origin or mobile clients — Enable the bearer plugin on the server and use
bearerClienton the client to exchange a session token via theAuthorizationheader instead. - Database queries per request — Each
auth.api.getSession()call hits the database. Enable cookie caching to reduce the load:lib/auth.ts export const auth = betterAuth({// ...session: {cookieCache: {enabled: true,maxAge: 60 * 60, // 1 hour},},}); - Schema migrations — Re-run
npx @better-auth/cli@latest migratewhenever you add or remove plugins, as some plugins require additional database tables.