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
| File | Purpose |
|---|---|
index.ts | Creates the postgres client and Drizzle instance with pool config |
health/index.ts | checkDatabaseService() — 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:
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):
| Option | Env var | Default |
|---|---|---|
max | DATABASE_POOL_MAX | 10 |
idle_timeout | DATABASE_IDLE_TIMEOUT | 30 |
connect_timeout | DATABASE_CONNECT_TIMEOUT | 10 |
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:
| Table | Purpose |
|---|---|
auth_user | Users with name, email, role, email verification |
auth_session | Session tokens with expiry, IP, and user agent |
auth_account | OAuth/credential accounts (provider, tokens, password) |
auth_verification | One-time verification tokens (email confirmations) |
Roles
The Role type is defined in src/core/auth/types/index.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:
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 succeededunhealthy— the query failed or timed out (includes the error message)
See API Endpoints for the full health check documentation.