SaaS Core/Documentation

Database Configuration

This guide provides comprehensive instructions for configuring PostgreSQL database for SaaS Core, covering both local development and production environments.

Database Overview

SaaS Core uses the following database technology stack:

  • PostgreSQL: Primary database
  • Drizzle ORM: Database ORM and query builder
  • Drizzle Kit: Database migration tool

Local Development Environment Setup

1. Install PostgreSQL

macOS (using Homebrew)

# Install PostgreSQL
brew install postgresql@15

# Start PostgreSQL service
brew services start postgresql@15

# Create database user
createuser -s postgres

Ubuntu/Debian

# Update package list
sudo apt update

# Install PostgreSQL
sudo apt install postgresql postgresql-contrib

# Start service
sudo systemctl start postgresql
sudo systemctl enable postgresql

# Switch to postgres user
sudo -u postgres psql

Windows

  1. Download PostgreSQL installer
  2. Run the installer and follow the wizard to complete installation
  3. Remember the superuser password you set

2. Create Development Database

# Connect to PostgreSQL
psql -U postgres

# Create database
CREATE DATABASE saas_core_dev;

# Create application user
CREATE USER saas_user WITH PASSWORD 'your_secure_password';

# Grant privileges
GRANT ALL PRIVILEGES ON DATABASE saas_core_dev TO saas_user;

# Exit
\q

3. Configure Environment Variables

Add to your .env.local file:

# Local development database
DATABASE_URL="postgresql://saas_user:your_secure_password@localhost:5432/saas_core_dev"

Production Database Options

1. Neon (Recommended)

Neon is a modern PostgreSQL cloud service:

Advantages

  • Free tier includes 0.5GB storage
  • Automatic backup and recovery
  • Branching functionality (similar to Git)
  • Serverless architecture
  • Excellent developer experience

Setup Steps

  1. Visit Neon website and create an account
  2. Create a new project:
    Project name: saas-core
    PostgreSQL version: 15
    Region: Choose the region closest to your users
    
  3. Get the connection string:
    postgresql://username:[email protected]/dbname?sslmode=require
    

2. Supabase

Supabase provides PostgreSQL database with additional features:

Setup Steps

  1. Visit Supabase and create an account
  2. Create a new project
  3. Get the connection string from Settings > Database

3. Railway

Railway is a simple and easy-to-use cloud platform:

Setup Steps

  1. Visit Railway and log in
  2. Create a new project and add PostgreSQL service
  3. Get DATABASE_URL from the Variables tab

4. Other Options

  • AWS RDS: Enterprise-grade solution
  • Google Cloud SQL: Google Cloud Platform
  • Azure Database: Microsoft Cloud Platform
  • DigitalOcean Managed Databases: Simple and reliable

Drizzle ORM Configuration

1. Database Schema

View src/db/schema.ts for the complete database schema:

// Main data tables
export const users = pgTable('users', {
  id: text('id').primaryKey(),
  name: text('name'),
  email: text('email').notNull().unique(),
  emailVerified: timestamp('emailVerified', { mode: 'date' }),
  // Stripe related fields
  stripeCustomerId: text('stripeCustomerId'),
  subscriptionId: text('subscriptionId'),
  subscriptionStatus: text('subscriptionStatus'),
  // Points system
  points: integer('points').default(0),
  purchasedPoints: integer('purchasedPoints').default(0),
  giftedPoints: integer('giftedPoints').default(0),
  // Timestamps
  createdAt: timestamp('createdAt', { mode: 'date' }).defaultNow(),
  updatedAt: timestamp('updatedAt', { mode: 'date' }).defaultNow(),
})

2. Database Connection Configuration

View src/db/drizzle.ts for connection configuration:

import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'

const connectionString = process.env.DATABASE_URL!

// Disable prepared statements for compatibility with some cloud services
const client = postgres(connectionString, { prepare: false })

export const db = drizzle(client, { schema })

3. Drizzle Configuration File

View drizzle.config.ts:

import type { Config } from 'drizzle-kit'

export default {
  schema: './src/db/schema.ts',
  out: './drizzle',
  driver: 'pg',
  dbCredentials: {
    connectionString: process.env.DATABASE_URL!,
  },
} satisfies Config

Database Migrations

1. Initialize Database

# Push schema to database (development environment)
pnpm run db:push

# Or generate and run migrations (recommended for production)
pnpm run db:generate
pnpm run db:migrate

2. Available Database Commands

# Push schema changes to database
pnpm run db:push

# Generate migration files
pnpm run db:generate

# Run migrations
pnpm run db:migrate

# Open database management interface
pnpm run db:studio

3. Migration File Management

Migration files are located in the drizzle/ directory:

drizzle/
├── 0000_fixed_spiral.sql          # Initial schema
├── 0001_newsletter_subscriptions.sql  # Newsletter feature
├── 0002_add_user_role.sql         # User roles
├── 0003_add_user_points.sql       # Points system
├── 0004_add_points_history.sql    # Points history
└── meta/                          # Metadata files

Database Management

1. Drizzle Studio

Drizzle Studio provides visual database management:

# Start Drizzle Studio
pnpm run db:studio

# Visit https://local.drizzle.studio

2. Common SQL Queries

-- View user statistics
SELECT
  COUNT(*) as total_users,
  COUNT(CASE WHEN "emailVerified" IS NOT NULL THEN 1 END) as verified_users,
  COUNT(CASE WHEN "subscriptionStatus" = 'active' THEN 1 END) as active_subscribers
FROM users;

-- View subscription statistics
SELECT
  "subscriptionPlan",
  COUNT(*) as count,
  SUM(points) as total_points
FROM users
WHERE "subscriptionStatus" = 'active'
GROUP BY "subscriptionPlan";

-- View points usage
SELECT
  action,
  COUNT(*) as count,
  SUM(points) as total_points
FROM "pointsHistory"
GROUP BY action
ORDER BY total_points DESC;

3. Data Backup

Local Backup

# Create backup
pg_dump -U saas_user -h localhost saas_core_dev > backup.sql

# Restore backup
psql -U saas_user -h localhost saas_core_dev < backup.sql

Cloud Service Backup

Most cloud services provide automatic backup features:

  • Neon: Automatic daily backups, retained for 7 days
  • Supabase: Automatic backups with manual snapshot creation
  • Railway: Automatic backups with downloadable backup files

Database Security

1. Connection Security

# Use SSL connection (required for production)
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"

# Connection pool configuration
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require&connection_limit=20"

2. Access Control

-- Create read-only user (for analytics)
CREATE USER analytics_user WITH PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE saas_core TO analytics_user;
GRANT USAGE ON SCHEMA public TO analytics_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_user;

-- Create backup user
CREATE USER backup_user WITH PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE saas_core TO backup_user;
GRANT USAGE ON SCHEMA public TO backup_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO backup_user;

3. Data Encryption

  • Use strong passwords
  • Enable SSL/TLS connections
  • Regularly rotate passwords
  • Restrict network access

Performance Optimization

1. Index Optimization

View drizzle/add_performance_indexes.sql:

-- User email index (already has unique constraint)
-- CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

-- User subscription status index
CREATE INDEX CONCURRENTLY idx_users_subscription_status ON users(subscription_status)
WHERE subscription_status IS NOT NULL;

-- Points history user index
CREATE INDEX CONCURRENTLY idx_points_history_user_id ON points_history(user_id);

-- Points history timestamp index
CREATE INDEX CONCURRENTLY idx_points_history_created_at ON points_history(created_at);

2. Query Optimization

// Use indexed queries
const activeSubscribers = await db.query.users.findMany({
  where: eq(users.subscriptionStatus, 'active'),
  columns: {
    id: true,
    email: true,
    subscriptionPlan: true,
  }
})

// Paginated queries
const paginatedUsers = await db.query.users.findMany({
  limit: 20,
  offset: page * 20,
  orderBy: desc(users.createdAt)
})

3. Connection Pool Configuration

// Configure connection pool
const client = postgres(connectionString, {
  prepare: false,
  max: 20,          // Maximum connections
  idle_timeout: 20, // Idle timeout (seconds)
  connect_timeout: 10, // Connection timeout (seconds)
})

Testing Database Configuration

You can create test scripts to verify database configuration:

# Create test script (optional)
cat > scripts/test-database.js << 'EOF'
const { drizzle } = require('drizzle-orm/postgres-js');
const postgres = require('postgres');

async function testDatabase() {
  try {
    const client = postgres(process.env.DATABASE_URL, { prepare: false });
    const db = drizzle(client);

    // Test connection
    const result = await client`SELECT version()`;
    console.log('✅ Database connection successful');
    console.log('PostgreSQL version:', result[0].version);

    // Test if tables exist
    const tables = await client`
      SELECT table_name
      FROM information_schema.tables
      WHERE table_schema = 'public'
    `;

    console.log('✅ Table check:');
    tables.forEach(table => {
      console.log(`  - ${table.table_name}`);
    });

    await client.end();
    console.log('✅ Database test completed');

  } catch (error) {
    console.error('❌ Database test failed:', error.message);
  }
}

testDatabase();
EOF

# Run test
node scripts/test-database.js

Troubleshooting

1. Connection Failure

Problem: connection refused or timeout Solution:

  • Check if database service is running
  • Verify connection string format
  • Check firewall settings

2. SSL Connection Issues

Problem: SSL-related errors Solution:

# Disable SSL (development only)
DATABASE_URL="postgresql://user:pass@localhost:5432/db?sslmode=disable"

# Force SSL (production)
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"

3. Migration Failure

Problem: Migration execution failed Solution:

  • Check database permissions
  • Verify schema file syntax
  • Execute SQL statements manually

📞 Need Help?

If you encounter issues during database configuration, consult the Drizzle official documentation or contact technical support.