Files
2025-11-29 18:47:58 +08:00

12 KiB

Database Configuration Command

You are helping the user configure database connections, optimize queries, and set up database-related infrastructure following Sngular's best practices.

Instructions

  1. Determine the task type:

    • Initial database setup and connection
    • Connection pool configuration
    • Query optimization
    • Migration setup
    • Backup strategy
    • Performance tuning
  2. Detect database and tools:

    • Database type (PostgreSQL, MySQL, MongoDB, etc.)
    • ORM/Query builder (TypeORM, Prisma, Sequelize, etc.)
    • Connection library
    • Current project structure
  3. Ask for specific needs:

    • Development, staging, or production environment
    • Connection pooling requirements
    • Read replicas needed
    • Caching strategy
    • Monitoring requirements

Implementation Tasks

1. Database Connection Setup

TypeORM Configuration

// src/config/database.ts
import { DataSource } from 'typeorm'
import { config } from 'dotenv'

config()

export const AppDataSource = new DataSource({
  type: 'postgres',
  host: process.env.DB_HOST || 'localhost',
  port: parseInt(process.env.DB_PORT || '5432'),
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,

  // Connection pool
  extra: {
    max: 20, // Maximum connections
    min: 5,  // Minimum connections
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 2000,
  },

  // Entities
  entities: ['src/entities/**/*.ts'],
  migrations: ['src/migrations/**/*.ts'],
  subscribers: ['src/subscribers/**/*.ts'],

  // Development settings
  synchronize: process.env.NODE_ENV === 'development',
  logging: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],

  // Connection retry
  retryAttempts: 10,
  retryDelay: 3000,

  // SSL for production
  ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
})

// Initialize connection
export const initializeDatabase = async () => {
  try {
    await AppDataSource.initialize()
    console.log('✅ Database connection established')
  } catch (error) {
    console.error('❌ Database connection failed:', error)
    process.exit(1)
  }
}

Prisma Configuration

// src/config/database.ts
import { PrismaClient } from '@prisma/client'

const prismaClientSingleton = () => {
  return new PrismaClient({
    log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
    datasources: {
      db: {
        url: process.env.DATABASE_URL,
      },
    },
  })
}

declare global {
  var prisma: undefined | ReturnType<typeof prismaClientSingleton>
}

export const prisma = globalThis.prisma ?? prismaClientSingleton()

if (process.env.NODE_ENV !== 'production') globalThis.prisma = prisma

// Graceful shutdown
export const disconnectDatabase = async () => {
  await prisma.$disconnect()
}

// Health check
export const checkDatabaseConnection = async () => {
  try {
    await prisma.$queryRaw`SELECT 1`
    return true
  } catch (error) {
    console.error('Database health check failed:', error)
    return false
  }
}

Mongoose (MongoDB) Configuration

// src/config/database.ts
import mongoose from 'mongoose'

const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp'

export const connectDatabase = async () => {
  try {
    await mongoose.connect(MONGODB_URI, {
      maxPoolSize: 10,
      minPoolSize: 5,
      socketTimeoutMS: 45000,
      serverSelectionTimeoutMS: 5000,
      family: 4, // Use IPv4
    })

    console.log('✅ MongoDB connected')

    // Connection events
    mongoose.connection.on('error', (err) => {
      console.error('MongoDB connection error:', err)
    })

    mongoose.connection.on('disconnected', () => {
      console.log('MongoDB disconnected')
    })

    // Graceful shutdown
    process.on('SIGINT', async () => {
      await mongoose.connection.close()
      process.exit(0)
    })
  } catch (error) {
    console.error('Failed to connect to MongoDB:', error)
    process.exit(1)
  }
}

2. Environment Variables

# .env
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=myapp_user
DB_PASSWORD=secure_password_here
DB_NAME=myapp_db

# Alternative: Full connection string
DATABASE_URL=postgresql://myapp_user:secure_password_here@localhost:5432/myapp_db

# MongoDB
MONGODB_URI=mongodb://localhost:27017/myapp

# Connection pool
DB_POOL_MIN=5
DB_POOL_MAX=20

# Production
NODE_ENV=production
DB_SSL=true
# .env.example (commit this to git)
DB_HOST=localhost
DB_PORT=5432
DB_USER=your_db_user
DB_PASSWORD=your_db_password
DB_NAME=your_db_name
DATABASE_URL=postgresql://user:password@host:port/database

3. Connection Pool Configuration

// Optimized pool settings by environment
export const getPoolConfig = () => {
  const env = process.env.NODE_ENV

  if (env === 'production') {
    return {
      max: 20,
      min: 10,
      idleTimeoutMillis: 30000,
      connectionTimeoutMillis: 2000,
    }
  } else if (env === 'staging') {
    return {
      max: 10,
      min: 5,
      idleTimeoutMillis: 30000,
      connectionTimeoutMillis: 2000,
    }
  } else {
    // development
    return {
      max: 5,
      min: 2,
      idleTimeoutMillis: 10000,
      connectionTimeoutMillis: 2000,
    }
  }
}

4. Query Optimization

// Bad: N+1 query problem
const users = await User.find()
for (const user of users) {
  const posts = await Post.find({ authorId: user.id }) // N queries
}

// Good: Eager loading
const users = await User.find({
  relations: ['posts'],
})

// Good: Join query
const users = await dataSource
  .createQueryBuilder(User, 'user')
  .leftJoinAndSelect('user.posts', 'post')
  .getMany()

// Prisma with includes
const users = await prisma.user.findMany({
  include: {
    posts: true,
  },
})
// Use indexes effectively
const users = await User.find({
  where: { email: 'test@example.com' }, // email column should be indexed
})

// Use select to fetch only needed fields
const users = await User.find({
  select: ['id', 'email', 'name'], // Don't fetch all columns
})

// Pagination with cursors (better than offset)
const users = await User.find({
  where: { id: MoreThan(lastSeenId) },
  take: 20,
  order: { id: 'ASC' },
})

5. Transactions

// TypeORM transaction
await AppDataSource.transaction(async (manager) => {
  const user = await manager.save(User, { email: 'test@example.com' })
  await manager.save(Profile, { userId: user.id, bio: 'Hello' })
  // Both saved or both rolled back
})

// Prisma transaction
await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({ data: { email: 'test@example.com' } })
  await tx.profile.create({ data: { userId: user.id, bio: 'Hello' } })
})

// Prisma sequential operations
await prisma.$transaction([
  prisma.user.create({ data: { email: 'test@example.com' } }),
  prisma.post.create({ data: { title: 'First post' } }),
])

6. Database Migrations

// Create migration script in package.json
{
  "scripts": {
    "migration:generate": "typeorm migration:generate -d src/config/database.ts src/migrations/Migration",
    "migration:run": "typeorm migration:run -d src/config/database.ts",
    "migration:revert": "typeorm migration:revert -d src/config/database.ts",
    "schema:sync": "typeorm schema:sync -d src/config/database.ts",
    "schema:drop": "typeorm schema:drop -d src/config/database.ts"
  }
}

// Prisma migrations
{
  "scripts": {
    "prisma:migrate:dev": "prisma migrate dev",
    "prisma:migrate:deploy": "prisma migrate deploy",
    "prisma:migrate:reset": "prisma migrate reset",
    "prisma:generate": "prisma generate",
    "prisma:studio": "prisma studio"
  }
}

7. Health Check Endpoint

// src/routes/health.ts
import { Request, Response } from 'express'
import { AppDataSource } from '../config/database'

export const healthCheck = async (req: Request, res: Response) => {
  try {
    // Check database connection
    await AppDataSource.query('SELECT 1')

    res.status(200).json({
      status: 'healthy',
      database: 'connected',
      timestamp: new Date().toISOString(),
    })
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      database: 'disconnected',
      error: error.message,
      timestamp: new Date().toISOString(),
    })
  }
}

8. Database Seeding

// src/seeds/seed.ts
import { AppDataSource } from '../config/database'
import { User } from '../entities/User'
import { Role } from '../entities/Role'

export const seedDatabase = async () => {
  await AppDataSource.initialize()

  // Create roles
  const adminRole = await Role.create({ name: 'admin' }).save()
  const userRole = await Role.create({ name: 'user' }).save()

  // Create users
  await User.create({
    email: 'admin@example.com',
    name: 'Admin User',
    role: adminRole,
  }).save()

  await User.create({
    email: 'user@example.com',
    name: 'Regular User',
    role: userRole,
  }).save()

  console.log('✅ Database seeded')

  await AppDataSource.destroy()
}

// Run: ts-node src/seeds/seed.ts
if (require.main === module) {
  seedDatabase().catch(console.error)
}

9. Query Logging and Monitoring

// Custom query logger
import { Logger, QueryRunner } from 'typeorm'

export class DatabaseLogger implements Logger {
  logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) {
    console.log('Query:', query)
    console.log('Parameters:', parameters)
  }

  logQueryError(error: string, query: string, parameters?: any[]) {
    console.error('Query Error:', error)
    console.error('Query:', query)
  }

  logQuerySlow(time: number, query: string, parameters?: any[]) {
    console.warn(`Slow query (${time}ms):`, query)
  }

  logSchemaBuild(message: string) {
    console.log('Schema Build:', message)
  }

  logMigration(message: string) {
    console.log('Migration:', message)
  }

  log(level: 'log' | 'info' | 'warn', message: any) {
    console.log(`[${level.toUpperCase()}]`, message)
  }
}

10. Database Backup Script

#!/bin/bash
# scripts/backup-db.sh

# Load environment variables
source .env

# Create backup directory
mkdir -p backups

# Generate timestamp
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")

# PostgreSQL backup
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > "backups/backup_${TIMESTAMP}.sql"

# Compress backup
gzip "backups/backup_${TIMESTAMP}.sql"

# Delete backups older than 30 days
find backups/ -name "*.gz" -mtime +30 -delete

echo "✅ Backup completed: backup_${TIMESTAMP}.sql.gz"

Best Practices

  1. Always use connection pooling - Reuse connections instead of creating new ones
  2. Use environment variables - Never hardcode credentials
  3. Implement health checks - Monitor database connectivity
  4. Use migrations - Never modify database schema manually
  5. Index appropriately - Index foreign keys and frequently queried columns
  6. Optimize queries - Use explain plans to identify slow queries
  7. Use transactions - For operations that must succeed or fail together
  8. Implement read replicas - For high-read applications
  9. Set up monitoring - Track query performance and connection pool metrics
  10. Regular backups - Automate database backups

Security Checklist

  • Use SSL/TLS for database connections in production
  • Store credentials in environment variables or secrets manager
  • Use least privilege principle for database users
  • Enable audit logging for sensitive operations
  • Implement connection timeout and retry logic
  • Validate and sanitize all inputs
  • Use parameterized queries (prevent SQL injection)
  • Regular security patches and updates
  • Implement IP whitelisting for database access
  • Enable database firewall rules

Ask the user: "What database configuration task would you like help with?"