Trovella Wiki

Package Architecture

The @repo/auth package structure, import conventions, server/client split, and integration points.

The @repo/auth package wraps Better Auth configuration and exposes three import paths with distinct purposes. This separation prevents server-only code from leaking into client bundles and enforces a clear boundary between auth types, auth operations, and browser-side auth calls.

Import Paths

ImportExportsUse In
@repo/authSession, User (types only)Anywhere
@repo/auth/serverauth instance, ensurePersonalOrganization, ensureActiveOrganization, Auth typeServer components, tRPC context, route handlers
@repo/auth/clientauthClient, AuthClient typeClient components only

Root Import: Types Only

import type { Session, User } from "@repo/auth";

The root import (@repo/auth) exports only TypeScript types. It never imports Better Auth at runtime. This is safe to use in any file -- server, client, or shared.

The types are inferred from the Better Auth instance using typeof auth.$Infer.Session.session and typeof auth.$Infer.Session.user, defined in packages/auth/src/types.ts.

Server Import: Auth Instance

import { auth, ensurePersonalOrganization, ensureActiveOrganization } from "@repo/auth/server";

The server export provides the configured Better Auth instance and the two organization bootstrapping helpers. This import triggers database connections and must only be used in server-side code.

The auth instance is configured with:

  • Database: Drizzle adapter connected to the shared @repo/db client
  • Base URL: process.env["BETTER_AUTH_URL"]
  • Secret: process.env["BETTER_AUTH_SECRET"]
  • Social providers: Google only (MVP)
  • Plugins: organization() with custom type field
  • Session: Cookie cache (5-minute TTL)
  • Rate limiting: 30 requests per 60-second window, in-memory storage

Client Import: Browser Auth Client

import { authClient } from "@repo/auth/client";

The client export provides a Better Auth client configured with the organizationClient() plugin. It reads NEXT_PUBLIC_BETTER_AUTH_URL for the base URL.

This is used in "use client" components for browser-side operations like authClient.signIn.social().

Web App Integration Points

The @repo/auth package integrates into the web app at four points:

1. Route Handler (apps/web/src/app/api/auth/[...all]/route.ts)

The catch-all auth route forwards all requests to Better Auth using toNextJsHandler(auth). This handles OAuth callbacks, session creation, sign-out, and all other Better Auth API endpoints.

import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@repo/auth/server";

export const { GET, POST } = toNextJsHandler(auth);

2. Server Component Helper (apps/web/src/lib/auth-server.ts)

A thin wrapper that calls auth.api.getSession() with the current request headers. Used in server component pages to check authentication.

export async function getSession() {
  return auth.api.getSession({ headers: await headers() });
}

3. tRPC Context (packages/api/src/context.ts)

Creates the tRPC context by reading the session with cookie cache disabled (for mutation freshness):

const session = await auth.api.getSession({
  headers: opts.headers,
  query: { disableCookieCache: true },
});

4. Client Auth (apps/web/src/lib/auth-client.ts)

Re-exports the auth client from @repo/auth/client (or creates a local instance with the same config). Used by components like GoogleSignInButton.

SDK Boundary Enforcement

Better Auth is an external SDK that must only be imported through @repo/auth. This is enforced at two levels:

  1. ESLint no-restricted-imports -- any direct import of better-auth outside @repo/auth triggers a lint error
  2. dependency-cruiser -- the CI pipeline validates the dependency graph; better-auth may only appear as a dependency of @repo/auth

The restriction is defined in packages/config-eslint/restrictions.js under the SDK_BOUNDARIES map.

File Layout

packages/auth/
  src/
    index.ts    -- Re-exports Session and User types (root entry point)
    types.ts    -- Type inference from Better Auth instance
    server.ts   -- auth instance + ensurePersonalOrganization + ensureActiveOrganization
    client.ts   -- authClient for browser-side operations
  CLAUDE.md     -- Package conventions and gotchas
  package.json  -- Exports map defining the three entry points

Gotchas

Do not import from @repo/auth when you need the auth instance. The root import is types only. Use @repo/auth/server.

The nextCookies() plugin is not in this package. It imports next/headers which fails outside a Next.js app context. It lives in the route handler file.

Organization slugs have a 48-character limit. The generateSlug() function in server.ts truncates to 48 characters, lowercase, hyphens only.

Personal orgs cannot have members or invitations. This is enforced in CASL (see Identity & Access -- Authorization), not in Better Auth config.

drizzle-orm is a peer dependency. The @repo/auth package lists drizzle-orm as a peer dep because the Drizzle adapter requires it at runtime.

On this page