Docs
Backend Structure

Backend Structure

Comprehensive overview of the backend architecture, API routes, database models, and server-side functionality.

Backend Structure

This document provides a detailed overview of the backend architecture, including API routes, database models, service layers, and server-side functionality that powers the YouTube Analyzer application.

Overview

The backend is built with Next.js 14 API Routes, providing a serverless architecture that scales automatically. The system uses PostgreSQL with Prisma ORM for data persistence, and integrates with multiple external services for YouTube data, AI processing, payments, and email delivery.

API Routes Architecture

Route Organization (app/api/)

The API routes are organized by feature and functionality:

app/api/
├── analysis/               # Manual analysis endpoints
│   ├── route.ts           # POST: Create new analysis
│   ├── [id]/              # Dynamic analysis management
│   │   └── route.ts       # GET: Fetch analysis, DELETE: Cancel analysis
│   ├── cancel/            # Cancel stuck analyses
│   └── check/             # Polling for new records
├── auto-analysis/          # Automated analysis management
│   ├── route.ts           # POST: Create, GET: List auto-analyses
│   └── [id]/              # Individual auto-analysis management
│       └── route.ts       # PATCH: Update, DELETE: Remove
├── dashboard/              # Dashboard data endpoints
│   ├── recent/            # Recent analyses (limit 5)
│   └── stats/             # Dashboard statistics
├── user/                   # User management
│   └── route.ts           # DELETE: User deletion
├── webhooks/               # External service webhooks
│   └── stripe/            # Stripe payment webhooks
├── cron/                   # Scheduled task endpoints
│   └── auto-analysis/     # Auto-analysis cron job
├── share-analysis/         # Analysis sharing via email
├── update-analysis/        # Analysis result updates
└── auth/                   # Authentication endpoints
    └── [...nextauth]/      # Auth.js authentication

Core API Endpoints

1. Analysis Management (/api/analysis/)

POST /api/analysis - Create New Analysis

interface AnalysisRequest {
  channelUrl: string;
  videoCount: number;
}
 
interface AnalysisResponse {
  success: boolean;
  analysisId?: string;
  error?: string;
}

Features:

  • Credit validation and reservation
  • Channel URL parsing and validation
  • Integration with analysis processing pipeline
  • Real-time credit tracking

Implementation:

export async function POST(req: NextRequest) {
  const user = await getCurrentUser();
  if (!user?.id) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
 
  const { channelUrl, videoCount } = await req.json();
  const cost = CREDIT_COSTS[videoCount] || videoCount;
 
  // Validate user credits
  const dbUser = await prisma.user.findUnique({ where: { id: user.id } });
  if (!dbUser || dbUser.credits < cost) {
    return NextResponse.json({ error: "Insufficient credits" }, { status: 400 });
  }
 
  // Process analysis and deduct credits
  // ... processing logic
}

GET /api/analysis/[id] - Fetch Analysis Results

interface AnalysisWithVideos {
  id: string;
  channelUrl: string;
  htmlResult: string;
  status: 'processing' | 'completed' | 'failed';
  videos: VideoData[];
  createdAt: string;
}

2. Auto-Analysis Management (/api/auto-analysis/)

POST /api/auto-analysis - Create Auto-Analysis Configuration

interface AutoAnalysisRequest {
  channelId: string;
  channelName: string;
  channelUrl: string;
  triggerType: 'per_video' | 'batch_3' | 'batch_5';
}

Features:

  • Subscription plan validation (paid plans only)
  • Duplicate prevention per user/channel
  • Configurable trigger conditions

GET /api/auto-analysis - List User's Auto-Analyses

interface AutoAnalysisResponse {
  autoAnalyses: Array<{
    id: string;
    channelName: string;
    triggerType: string;
    isActive: boolean;
    analysisCount: number;
    lastRunAt?: string;
  }>;
}

PATCH /api/auto-analysis/[id] - Update Configuration

  • Toggle active/inactive status
  • Change trigger type
  • Update configuration parameters

3. Dashboard Data (/api/dashboard/)

GET /api/dashboard/recent - Recent Analyses

interface RecentAnalysesResponse {
  recentAnalyses: Array<{
    id: string;
    channel_name: string;
    channelUrl: string;
    status: string;
    createdAt: string;
    video_thumbnail?: string;
  }>;
}

GET /api/dashboard/stats - Dashboard Statistics

interface DashboardStats {
  monthlyAnalyses: number;
  processingCount: number;
  apiCreditsRemaining: number;
  hasActiveSubscription: boolean;
}

4. YouTube Data Processing (/api/youtube/process)

This is the core endpoint that handles the entire analysis workflow:

interface YouTubeProcessRequest {
  channel: string;
  count: number;
  analysisType?: string;
}
 
interface YouTubeProcessResponse {
  success: boolean;
  analysisId: string;
  status: 'processing' | 'completed';
}

Process Flow:

  1. Credit Validation: Check and reserve user credits
  2. Channel Resolution: Parse and validate YouTube channel
  3. Data Collection: Fetch videos, transcripts, and metadata
  4. AI Processing: Generate analysis using OpenAI
  5. Data Storage: Save results and update database
  6. Credit Deduction: Deduct credits only after successful completion

5. Webhook Handlers (/api/webhooks/)

POST /api/webhooks/stripe - Stripe Payment Webhooks

interface StripeWebhookEvents {
  'checkout.session.completed': PaymentSuccessHandler;
  'invoice.payment_succeeded': SubscriptionRenewalHandler;
  'customer.subscription.updated': SubscriptionChangeHandler;
}

Features:

  • Secure webhook signature validation
  • Subscription status updates
  • Credit replenishment for paid plans
  • Customer data synchronization

Admin Tools & Prompt Management

The backend includes dedicated logic and API endpoints for admin-only prompt management:

  • All prompt management endpoints are under /app/api/prompts/ and handled in lib/prompts.ts.
  • Admins can create, edit, and version prompt templates used by the AI analysis workflow.
  • All changes are versioned, auditable, and restricted to users with the ADMIN role.

See the Admin Tools documentation for a full technical breakdown of backend prompt management and other admin features.

Service Layer Architecture

Core Services (lib/)

1. Database Service (lib/db.ts)

// Prisma client with connection pooling
export const prisma: PrismaClient;
 
// Connection management for development vs production
if (process.env.NODE_ENV === "production") {
  prisma = new PrismaClient();
} else {
  // Development: use global cache to prevent connection exhaustion
  if (!global.cachedPrisma) {
    global.cachedPrisma = new PrismaClient();
  }
  prisma = global.cachedPrisma;
}

2. YouTube Integration (lib/youtube.ts)

interface YouTubeVideo {
  id: string;
  publishedAt: string;
  title: string;
  description: string;
  thumbnail: string;
  duration: string;
  viewCount: number;
  likeCount: number;
}
 
// Core YouTube API functions
export async function getChannelVideos(
  channelId: string, 
  maxResults: number
): Promise<YouTubeVideo[]>;
 
export async function getVideoTranscript(
  videoId: string
): Promise<string>;
 
export async function analyzeCommentsSentiment(
  videoId: string
): Promise<SentimentAnalysis>;

Features:

  • YouTube Data API v3 integration
  • Automatic transcript extraction
  • Comment sentiment analysis
  • Rate limiting and error handling
  • Mock data support for testing

3. OpenAI Integration (lib/openai-streaming.ts)

interface AnalysisPrompt {
  channelName: string;
  videoCount: number;
  performanceData: string;
  combinedTranscripts: string;
  videoLinksWithData: string;
}
 
export async function generateAnalysis(
  prompt: AnalysisPrompt,
  analysisType: string
): Promise<string>;
 
export async function streamAnalysisToUser(
  prompt: AnalysisPrompt,
  onChunk: (chunk: string) => void
): Promise<void>;

Features:

  • GPT-4 powered content analysis
  • Streaming responses for real-time feedback
  • Multiple analysis types with different prompts
  • Token optimization and cost management

4. Email Service (lib/email.ts)

interface EmailTemplate {
  to: string;
  subject: string;
  react: ReactElement;
}
 
export async function sendAnalysisResultEmail(
  analysis: Analysis,
  userEmail: string
): Promise<void>;
 
export async function sendWelcomeEmail(
  user: User
): Promise<void>;

Integration with Resend and React Email:

  • HTML email templates using React components
  • Transactional email delivery
  • Email tracking and analytics
  • Template management system

5. Stripe Integration (lib/stripe.ts)

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export async function createCheckoutSession(
  userId: string,
  priceId: string
): Promise<Stripe.Checkout.Session>;
 
export async function createCustomerPortalSession(
  customerId: string
): Promise<Stripe.BillingPortal.Session>;

6. Subscription Management (lib/subscription.ts)

interface SubscriptionPlan {
  name: string;
  credits: number;
  isPaid: boolean;
  features: string[];
}
 
export async function getUserSubscriptionPlan(
  userId: string
): Promise<SubscriptionPlan>;
 
export async function updateUserCredits(
  userId: string,
  credits: number
): Promise<void>;

7. Subscription Features (lib/subscription-features.ts)

Centralized feature access system with admin bypasses:

// Core feature checking functions
export function canUseAutomation(
  userPlan: UserPlan | null, 
  isAdmin: boolean = false
): boolean;
 
export function canUseEmailNotifications(
  userPlan: UserPlan | null,
  isAdmin: boolean = false
): boolean;
 
export function canUseAnalysisType(
  analysisType: string,
  userPlan: UserPlan | null,
  isAdmin: boolean = false
): boolean;
 
export function hasEnoughCredits(
  analysisType: string,
  userPlan: UserPlan | null,
  isAdmin: boolean = false
): boolean;

Key Features:

  • Admin bypass for all feature restrictions
  • Type-safe integration with config files
  • Consistent API across frontend and backend
  • Single source of truth for subscription logic

Database Models and Relationships

Core Models (prisma/schema.prisma)

1. User Model

model User {
  id                    String    @id @default(cuid())
  name                  String?
  email                 String    @unique
  role                  UserRole  @default(USER)
  credits               Int       @default(3)
  creditsUsed           Int       @default(0)
  stripeCustomerId      String?   @unique
  stripeSubscriptionId  String?   @unique
  stripePriceId         String?
  stripeCurrentPeriodEnd DateTime?
  createdAt             DateTime  @default(now())
  updatedAt             DateTime  @updatedAt
 
  // Relationships
  accounts      Account[]
  sessions      Session[]
  analyses      Analysis[]
  autoAnalyses  AutoAnalysis[]
}

2. Analysis Model

model Analysis {
  id              String   @id @default(cuid())
  userId          String
  channelUrl      String
  channelId       String?
  channel_name    String?
  channel_thumbnail String?
  videoCount      Int
  video_count     Int?
  htmlResult      String?  @db.Text
  status          String   @default("processing")
  error_message   String?
  summary_preview String?
  triggerType     String?  // "manual", "per_video", "batch_3", "batch_5"
  autoAnalysisId  String?
  analysisType    String   @default("standard-summary")
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
 
  // Relationships
  user         User          @relation(fields: [userId], references: [id], onDelete: Cascade)
  autoAnalysis AutoAnalysis? @relation(fields: [autoAnalysisId], references: [id])
  videos       Video[]
}

3. AutoAnalysis Model

model AutoAnalysis {
  id                   String   @id @default(cuid())
  userId               String
  channelId            String
  channelName          String
  channelUrl           String
  triggerType          String   // "per_video", "batch_3", "batch_5"
  isActive             Boolean  @default(true)
  lastAnalyzedVideoId  String?
  lastRunAt            DateTime?
  totalRuns            Int      @default(0)
  createdAt            DateTime @default(now())
  updatedAt            DateTime @updatedAt
 
  // Relationships
  user      User       @relation(fields: [userId], references: [id], onDelete: Cascade)
  analyses  Analysis[]
 
  @@unique([userId, channelId])
}

4. Video Model

model Video {
  id          String   @id @default(cuid())
  analysisId  String
  youtubeId   String
  title       String
  description String?  @db.Text
  thumbnail   String?
  duration    String?
  viewCount   Int?
  likeCount   Int?
  commentCount Int?
  uploadDate  DateTime?
  channelName String?
  transcript  String?  @db.Text
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
 
  // Relationships
  analysis Analysis @relation(fields: [analysisId], references: [id], onDelete: Cascade)
 
  @@index([analysisId])
}

Database Relationships

User (1) ──────── (N) Analysis
  │                     │
  │                     │
  └── (N) AutoAnalysis ─┘
              │
              └── (N) Analysis
                      │
                      └── (N) Video

Authentication and Authorization

Auth.js v5 Configuration (auth.ts)

export const { handlers: { GET, POST }, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  session: { strategy: "jwt" },
  pages: {
    signIn: "/login",
  },
  callbacks: {
    async session({ token, session }) {
      if (token) {
        session.user.id = token.id;
        session.user.name = token.name;
        session.user.email = token.email;
        session.user.image = token.picture;
        session.user.role = token.role;
      }
      return session;
    },
    async jwt({ token }) {
      if (!token.sub) return token;
      
      const existingUser = await getUserById(token.sub);
      if (!existingUser) return token;
      
      token.name = existingUser.name;
      token.email = existingUser.email;
      token.picture = existingUser.image;
      token.role = existingUser.role;
      
      return token;
    },
  },
  ...authConfig,
});

Session Management (lib/session.ts)

export async function getCurrentUser(): Promise<User | null> {
  const session = await auth();
  return session?.user ?? null;
}
 
export async function requireAuth(): Promise<User> {
  const user = await getCurrentUser();
  if (!user) {
    throw new Error("Authentication required");
  }
  return user;
}

Role-Based Access Control

enum UserRole {
  USER = "USER",
  ADMIN = "ADMIN"
}
 
// Middleware for role checking
export function requireRole(role: UserRole) {
  return async (req: NextRequest) => {
    const user = await getCurrentUser();
    if (!user || user.role !== role) {
      return NextResponse.json({ error: "Forbidden" }, { status: 403 });
    }
  };
}

Background Jobs and Automation

Cron Job System (/api/cron/auto-analysis)

export async function GET() {
  // Vercel Cron: runs every 30 minutes
  const results = await runAutoAnalysisCron();
  return NextResponse.json({ results });
}
 
async function runAutoAnalysisCron(): Promise<CronResult[]> {
  const autoAnalyses = await prisma.autoAnalysis.findMany({
    where: { isActive: true },
    include: { user: true },
  });
 
  for (const aa of autoAnalyses) {
    // 1. Get latest videos from YouTube
    const videos = await getChannelVideos(aa.channelId, 10);
    
    // 2. Find new videos since last analysis
    const newVideos = findNewVideos(videos, aa.lastAnalyzedVideoId);
    
    // 3. Check trigger conditions
    if (shouldTriggerAnalysis(newVideos, aa.triggerType)) {
      // 4. Validate user credits
      if (hasEnoughCredits(aa.user, newVideos.length)) {
        // 5. Trigger analysis
        await triggerAutoAnalysis(aa, newVideos);
      }
    }
  }
}

Analysis Processing Pipeline

1. Data Collection Phase

async function collectAnalysisData(channelId: string, videoCount: number) {
  // Parallel data fetching for performance
  const [channelInfo, videos] = await Promise.all([
    getChannelInfo(channelId),
    getChannelVideos(channelId, videoCount)
  ]);
 
  // Fetch video transcripts in parallel
  const transcripts = await Promise.all(
    videos.map(video => getVideoTranscript(video.id))
  );
 
  return { channelInfo, videos, transcripts };
}

2. AI Processing Phase

async function processWithAI(analysisData: AnalysisData, analysisType: string) {
  const prompt = buildAnalysisPrompt(analysisData, analysisType);
  
  if (analysisType === 'channel-deep-dive') {
    // Include sentiment analysis for premium tier
    const sentimentData = await analyzeSentiment(analysisData.videos);
    prompt.sentimentData = sentimentData;
  }
 
  return await generateAnalysis(prompt, analysisType);
}

3. Result Storage Phase

async function storeAnalysisResults(
  analysisId: string,
  htmlResult: string,
  videoData: VideoData[]
) {
  await prisma.$transaction(async (tx) => {
    // Update analysis with results
    await tx.analysis.update({
      where: { id: analysisId },
      data: { 
        htmlResult, 
        status: 'completed',
        summary_preview: extractPreview(htmlResult)
      }
    });
 
    // Store video data
    await tx.video.createMany({
      data: videoData.map(video => ({
        analysisId,
        ...video
      }))
    });
 
    // Deduct credits after successful completion
    await tx.user.update({
      where: { id: userId },
      data: { 
        credits: { decrement: creditCost },
        creditsUsed: { increment: creditCost }
      }
    });
  });
}

Error Handling and Logging

Global Error Handling

class AnalysisError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode: number = 500
  ) {
    super(message);
    this.name = 'AnalysisError';
  }
}
 
export function handleApiError(error: unknown): NextResponse {
  console.error('API Error:', error);
 
  if (error instanceof AnalysisError) {
    return NextResponse.json(
      { error: error.message, code: error.code },
      { status: error.statusCode }
    );
  }
 
  // Log unexpected errors for monitoring
  console.error('Unexpected error:', error);
  
  return NextResponse.json(
    { error: 'Internal server error' },
    { status: 500 }
  );
}

Request Validation

import { z } from 'zod';
 
const analysisRequestSchema = z.object({
  channelUrl: z.string().url(),
  videoCount: z.number().min(1).max(50),
  analysisType: z.enum(['standard-summary', 'executive-summary', 'channel-deep-dive']).optional()
});
 
export function validateAnalysisRequest(data: unknown) {
  return analysisRequestSchema.parse(data);
}

Performance Optimization

Database Query Optimization

// Efficient analysis fetching with proper includes
export async function getAnalysisWithVideos(analysisId: string, userId: string) {
  return await prisma.analysis.findUnique({
    where: { id: analysisId },
    include: {
      videos: {
        select: {
          id: true,
          youtubeId: true,
          title: true,
          thumbnail: true,
          viewCount: true,
          likeCount: true,
          duration: true,
          uploadDate: true,
        },
        orderBy: { uploadDate: 'desc' }
      }
    }
  });
}
 
// Paginated analysis history
export async function getUserAnalysisHistory(
  userId: string, 
  page: number = 1, 
  limit: number = 10
) {
  const skip = (page - 1) * limit;
  
  return await prisma.analysis.findMany({
    where: { userId },
    orderBy: { createdAt: 'desc' },
    skip,
    take: limit,
    select: {
      id: true,
      channelUrl: true,
      channel_name: true,
      status: true,
      createdAt: true,
      summary_preview: true,
    }
  });
}

Caching Strategy

// Redis-like caching for YouTube API responses
const cache = new Map<string, { data: any; expires: number }>();
 
export async function getCachedChannelVideos(channelId: string, maxResults: number) {
  const cacheKey = `videos:${channelId}:${maxResults}`;
  const cached = cache.get(cacheKey);
  
  if (cached && cached.expires > Date.now()) {
    return cached.data;
  }
  
  const videos = await getChannelVideos(channelId, maxResults);
  
  // Cache for 30 minutes
  cache.set(cacheKey, {
    data: videos,
    expires: Date.now() + 30 * 60 * 1000
  });
  
  return videos;
}

Security Measures

Input Sanitization

import DOMPurify from 'isomorphic-dompurify';
 
export function sanitizeHtmlContent(html: string): string {
  return DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'h1', 'h2', 'h3'],
    ALLOWED_ATTR: []
  });
}

Rate Limiting

const rateLimiter = new Map<string, { count: number; resetTime: number }>();
 
export function checkRateLimit(userId: string, limit: number = 10, windowMs: number = 60000): boolean {
  const now = Date.now();
  const userLimit = rateLimiter.get(userId);
  
  if (!userLimit || now > userLimit.resetTime) {
    rateLimiter.set(userId, { count: 1, resetTime: now + windowMs });
    return true;
  }
  
  if (userLimit.count >= limit) {
    return false;
  }
  
  userLimit.count++;
  return true;
}

Environment Variable Validation

import { z } from 'zod';
 
const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  YOUTUBE_API_KEY: z.string().min(1),
  OPENAI_API_KEY: z.string().min(1),
  STRIPE_SECRET_KEY: z.string().min(1),
  RESEND_API_KEY: z.string().min(1),
  NEXTAUTH_SECRET: z.string().min(1),
});
 
export const env = envSchema.parse(process.env);

Prompt Management Backend & API (2025-06-22)

The backend for prompt management provides CRUD, versioning, and test utilities for LLM prompt templates.

API Endpoints

  • GET /api/prompts — List all prompt templates (admin only)
  • GET /api/prompts/:id — Get a prompt and all its versions
  • POST /api/prompts — Create a new prompt template
  • PATCH /api/prompts/:id — Edit prompt or add a new version
  • POST /api/prompts/:id/test — Render prompt with test data (variables, transcript)

Implementation

  • All logic is in /app/api/prompts/ and lib/prompts.ts
  • Each prompt template is linked to an analysis type (e.g., standard-summary)
  • Versioning is handled at the database level; all edits create a new version
  • Test utilities allow admins to preview prompt output with sample data

Security

  • Only ADMIN users can access these endpoints (middleware enforced)
  • All changes are logged and versioned

Integration

  • The analysis workflow always uses the latest active prompt version for the selected type
  • Prompt templates are injected into the OpenAI request pipeline

Maintenance

  • All legacy/duplicate prompt admin code has been removed (2025-06-22)
  • Only /app/api/prompts/ and lib/prompts.ts are active for prompt management

This backend structure provides a robust, scalable foundation for the YouTube Analyzer application. The modular design, comprehensive error handling, and performance optimizations ensure the system can handle growth while maintaining reliability and user experience.