BTTRBTTR Blocks

Auth with Hono, Better Auth & Drizzle

Learn how to quickly build a Hono auth backend using Better Auth and Drizzle ORM.

Project Structure

This project utilizes a monorepo architecture to enforce separation of concerns. The structure isolates the core API application from shared internal packages, specifically separating authentication logic and database management into workspaces.

index.ts
.env
package.json
tsconfig.json
wrangler.jsonc

Setup Environment

Configure environment variables for API, auth, and database. These secrets will be used across the project.

Create .env Files

Each package has its own .env for its specific configuration.

apps/api/.env
DATABASE_URL=
BETTER_AUTH_SECRET=
packages/db/.env
DATABASE_URL=
packages/auth/.env
DATABASE_URL=
BETTER_AUTH_SECRET=

Configure Database

The database package manages the connection and schema definitions.

Database Client

packages/db/src/index.ts
import "dotenv/config";
import { drizzle } from "drizzle-orm/node-postgres";

export function createDBClient(database_url: string) {
  return drizzle(database_url!);
}

Create Package Config

import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  out: './drizzle',
  schema: './src/schema',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Define Schema

Create database tables to match Better Auth's requirements.

Generate Better Auth Schemas

This command generates all required auth tables automatically.

npx @better-auth/cli generate --config ./packages/auth/better-auth.config.ts --output ./packages/db/src/schema/auth.ts

Export Schemas

We split the schema into modules. The auth.ts file will be auto-generated by the Better Auth CLI later.

packages/db/src/schema/index.ts
import * as authTables from "./auth";

export { authTables as authSchema };

Migrate Database

Apply schema changes to your database.

Generate Migration Files

npx drizzle-kit generate

Apply Migrations

npx drizzle-kit migrate

View Database in Drizzle Studio

Inspect your tables and data visually.

npx drizzle-kit studio

Configure Auth

Set up the Better Auth package to handle authentication with your Hono backend.

Create Better Auth Package

This file initializes a Better Auth instance with a Drizzle adapter and optional plugins.

packages/auth/src/index.ts
import { betterAuth } from "better-auth";
import { openAPI } from "better-auth/plugins"
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { createDBClient } from "@better-hr/db";
import { authSchema } from "@better-hr/db/schema";

export function createAuth({ database_url }: { database_url: string }) {
  const db = createDBClient(database_url);

  return betterAuth({
   	appName: "BTTR Blocks Authentication",
    basePath: "/auth",
    experimental: { joins: true },
    database: drizzleAdapter(db, {
      provider: "pg",
      schema: {
        ...authSchema,
      },
    }),
    plugins: [ 
      openAPI({ disableDefaultReference: false })
    ] 
  });
}

type AuthInstance = ReturnType<typeof createAuth>

export type AuthType = {
  user: AuthInstance["$Infer"]["Session"]["user"] | null
  session: AuthInstance["$Infer"]["Session"]["session"] | null
}

For more plugins and advanced configuration, see Better Auth Docs.

Create Package Config

Define the database connection and schema location for migrations.

import { createAuth } from "./src";

export const auth = createAuth({ database_url: process.env.DATABASE_URL! });

For more plugins and advanced configuration, see Better Auth Docs.

Build API with Hono

The Hono application serves as the primary backend service. It uses the `@bttr-blocks/auth package to expose authentication endpoints.

Create Hono Package

The entry point initializes the Hono app and mounts the authentication routes.

apps/backend/src/index.ts
import { Hono } from 'hono'
import { default as AuthRoute } from "@/paths/auth";

const app = new Hono();

app.get(
  "/auth/docs",
  Scalar({
    title: "BTTR Blocks Auth API",
    slug: "bttr-blocks-auth",
    pageTitle: "BTTR Blocks Auth API",
    sources: [{ url: "/auth/open-api/generate-schema", title: "Auth" }],
  }),
);

app.get("/", (c) => {
  return c.text('Welcome to BTTR Blocks!')
});

app.route("/", AuthRoute);

export default app;

In the auth route handler, we instantiate the auth client using the request's environment bindings and attach the Better Auth handler. CORS middleware is configured to allow cross-origin requests from the client application.

apps/backend/src/paths/auth/index.ts
import { Hono } from 'hono'
import { createAuth, AuthType } from "@better-hr/auth";
import { cors } from "hono/cors";

type Bindings = AuthType & {
  DATABASE_URL: string;
};

const app = new Hono<{ Bindings: Bindings }>({});

// CORS middleware
app.use(
  "/auth/*",
  cors({
    origin: "http://localhost:3001",
    allowHeaders: ["Content-Type", "Authorization"],
    allowMethods: ["POST", "GET", "OPTIONS"],
    exposeHeaders: ["Content-Length"],
    maxAge: 600,
    credentials: true,
  }),
);

// Auth route
app.on(["POST", "GET"], "/auth/*", (c) => {
  const auth = createAuth({
    database_url: c.env.DATABASE_URL,
  });

  return auth.handler(c.req.raw);
});

export default app;

Create Package Config

{
  "name": "@bttr-blocks/api",
  "version": "0.1.0",
  "type": "module",
  "private": true,
  "scripts": {
    "dev": "wrangler dev",
    "deploy": "wrangler deploy --minify",
    "cf-typegen": "wrangler types --env-interface CloudflareBindings"
  },
  "dependencies": {
    "@scalar/hono-api-reference": "^0.9.28",
    "better-auth": "^1.4.5",
    "drizzle-orm": "^0.44.7",
    "hono": "^4.10.7",
  },
  "devDependencies": {
    "@bttr-hr/auth": "workspace:*",
    "@bttr-hr/db": "workspace:*",
    "wrangler": "^4.4.0"
  }
}

Start the API server

npm run dev

Inspired by the shadcn login block!

On this page