@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
| Export | Description |
|---|---|
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 === auth2The 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/databasefor persistence - UUID v7 -- time-sortable IDs via the
generateIdhook - 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
| Variable | Required | Description |
|---|---|---|
AUTH_SECRET | Yes | Minimum 32 characters |
AUTH_URL | Yes | Public URL of the auth server |
AUTH_CORS_ORIGIN | No | Allowed CORS origin |
AUTH_EMAIL_FROM_NAME | No | Sender name for emails |
VITE_PUBLIC_AUTH_GOOGLE_ID | No | Google OAuth client ID |
AUTH_GOOGLE_SECRET | No | Google OAuth client secret |
AUTH_GITHUB_ID | No | GitHub OAuth client ID |
AUTH_GITHUB_SECRET | No | GitHub 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 })