Raypx

@raypx/auth

Better Auth integration with lazy initialization.

@raypx/auth integrates Better Auth with Drizzle ORM and TanStack Start. It uses lazy initialization to avoid loading auth configuration until the first request.

Core API

ExportDescription
createAuth()Create a new Better Auth instance (call directly only if needed)
getAuth()Get the lazily-initialized auth singleton
auth(deprecated) Proxy-based lazy getter; use getAuth() instead

Lazy Initialization

getAuth() creates the auth instance on first call and caches it. This avoids importing Better Auth at module evaluation time:

import { getAuth } from "@raypx/auth"

// First call: creates the instance
const auth = getAuth()

// Subsequent calls: returns cached instance
const auth2 = getAuth() // auth === auth2

The auth export is deprecated. It uses a Proxy pattern that is less explicit. Use getAuth() instead.

Configuration

The auth instance is configured with:

  • Drizzle adapter -- uses @raypx/database for persistence
  • UUID v7 -- time-sortable IDs via the generateId hook
  • Email/password -- enabled by default with password reset support
  • Email verification -- automatic on sign-up, auto sign-in after verification
  • Social providers -- dynamically registered based on available env vars
  • TanStack Start cookies -- via the tanstackStartCookies() plugin

Dynamic Social Provider Registration

Social providers are only registered when their credentials are present in the environment:

// packages/auth/src/index.ts (simplified)
const socialProviders: Record<string, unknown> = {}

if (env.VITE_PUBLIC_AUTH_GOOGLE_ID && env.AUTH_GOOGLE_SECRET) {
  socialProviders.google = {
    clientId: env.VITE_PUBLIC_AUTH_GOOGLE_ID,
    clientSecret: env.AUTH_GOOGLE_SECRET,
  }
}

if (env.AUTH_GITHUB_ID && env.AUTH_GITHUB_SECRET) {
  socialProviders.github = {
    clientId: env.AUTH_GITHUB_ID,
    clientSecret: env.AUTH_GITHUB_SECRET,
  }
}

This means adding a new OAuth provider requires only setting the environment variables -- no code changes.

Environment Variables

VariableRequiredDescription
AUTH_SECRETYesMinimum 32 characters
AUTH_URLYesPublic URL of the auth server
AUTH_CORS_ORIGINNoAllowed CORS origin
AUTH_EMAIL_FROM_NAMENoSender name for emails
VITE_PUBLIC_AUTH_GOOGLE_IDNoGoogle OAuth client ID
AUTH_GOOGLE_SECRETNoGoogle OAuth client secret
AUTH_GITHUB_IDNoGitHub OAuth client ID
AUTH_GITHUB_SECRETNoGitHub OAuth client secret

Email Functions

The auth package delegates email sending to @raypx/email templates:

import { sendVerificationEmail, sendPasswordResetEmail } from "@raypx/auth/email"

// Called automatically by Better Auth callbacks:
// sendVerificationEmail({ email, name, url })
// sendPasswordResetEmail({ email, name, url })

Usage in RPC Context

The RPC layer uses auth to populate request context:

import { auth } from "@raypx/auth"

export async function createContext({ req }: { req: Request }) {
  const session = await auth.api.getSession({ headers: req.headers })
  return { session }
}

Deprecated Auth Proxy

The old auth export uses a Proxy to lazily delegate to getAuth(). While it still works, prefer the explicit getAuth() call for clarity and to avoid Proxy overhead:

// Deprecated
import { auth } from "@raypx/auth"
auth.api.getSession({ headers })

// Recommended
import { getAuth } from "@raypx/auth"
getAuth().api.getSession({ headers })

On this page