npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

effect-better-auth

v0.1.0

Published

Effect.ts integration for Better Auth with Kysely/Postgres support

Readme

effect-better-auth

Effect.ts integration for Better Auth with Kysely/Postgres support.

Features

  • 🎯 Type-safe Better Auth integration using Effect.ts
  • 🗄️ Kysely database adapter for Postgres
  • 🔄 Effect layers for dependency injection
  • 🔐 HTTP middleware for authentication
  • ⚡ Built for production use

Installation

pnpm add effect-better-auth better-auth kysely pg
pnpm add -D @types/pg

Quick Start

import { Auth, BetterAuthRouter } from "effect-better-auth"
import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"
import * as Layer from "effect/Layer"
import { createServer } from "node:http"

// Merge BetterAuthRouter with your routes
const AllRoutes = Layer.mergeAll(
  YourApiRoutes,
  BetterAuthRouter  // Handles /api/auth/* endpoints
)

// Provide Auth.Default layer
const HttpLive = HttpLayerRouter.serve(AllRoutes).pipe(
  Layer.provide(
    NodeHttpServer.layer(createServer, { port: 3000 })
  ),
  Layer.provide(Auth.Default)
)

// Run server
NodeRuntime.runMain(
  Layer.launch(HttpLive).pipe(Effect.scoped)
)

Authentication Middleware Pattern

1. Define AuthContext

Create a context tag to hold authenticated user information:

import { Context } from "effect"

export class AuthContext extends Context.Tag("AuthContext")<
  AuthContext,
  { readonly user_id: string }
>() {}

2. Create Authorization Middleware

Build middleware that extracts session from Better Auth and provides AuthContext:

import { HttpApiMiddleware, HttpServerRequest } from "@effect/platform"
import { Auth } from "effect-better-auth"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as Schema from "effect/Schema"

export class Unauthorized extends Schema.TaggedError<Unauthorized>()(
  "Unauthorized",
  { details: Schema.String }
) {}

export class Authorization extends HttpApiMiddleware.Tag<Authorization>()(
  "Authorization",
  {
    failure: Unauthorized,
    provides: AuthContext,
  }
) {}

export const AuthorizationLive = Layer.effect(
  Authorization,
  Effect.gen(function* () {
    const auth = yield* Auth

    return Effect.gen(function* () {
      // Extract headers from HTTP request
      const headers = yield* HttpServerRequest.schemaHeaders(
        Schema.Struct({
          cookie: Schema.optional(Schema.String),
          authorization: Schema.optional(Schema.String),
        })
      ).pipe(
        Effect.mapError(() =>
          new Unauthorized({ details: "Failed to parse headers" })
        )
      )

      // Forward to Better Auth
      const forwardedHeaders = new Headers()
      if (headers.cookie) {
        forwardedHeaders.set("cookie", headers.cookie)
      }
      if (headers.authorization) {
        forwardedHeaders.set("authorization", headers.authorization)
      }

      // Get session from Better Auth
      const session = yield* Effect.tryPromise({
        try: () => auth.api.getSession({ headers: forwardedHeaders }),
        catch: (cause) =>
          new Unauthorized({ details: String(cause) }),
      })

      if (!session) {
        return yield* Effect.fail(
          new Unauthorized({ details: "Missing or invalid authentication" })
        )
      }

      // Provide authenticated user context
      return AuthContext.of({ user_id: session.user.id })
    })
  })
)

3. Apply Middleware to API Endpoints

import { HttpApiGroup, HttpApiEndpoint } from "@effect/platform"
import * as Schema from "effect/Schema"

export class MyApiGroup extends HttpApiGroup.make("myapi")
  .add(
    HttpApiEndpoint.get("getData", "/data")
      .addSuccess(Schema.Array(DataSchema))
  )
  .add(
    HttpApiEndpoint.post("createData", "/data")
      .setPayload(CreateDataPayload)
      .addSuccess(DataSchema)
  )
  .middleware(Authorization)  // Require auth for all endpoints
  .prefix("/api") {}

4. Access AuthContext in Handlers

import { HttpApiBuilder } from "@effect/platform"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"

const MyApiHandlers = HttpApiBuilder.group(MyApi, "myapi", (handlers) =>
  handlers
    .handle("getData", () =>
      Effect.gen(function* () {
        const auth = yield* AuthContext  // Get authenticated user
        const repo = yield* DataRepo

        // Use auth.user_id to scope queries
        return yield* repo.findAll(auth.user_id)
      })
    )
    .handle("createData", ({ payload }) =>
      Effect.gen(function* () {
        const auth = yield* AuthContext
        const repo = yield* DataRepo

        return yield* repo.create(payload, auth.user_id)
      })
    )
)

export const MyApiHandlersLive = Layer.provide(
  MyApiHandlers,
  Layer.mergeAll(
    DataRepo.Default,
    // Other dependencies
  )
)

5. Wire Everything Together

import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"
import * as Layer from "effect/Layer"

// Add your API with authorization middleware
const MyApiLive = HttpLayerRouter.addHttpApi(MyApi).pipe(
  Layer.provide(MyApiHandlersLive),
  Layer.provide(AuthorizationLive)  // Provide auth middleware
)

// Merge with Better Auth router
const AllRoutes = Layer.mergeAll(
  MyApiLive,
  BetterAuthRouter  // Handles /api/auth/*
)

// Serve with Auth.Default
const HttpLive = HttpLayerRouter.serve(AllRoutes).pipe(
  Layer.provide(
    NodeHttpServer.layer(createServer, { port: 3000 })
  ),
  Layer.provide(Auth.Default)  // Provides Auth service
)

Environment Configuration

Required environment variables:

BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-secret-key-here
DATABASE_URL=postgresql://user:pass@localhost:5432/db
CLIENT_ORIGIN=http://localhost:5173  # Optional, defaults to this

Access environment config in code:

import { AuthEnv } from "effect-better-auth"
import * as Effect from "effect/Effect"

const program = Effect.gen(function* () {
  const env = yield* AuthEnv
  console.log(env.BETTER_AUTH_URL)
  // env.BETTER_AUTH_SECRET is Redacted type
  // env.DATABASE_URL is Redacted type
})

Better Auth Configuration

The package automatically configures Better Auth with:

  • Email/Password authentication: Enabled by default
  • Postgres database: Via Kysely adapter
  • Automatic migrations: Runs on startup
  • Camel case: Database column naming

The Auth service exposes the full Better Auth API:

import { Auth } from "effect-better-auth"
import * as Effect from "effect/Effect"

const program = Effect.gen(function* () {
  const auth = yield* Auth

  // Access Better Auth API
  const session = yield* Effect.promise(() =>
    auth.api.getSession({ headers: request.headers })
  )

  // Other Better Auth methods available on auth.api
  // signIn, signUp, signOut, etc.
})

Testing

Mock the Auth service for tests:

import { Auth } from "effect-better-auth"
import * as Layer from "effect/Layer"

const TestAuthLayer = Layer.succeed(Auth, {
  api: {
    getSession: () => Promise.resolve({
      user: {
        id: "test-user",
        email: "[email protected]",
        emailVerified: false,
        name: "Test User",
        created_at: new Date(),
        updated_at: new Date()
      },
      session: {
        id: "test-session",
        user_id: "test-user",
        token: "test-token",
        expiresAt: new Date(Date.now() + 86400000),
        created_at: new Date(),
        updated_at: new Date()
      }
    })
  }
} as Auth)

// Mock authorization middleware for tests
const TestAuthorizationLayer = Layer.effect(
  Authorization,
  Effect.succeed(
    Effect.succeed(
      AuthContext.of({ user_id: "test-user" })
    )
  )
)

// Use in tests
const testProgram = yourProgram.pipe(
  Effect.provide(TestAuthorizationLayer),
  Effect.provide(TestAuthLayer)
)

API Reference

Auth

Effect service providing Better Auth instance.

Type: Effect.Service<Auth> Access: yield* Auth in Effect.gen Layer: Auth.Default - Reads from AuthEnv, creates Kysely connection, configures Better Auth Properties:

  • api - Better Auth API with methods like getSession, signIn, signUp, etc.

BetterAuthRouter

Effect HTTP router layer that handles Better Auth endpoints.

Type: Layer.Layer<never> Routes: Handles all requests to /api/auth/* Usage: Merge with your application routes using Layer.mergeAll

AuthEnv

Effect service providing typed environment configuration.

Type: Effect.Service<AuthEnv> Layer: AuthEnv.Default - Reads from process.env Properties:

  • BETTER_AUTH_URL - Base URL for auth endpoints (string)
  • BETTER_AUTH_SECRET - Secret for signing tokens (Redacted)
  • DATABASE_URL - Postgres connection string (Redacted)
  • CLIENT_ORIGIN - Allowed CORS origin (string, optional, defaults to http://localhost:5173)

AuthKysely

Effect service providing Kysely database instance.

Type: Effect.Service<AuthKysely> Layer: AuthKysely.Default - Creates Postgres connection pool Usage: Internal to Auth service, provides database adapter to Better Auth

How It Works

  1. BetterAuthRouter handles /api/auth/* endpoints (sign-in, sign-up, etc.)
  2. Better Auth sets session cookies on successful authentication
  3. Your Authorization middleware extracts cookies/headers from requests
  4. Middleware calls auth.api.getSession() to validate
  5. On valid session, middleware provides AuthContext with user_id
  6. Your API handlers access AuthContext via yield* AuthContext
  7. Handlers use user_id to scope database queries per user

License

Apache-2.0

Author

Ryan Hunter (@artimath)

Links