Skip to main content

Database Deployment

Reportr uses PostgreSQL with Prisma ORM for data persistence. This guide covers production database setup, migrations, and best practices.

Database Requirements

PostgreSQL Version

  • Minimum: PostgreSQL 12
  • Recommended: PostgreSQL 14 or higher
  • Compatibility: Tested with PostgreSQL 12-16

Connection Pooling

Required for serverless environments (Vercel, AWS Lambda):
  • PgBouncer - Connection pooler for PostgreSQL
  • Supabase Pooler - Built-in connection pooling
  • Neon - Automatic connection pooling
Without connection pooling, you may hit PostgreSQL connection limits in serverless environments.

Database Providers

Choose a PostgreSQL provider based on your needs:

Vercel Postgres

Pros: Deep Vercel integration, automatic pooling, simple setupCons: More expensive, limited to Vercel ecosystemPricing: 0.25/GBstored+0.25/GB stored + 0.02/GB transferredBest for: Vercel-first deployments

Neon

Pros: Serverless, autoscaling, generous free tier, branchingCons: Newer platform, limited regionsPricing: Free tier (0.5GB), $20/month for proBest for: Development and small-medium production

Supabase

Pros: Free tier (500MB), real-time features, backupsCons: Requires manual pooling configurationPricing: Free tier available, $25/month for proBest for: Projects needing real-time features

Railway

Pros: Simple setup, automatic backups, fair pricingCons: Less specialized for serverlessPricing: $5/month base + usageBest for: Developers wanting simplicity

Database Schema

Core Models

Reportr’s Prisma schema includes:
model User {
  id                   String    @id @default(cuid())
  email                String    @unique
  whiteLabelEnabled    Boolean   @default(false)
  companyName          String?
  primaryColor         String    @default("#8B5CF6")
  logo                 String?
  plan                 Plan      @default(FREE)
  subscriptionStatus   String    @default("free")
  // ... additional fields
  
  clients              Client[]
  reports              Report[]
  payments             Payment[]
}

model Client {
  id                           String    @id @default(cuid())
  name                         String
  domain                       String
  googleSearchConsoleConnected Boolean   @default(false)
  googleAnalyticsConnected     Boolean   @default(false)
  // ... Google API tokens (encrypted)
  
  userId                       String
  user                         User      @relation(fields: [userId], references: [id])
  reports                      Report[]
}

model Report {
  id                    String       @id @default(cuid())
  title                 String
  status                ReportStatus @default(PENDING)
  data                  Json?
  pdfUrl                String?
  aiInsights            Json?
  // ... additional fields
  
  clientId              String
  userId                String
  client                Client       @relation(fields: [clientId], references: [id])
  user                  User         @relation(fields: [userId], references: [id])
}

Key Features

  • User white-labeling: companyName, primaryColor, logo
  • Google API tokens: Encrypted storage for OAuth tokens
  • Report data: JSON fields for flexible data storage
  • AI insights: Structured AI-generated insights with metadata
  • Subscription management: PayPal integration for billing

Setup Instructions

1. Create Database

# Install Vercel CLI
npm i -g vercel

# Create database
vercel postgres create reportr-db

# Link to project
vercel link
vercel env pull .env.local
This automatically sets POSTGRES_URL and other connection variables.

2. Configure Environment Variables

Set in your deployment platform (Vercel, etc.):
# Primary connection (for Prisma migrations)
DATABASE_URL="postgresql://user:password@host:5432/database"

# Pooled connection (for application queries)
PRISMA_DATABASE_URL="postgresql://user:password@host:6543/database?pgbouncer=true"
Use DATABASE_URL for migrations and PRISMA_DATABASE_URL for runtime queries.

3. Run Migrations

1

Generate Prisma Client

npx prisma generate
2

Push schema to database

For production (first time):
npx prisma db push
For development (with migration history):
npx prisma migrate dev
3

Verify database

npx prisma studio
Opens Prisma Studio at http://localhost:5555 to browse your database.

4. Seed Initial Data (Optional)

npm run db:seed
This populates the database with:
  • Sample app settings
  • Default email templates
  • Test user (in development)

Prisma Configuration

Connection Datasource

In prisma/schema.prisma:
datasource db {
  provider  = "postgresql"
  url       = env("PRISMA_DATABASE_URL")  // Pooled connection
  directUrl = env("DATABASE_URL")          // Direct connection for migrations
}

Client Configuration

generator client {
  provider = "prisma-client-js"
}

Using Prisma Client

In your application code:
// src/lib/db.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = global as unknown as { prisma: PrismaClient }

export const prisma =
  globalForPrisma.prisma ||
  new PrismaClient({
    log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
  })

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
This prevents multiple Prisma Client instances in development (hot reload).

Database Migrations

Creating Migrations

# Create a new migration
npx prisma migrate dev --name add_custom_metrics

# Apply migrations in production
npx prisma migrate deploy

Migration Strategy

Use prisma migrate dev to:
  • Create migration files
  • Apply to development database
  • Regenerate Prisma Client
Use prisma migrate deploy to:
  • Apply pending migrations
  • Skip migration generation
  • Safe for CI/CD pipelines
Use prisma migrate deploy with:
  • Automatic backups before migration
  • Rollback plan prepared
  • Monitor for issues post-migration

Rollback Strategy

Prisma doesn’t have built-in rollback. Options:
  1. Database snapshots: Take snapshot before migration
  2. Manual rollback: Write down migration SQL to revert
  3. Restore from backup: Use provider’s backup system

Performance Optimization

Indexes

Key indexes defined in schema:
model Report {
  // ... fields
  
  @@index([clientId])
  @@index([userId])
  @@index([status])
  @@index([createdAt])
}

model Client {
  // ... fields
  
  @@index([userId])
  @@index([domain])
}

Connection Pooling

Configure PgBouncer for optimal performance:
# Recommended connection string parameters
PRISMA_DATABASE_URL="postgresql://...?pgbouncer=true&connection_limit=1&pool_timeout=10"
Parameters:
  • pgbouncer=true: Enable PgBouncer mode
  • connection_limit=1: Limit connections per serverless function
  • pool_timeout=10: Timeout for acquiring connection (seconds)

Query Optimization

// Bad: Fetches all fields
const users = await prisma.user.findMany()

// Good: Only fetch needed fields
const users = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    companyName: true,
  },
})

Backup Strategy

Automated Backups

  • Automatic: Daily backups (retained 7 days)
  • Point-in-time recovery: Available on Pro plan
  • Configuration: No setup required

Manual Backup

# Export database to SQL file
pg_dump $DATABASE_URL > backup.sql

# Restore from backup
psql $DATABASE_URL < backup.sql

Monitoring & Maintenance

Database Monitoring

Monitor these metrics:
  • Connection count: Should stay below max connections
  • Query performance: Slow queries > 1 second
  • Database size: Monitor growth for cost planning
  • Error rate: Connection errors, timeouts

Prisma Logging

Enable query logging in development:
const prisma = new PrismaClient({
  log: [
    { level: 'query', emit: 'event' },
    { level: 'error', emit: 'stdout' },
    { level: 'warn', emit: 'stdout' },
  ],
})

prisma.$on('query', (e) => {
  console.log('Query: ' + e.query)
  console.log('Duration: ' + e.duration + 'ms')
})

Database Maintenance

Regular tasks:
  1. Vacuum: Reclaim storage (automatic in most providers)
  2. Analyze: Update query planner statistics
  3. Reindex: Rebuild indexes if performance degrades
-- Run manually if needed
VACUUM ANALYZE;
REINDEX DATABASE reportr;

Security Best Practices

1

Use SSL connections

Always use sslmode=require in connection string:
DATABASE_URL="postgresql://...?sslmode=require"
2

Rotate database credentials

Update database password periodically:
# Update in database provider dashboard
# Update environment variables in Vercel/deployment platform
# Redeploy application
3

Use read-only connections for analytics

Create separate database user with read-only access:
CREATE USER analytics_readonly WITH PASSWORD 'secure_password';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_readonly;
4

Encrypt sensitive data

Encrypt Google API tokens and other sensitive fields before storing:
import crypto from 'crypto'

function encrypt(text: string): string {
  const cipher = crypto.createCipher('aes-256-cbc', process.env.ENCRYPTION_KEY!)
  return cipher.update(text, 'utf8', 'hex') + cipher.final('hex')
}

Troubleshooting

Error: Can't reach database serverSolution:
  1. Enable connection pooling with PgBouncer
  2. Set connection_limit=1 in connection string
  3. Reduce concurrent function executions
Error: @prisma/client did not initialize yetSolution:
npm run postinstall
# or
npx prisma generate
Error: Migration failed to applySolution:
# Reset shadow database
npx prisma migrate reset

# Or resolve manually
npx prisma migrate resolve --applied "migration_name"
Issue: Queries taking > 1 secondSolution:
  1. Add indexes for frequently queried fields
  2. Use select to limit returned fields
  3. Implement pagination for large datasets
  4. Use database query analyzer

Cost Estimation

Monthly Database Costs

  • Neon: Free tier (0.5GB)
  • Supabase: Free tier (500MB)
  • Railway: ~$5-10/month
  • Vercel Postgres: ~$10-20/month

Next Steps

Environment Variables

Configure all required environment variables

Vercel Deployment

Deploy your application to Vercel

Build docs developers (and LLMs) love