20 KiB

Debugging Guide

Comprehensive guide to debugging Changemaker Lite V2 applications, covering API, frontend, database, and Docker debugging techniques.

Overview

Effective debugging requires:

  • Understanding the tools (VSCode, Chrome DevTools, logs)
  • Systematic approach (reproduce, isolate, fix, verify)
  • Knowledge of common issues

This guide covers debugging strategies for all parts of V2.

API Debugging

VSCode Debugging

Launch Configuration

Create .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug API",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run", "dev"],
      "cwd": "${workspaceFolder}/api",
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**"],
      "envFile": "${workspaceFolder}/.env",
      "sourceMaps": true,
      "restart": true,
      "protocol": "inspector"
    },
    {
      "name": "Debug Media API",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run", "dev:media"],
      "cwd": "${workspaceFolder}/api",
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**"],
      "envFile": "${workspaceFolder}/.env"
    },
    {
      "name": "Attach to API (Docker)",
      "type": "node",
      "request": "attach",
      "port": 9229,
      "address": "localhost",
      "restart": true,
      "sourceMaps": true,
      "localRoot": "${workspaceFolder}/api",
      "remoteRoot": "/app",
      "skipFiles": ["<node_internals>/**"]
    }
  ]
}

Start Debugging

  1. Open VSCode
  2. Open Run and Debug panel (Cmd+Shift+D / Ctrl+Shift+D)
  3. Select "Debug API" configuration
  4. Press F5 to start debugging
  5. API starts with debugger attached

Set Breakpoints

Click line number gutter to set breakpoint:

// api/src/modules/auth/auth.service.ts
async login(email: string, password: string) {
  const user = await this.prisma.user.findUnique({ // ← Click here
    where: { email }
  });

  if (!user) {
    throw new Error('User not found'); // ← Or here
  }

  // Breakpoint pauses execution
  const isValid = await bcrypt.compare(password, user.password);

  return { user, tokens: this.generateTokens(user) };
}

Debug Features

Step Controls:

  • F10: Step over (next line)
  • F11: Step into (enter function)
  • Shift+F11: Step out (exit function)
  • F5: Continue (run to next breakpoint)

Inspect Variables:

  • Hover over variable to see value
  • Use "Variables" panel to see all local variables
  • Use "Watch" panel to monitor specific expressions

Debug Console:

  • Evaluate expressions while paused
  • Call functions with current scope
// In debug console (while paused)
> user.email
'john@example.com'

> bcrypt.compare('test', user.password)
Promise { <pending> }

> await bcrypt.compare('test', user.password)
false

Call Stack:

  • See function call hierarchy
  • Click stack frame to jump to code
  • Useful for understanding execution flow

Logging (Winston)

Using Logger

import { logger } from '../../utils/logger';

// Info level
logger.info('User logged in', { userId: user.id, email: user.email });

// Error level
logger.error('Failed to create user', {
  error: error.message,
  stack: error.stack,
  email
});

// Warn level
logger.warn('Deprecated endpoint accessed', { endpoint: req.path });

// Debug level (only in development)
logger.debug('Processing request', {
  method: req.method,
  path: req.path,
  query: req.query
});

Log Output

Development (console):

[2026-02-13 10:30:45] INFO: User logged in {"userId":1,"email":"john@example.com"}
[2026-02-13 10:30:46] ERROR: Failed to create user {"error":"Email already exists","email":"john@example.com"}

Production (JSON):

{"level":"info","message":"User logged in","userId":1,"email":"john@example.com","timestamp":"2026-02-13T10:30:45.123Z"}
{"level":"error","message":"Failed to create user","error":"Email already exists","email":"john@example.com","timestamp":"2026-02-13T10:30:46.456Z"}

Log Levels

Set log level via environment:

# .env
LOG_LEVEL=debug  # dev: debug, info, warn, error
LOG_LEVEL=info   # prod: info, warn, error

Database Query Logging

Prisma Query Logging

Enable in Prisma Client:

// api/src/config/prisma.ts
const prisma = new PrismaClient({
  log: [
    { emit: 'event', level: 'query' },
    { emit: 'event', level: 'error' },
    { emit: 'event', level: 'warn' }
  ]
});

prisma.$on('query', (e) => {
  logger.debug('Prisma query', {
    query: e.query,
    params: e.params,
    duration: e.duration
  });
});

prisma.$on('error', (e) => {
  logger.error('Prisma error', { target: e.target, message: e.message });
});

Output:

[2026-02-13 10:30:45] DEBUG: Prisma query {
  "query": "SELECT * FROM users WHERE id = $1",
  "params": "[1]",
  "duration": 5
}

Slow Query Logging

Log slow queries:

prisma.$on('query', (e) => {
  if (e.duration > 100) { // > 100ms
    logger.warn('Slow query detected', {
      query: e.query,
      duration: e.duration,
      params: e.params
    });
  }
});

Network Debugging

Request Logging

Log all HTTP requests:

// api/src/middleware/logger.ts
import { Request, Response, NextFunction } from 'express';
import { logger } from '../utils/logger';

export function requestLogger(req: Request, res: Response, next: NextFunction) {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;

    logger.info('HTTP request', {
      method: req.method,
      path: req.path,
      status: res.statusCode,
      duration,
      ip: req.ip,
      userAgent: req.get('user-agent')
    });

    if (duration > 1000) {
      logger.warn('Slow request', {
        method: req.method,
        path: req.path,
        duration
      });
    }
  });

  next();
}

// In server.ts
app.use(requestLogger);

Testing with curl

# GET request
curl http://localhost:4000/api/users

# POST request with JSON
curl -X POST http://localhost:4000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@example.com","password":"Admin123!"}'

# With authentication
curl http://localhost:4000/api/users \
  -H "Authorization: Bearer <token>"

# Verbose output (see headers)
curl -v http://localhost:4000/api/users

# Save response to file
curl http://localhost:4000/api/users > users.json

Testing with HTTPie

# Install httpie
brew install httpie  # macOS
sudo apt install httpie  # Linux

# GET request
http localhost:4000/api/users

# POST request
http POST localhost:4000/api/auth/login \
  email=admin@example.com \
  password=Admin123!

# With authentication
http localhost:4000/api/users \
  Authorization:"Bearer <token>"

# Pretty JSON output
http --pretty=all localhost:4000/api/users

Frontend Debugging

Chrome DevTools

Opening DevTools

  • F12 or Cmd+Option+I (Mac) / Ctrl+Shift+I (Windows/Linux)

Console Tab

View console logs and errors:

// admin/src/pages/UsersPage.tsx
console.log('Users loaded', users);
console.error('Failed to fetch users', error);
console.warn('Deprecated API used');
console.table(users); // Display as table

// Conditional logging
if (import.meta.env.DEV) {
  console.log('Debug info', { users, loading });
}

Output:

Users loaded [{ id: 1, email: 'john@example.com' }, ...]

Sources Tab

Debug JavaScript/TypeScript:

  1. Open Sources tab
  2. Find file in file tree (webpack://./src/)
  3. Click line number to set breakpoint
  4. Interact with UI to trigger breakpoint
  5. Use step controls (same as VSCode)

Conditional Breakpoints:

  • Right-click line number
  • Select "Add conditional breakpoint"
  • Enter condition: user.id === 1
  • Pauses only when condition is true

Network Tab

Debug API calls:

  1. Open Network tab
  2. Filter by "Fetch/XHR"
  3. Interact with UI
  4. Click request to see:
    • Headers (request/response)
    • Payload (request body)
    • Preview (formatted response)
    • Response (raw response)
    • Timing (request duration)

Common Issues:

  • 404 Not Found: Check URL path
  • 401 Unauthorized: Check token/auth header
  • 500 Server Error: Check API logs
  • CORS Error: Check CORS_ORIGIN setting

Application Tab

Inspect storage:

  • Local Storage: See persisted auth tokens
  • Session Storage: See session data
  • Cookies: See cookies
  • Cache Storage: See cached resources
// View in console
localStorage.getItem('auth-token');
sessionStorage.getItem('cart');

React DevTools

Installation

Install browser extension:

Components Tab

Inspect React component tree:

  1. Open DevTools
  2. Go to "Components" tab
  3. Select component from tree
  4. View:
    • Props
    • State (hooks)
    • Context
    • Owner (parent component)

Edit Props/State:

  • Click value to edit
  • Change takes effect immediately
  • Useful for testing edge cases

Profiler Tab

Profile component renders:

  1. Go to "Profiler" tab
  2. Click "Record"
  3. Interact with UI
  4. Click "Stop"
  5. See:
    • Flame graph (render hierarchy)
    • Ranked chart (slowest components)
    • Component details (render duration)

Identify Performance Issues:

  • Components rendering too often
  • Slow component renders
  • Unnecessary re-renders

Zustand DevTools

Enable Redux DevTools

Already configured in stores:

// admin/src/stores/auth.store.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

export const useAuthStore = create<AuthState>()(
  devtools(
    (set, get) => ({
      user: null,
      isAuthenticated: false,
      setUser: (user) => set({ user, isAuthenticated: !!user }),
      logout: () => set({ user: null, isAuthenticated: false })
    }),
    { name: 'AuthStore' } // Name in DevTools
  )
);

Using Redux DevTools

  1. Install Redux DevTools extension
  2. Open DevTools
  3. Go to "Redux" tab
  4. Select store from dropdown (AuthStore, CanvassStore)
  5. View:
    • State tree
    • Action history
    • State diff

Features:

  • Time-travel debugging (jump to previous state)
  • Action replay
  • State export/import

VSCode Debugging (Frontend)

Launch Configuration

{
  "name": "Debug Admin (Chrome)",
  "type": "chrome",
  "request": "launch",
  "url": "http://localhost:3000",
  "webRoot": "${workspaceFolder}/admin/src",
  "sourceMapPathOverrides": {
    "webpack:///./*": "${webRoot}/*",
    "webpack:///src/*": "${webRoot}/*",
    "webpack:///*": "*"
  },
  "userDataDir": false
}

Start Debugging:

  1. Start Admin dev server: npm run dev
  2. Select "Debug Admin (Chrome)" in VSCode
  3. Press F5
  4. Chrome opens with debugger attached
  5. Set breakpoints in VSCode
  6. Breakpoints hit when code executes

Database Debugging

Prisma Studio

Visual database browser:

# Start Prisma Studio
cd api
npx prisma studio

Features:

  • Browse all tables
  • Filter and sort data
  • Edit records directly
  • Create new records
  • Delete records

Use Cases:

  • Inspect database state
  • Manual data fixes
  • Verify migrations
  • Test queries

PostgreSQL Shell

Direct database access:

# Connect to database
docker compose exec v2-postgres psql -U changemaker_v2 -d changemaker_v2_db

# List tables
\dt

# Describe table
\d users

# Run query
SELECT * FROM users WHERE role = 'SUPER_ADMIN';

# Count records
SELECT COUNT(*) FROM campaigns;

# Exit
\q

Common Queries:

-- Find user by email
SELECT * FROM users WHERE email = 'admin@example.com';

-- Count users by role
SELECT role, COUNT(*) FROM users GROUP BY role;

-- Recent campaigns
SELECT * FROM campaigns ORDER BY created_at DESC LIMIT 10;

-- Users without name
SELECT * FROM users WHERE name IS NULL;

-- Delete test data
DELETE FROM users WHERE email LIKE '%test%';

Query Analysis

Explain Query Plan

EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'admin@example.com';

Output:

Index Scan using users_email_key on users (cost=0.28..8.29 rows=1 width=...)
  Index Cond: (email = 'admin@example.com'::text)
  Planning Time: 0.123 ms
  Execution Time: 0.045 ms

Identify Issues:

  • Sequential scans (slow on large tables)
  • Missing indexes
  • Expensive joins

Slow Query Log

Enable slow query logging:

-- Set log threshold (100ms)
ALTER DATABASE changemaker_v2_db SET log_min_duration_statement = 100;

-- View slow queries in logs
docker compose logs v2-postgres | grep "duration:"

Docker Debugging

Container Logs

View container output:

# All services
docker compose logs -f

# Specific service
docker compose logs -f api

# Last 100 lines
docker compose logs --tail=100 api

# With timestamps
docker compose logs -t -f api

# Since specific time
docker compose logs --since 2024-01-01T10:00:00 api

Execute Commands in Container

# Shell access
docker compose exec api sh

# Run command
docker compose exec api npm run type-check

# Run as specific user
docker compose exec -u root api sh

# Non-interactive command
docker compose exec -T api npm run lint

Inspect Container

# Container details
docker inspect api

# Environment variables
docker inspect api | grep -A 20 "Env"

# Mounts
docker inspect api | grep -A 50 "Mounts"

# Network settings
docker inspect api | grep -A 20 "Networks"

# Resource limits
docker inspect api | grep -A 10 "Memory"

Container Stats

# Real-time stats
docker stats

# Specific container
docker stats api

# Format output
docker stats --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"

Network Debugging

# Test connectivity between containers
docker compose exec api ping v2-postgres
docker compose exec api ping redis

# Check listening ports
docker compose exec api netstat -tuln

# Test HTTP endpoint from inside container
docker compose exec api wget -O- http://localhost:4000/health

# DNS lookup
docker compose exec api nslookup v2-postgres

Common Issues

401 Unauthorized

Symptoms: API returns 401 for authenticated requests.

Causes:

  1. Token expired
  2. Invalid token
  3. Missing Authorization header
  4. Token format incorrect

Debug:

# Check token in browser DevTools
localStorage.getItem('auth-token')

# Test token with curl
curl http://localhost:4000/api/users \
  -H "Authorization: Bearer <token>" \
  -v

# Decode JWT (jwt.io)
# Check expiration (exp claim)

Fix:

  • Refresh token
  • Re-login
  • Check token format (Bearer prefix)

500 Internal Server Error

Symptoms: API returns 500 error.

Causes:

  1. Unhandled exception
  2. Database error
  3. External service failure

Debug:

# Check API logs
docker compose logs -f api

# Look for error stack trace
docker compose logs api | grep -A 20 "Error:"

# Check database connection
docker compose exec api npx prisma db execute --stdin <<< "SELECT 1"

Fix:

  • Check error message in logs
  • Verify database is running
  • Check external service (Redis, SMTP, etc.)

CORS Errors

Symptoms: Browser blocks request with CORS error.

Causes:

  1. Incorrect CORS_ORIGIN setting
  2. Missing CORS headers
  3. Preflight OPTIONS request fails

Debug:

# Check CORS_ORIGIN in .env
grep CORS_ORIGIN .env

# Test with curl (bypasses CORS)
curl http://localhost:4000/api/users

# Check preflight request
curl -X OPTIONS http://localhost:4000/api/users \
  -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: GET" \
  -v

Fix:

  • Set CORS_ORIGIN=http://localhost:3000 in .env
  • Restart API: docker compose restart api

Database Connection Errors

Symptoms: API fails to connect to database.

Causes:

  1. PostgreSQL not running
  2. Incorrect DATABASE_URL
  3. Network issue

Debug:

# Check PostgreSQL is running
docker compose ps v2-postgres

# Check DATABASE_URL
docker compose exec api sh -c 'echo $DATABASE_URL'

# Test connection
docker compose exec v2-postgres psql -U changemaker_v2 -d changemaker_v2_db -c "SELECT 1"

# Check logs
docker compose logs v2-postgres

Fix:

  • Start PostgreSQL: docker compose up -d v2-postgres
  • Verify DATABASE_URL matches docker-compose.yml
  • Check password in .env

Redis Connection Errors

Symptoms: API fails to connect to Redis.

Causes:

  1. Redis not running
  2. Incorrect REDIS_URL
  3. Missing REDIS_PASSWORD

Debug:

# Check Redis is running
docker compose ps redis

# Test connection
docker compose exec redis redis-cli -a your_password ping

# Check Redis logs
docker compose logs redis

Fix:

  • Start Redis: docker compose up -d redis
  • Set REDIS_PASSWORD in .env
  • Update REDIS_URL with password

Hot Reload Not Working

Symptoms: Code changes don't trigger reload.

Causes:

  1. Volume mount missing
  2. File watcher not detecting changes
  3. Build cache issue

Debug:

# Check volume mounts
docker inspect api | grep -A 20 "Mounts"

# Test file sync
docker compose exec api ls -la /app/src

# Check for .dockerignore blocking sync
cat api/.dockerignore

Fix:

  • Verify volume mount in docker-compose.yml
  • Restart container: docker compose restart api
  • Clear cache: rm -rf api/dist && docker compose restart api

Debug Checklist

Systematic Debugging Approach

  1. Reproduce:

    • Can you consistently reproduce the issue?
    • What are the exact steps?
  2. Isolate:

    • Does it happen in all environments?
    • Is it specific to one user/data/scenario?
  3. Gather Information:

    • Check logs (API, frontend, database)
    • Check network requests (DevTools)
    • Check error messages
  4. Form Hypothesis:

    • What do you think is causing it?
    • What evidence supports this?
  5. Test Hypothesis:

    • Set breakpoints
    • Add logging
    • Test specific scenario
  6. Fix:

    • Make minimal change to fix issue
    • Don't fix multiple issues at once
  7. Verify:

    • Re-test original scenario
    • Test related functionality
    • Check for side effects
  8. Prevent:

    • Add tests to catch regression
    • Update documentation
    • Share learnings with team

Performance Debugging

API Response Time

// Measure endpoint performance
app.get('/users', async (req, res) => {
  const start = Date.now();

  const users = await prisma.user.findMany();

  const duration = Date.now() - start;
  logger.info('Users endpoint', { duration, count: users.length });

  res.json({ users });
});

Database Query Performance

// Log slow queries
prisma.$on('query', (e) => {
  if (e.duration > 100) {
    logger.warn('Slow query', {
      query: e.query,
      duration: e.duration,
      params: e.params
    });
  }
});

Frontend Render Performance

// Measure component render time
function UserList() {
  const renderStart = performance.now();

  useEffect(() => {
    const renderTime = performance.now() - renderStart;
    if (renderTime > 16) { // > 1 frame (60fps)
      console.warn('Slow render', { component: 'UserList', renderTime });
    }
  });

  return <div>...</div>;
}

Summary

You now know:

  • How to debug API with VSCode
  • How to use Winston logging effectively
  • How to debug frontend with Chrome DevTools
  • How to use React DevTools and Zustand DevTools
  • How to debug database with Prisma Studio and psql
  • How to debug Docker containers
  • Common issues and their solutions
  • Systematic debugging approach
  • Performance debugging techniques

Quick Start:

# API debugging
cd api && npm run dev  # Start with debugger
# Set breakpoints in VSCode, press F5

# Frontend debugging
# Open Chrome DevTools (F12)
# Network tab for API calls, Console for logs

# Database debugging
npx prisma studio  # Visual browser
docker compose exec v2-postgres psql -U changemaker_v2 -d changemaker_v2_db

# Logs
docker compose logs -f api admin