All files / src/app/api/health/db route.ts

95.58% Statements 130/136
82.6% Branches 19/23
100% Functions 1/1
95.58% Lines 130/136

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 1371x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 6x 6x 6x 6x 7x     7x       6x 6x 6x 6x 6x 6x 7x   7x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 30x 30x 30x 6x 6x 6x 6x 6x 7x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 7x 1x 7x 1x 1x 6x 6x 7x 7x 7x 7x 7x 7x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x  
export const dynamic = "force-dynamic";
 
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { getMetricsSummary } from '@/lib/database/metrics';
 
interface HealthCheckResult {
  status: 'healthy' | 'degraded' | 'unhealthy';
  timestamp: string;
  checks: {
    connection: {
      status: 'pass' | 'fail';
      latency: number;
      message?: string;
    };
    queryPerformance: {
      status: 'pass' | 'warn' | 'fail';
      avgQueryTime: number;
      slowQueryPercentage: number;
    };
    tableHealth: {
      status: 'pass' | 'warn' | 'fail';
      tables: { name: string; count: number; healthy: boolean }[];
    };
  };
}
 
/**
 * GET /api/health/db
 *
 * Dedicated database health check endpoint.
 * Returns detailed information about database connectivity and performance.
 */
export async function GET() {
  const result: HealthCheckResult = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    checks: {
      connection: {
        status: 'pass',
        latency: 0
      },
      queryPerformance: {
        status: 'pass',
        avgQueryTime: 0,
        slowQueryPercentage: 0
      },
      tableHealth: {
        status: 'pass',
        tables: []
      }
    }
  };
 
  try {
    // Check 1: Database Connection
    const connectionStart = performance.now();
    await prisma.$queryRaw`SELECT 1`;
    const connectionLatency = performance.now() - connectionStart;
 
    result.checks.connection.latency = Math.round(connectionLatency);
 
    if (connectionLatency > 500) {
      result.checks.connection.status = 'fail';
      result.checks.connection.message = 'Connection latency too high';
    } else if (connectionLatency > 100) {
      result.checks.connection.status = 'pass';
      result.checks.connection.message = 'Connection latency elevated';
    }
 
    // Check 2: Query Performance Metrics
    const metrics = getMetricsSummary();
    result.checks.queryPerformance.avgQueryTime = metrics.avgQueryTime;
    result.checks.queryPerformance.slowQueryPercentage = metrics.slowQueryPercentage;
 
    if (metrics.avgQueryTime > 100 || metrics.slowQueryPercentage > 10) {
      result.checks.queryPerformance.status = 'fail';
    } else if (metrics.avgQueryTime > 50 || metrics.slowQueryPercentage > 5) {
      result.checks.queryPerformance.status = 'warn';
    }
 
    // Check 3: Table Health (verify key tables are accessible)
    const tableChecks = await Promise.allSettled([
      prisma.product.count(),
      prisma.user.count(),
      prisma.order.count(),
      prisma.review.count(),
      prisma.category.count(),
    ]);
 
    const tableNames = ['products', 'users', 'orders', 'reviews', 'categories'];
    const tableResults = tableChecks.map((check, index) => ({
      name: tableNames[index],
      count: check.status === 'fulfilled' ? check.value : -1,
      healthy: check.status === 'fulfilled'
    }));
 
    result.checks.tableHealth.tables = tableResults;
 
    const unhealthyTables = tableResults.filter(t => !t.healthy);
    if (unhealthyTables.length > 0) {
      result.checks.tableHealth.status = 'fail';
    }
 
    // Determine overall status
    const statuses = [
      result.checks.connection.status,
      result.checks.queryPerformance.status,
      result.checks.tableHealth.status,
    ];
 
    if (statuses.includes('fail')) {
      result.status = 'unhealthy';
    } else if (statuses.includes('warn')) {
      result.status = 'degraded';
    }
 
    return NextResponse.json(result, {
      status: result.status === 'unhealthy' ? 503 : 200,
      headers: {
        'Cache-Control': 'no-store, no-cache, must-revalidate'
      }
    });
  } catch (error) {
    result.status = 'unhealthy';
    result.checks.connection.status = 'fail';
    result.checks.connection.message = error instanceof Error ? error.message : 'Unknown error';
 
    return NextResponse.json(result, {
      status: 503,
      headers: {
        'Cache-Control': 'no-store, no-cache, must-revalidate'
      }
    });
  }
}