Skip to content

Database

saaskip uses Drizzle ORM with the postgres-js driver for type-safe database access. The client is configured with connection pooling, query logging to Axiom, and a health check integrated into the /api/health endpoint.

File structure

FilePurpose
index.tsCreates the postgres client and Drizzle instance with pool config
health/index.tscheckDatabaseService() — pings the database with SELECT 1
../auth/db-schemas/Auth tables (users, sessions, accounts, verifications)
../auth/types/Shared types (Role)

Client

The database client is created in src/core/db/index.ts:

ts
import { db } from '@/core/db';

The db instance is a Drizzle ORM client backed by a postgres-js connection pool. The raw postgres client is private — all database access goes through Drizzle.

Pool configuration

Pool settings are read from environment variables (see Infrastructure):

OptionEnv varDefault
maxDATABASE_POOL_MAX10
idle_timeoutDATABASE_IDLE_TIMEOUT30
connect_timeoutDATABASE_CONNECT_TIMEOUT10

Query logging

In non-production environments, every SQL query is logged to Axiom at debug level with the query string and parameters. Logging is disabled in production to avoid noise and cost.

Schemas

Schemas live in src/**/db-schemas/ directories and are auto-discovered by Drizzle Kit via the glob in drizzle.config.ts.

Auth schemas

The auth schemas (src/core/auth/db-schemas/index.ts) define the tables required by Better Auth:

TablePurpose
auth_userUsers with name, email, role, email verification
auth_sessionSession tokens with expiry, IP, and user agent
auth_accountOAuth/credential accounts (provider, tokens, password)
auth_verificationOne-time verification tokens (email confirmations)

Roles

The Role type is defined in src/core/auth/types/index.ts:

ts
type Role = 'guest' | 'manager' | 'admin' | 'superAdmin';

The role column on auth_user defaults to 'guest'.

Inferred types

Drizzle infers select and insert types from the schema definitions:

ts
import { type User, type UserInsert } from '@/core/auth/db-schemas';

const newUser: UserInsert = {
  name: 'Alice',
  email: 'alice@example.com',
  emailVerified: false,
};

Migrations

Migrations are generated by Drizzle Kit and stored in drizzle/. See Infrastructure for the workflow.

Health check

The database is included in the /api/health endpoint as the database service. Unlike other services (Axiom, Sentry, Arcjet), the database is always required and never returns disabled.

The check runs SELECT 1 with a 3-second timeout:

  • healthy — the query succeeded
  • unhealthy — the query failed or timed out (includes the error message)

See API Endpoints for the full health check documentation.