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¶
- Open VSCode
- Open Run and Debug panel (Cmd+Shift+D / Ctrl+Shift+D)
- Select "Debug API" configuration
- Press F5 to start debugging
- 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:
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:
Sources Tab¶
Debug JavaScript/TypeScript:
- Open Sources tab
- Find file in file tree (webpack://./src/)
- Click line number to set breakpoint
- Interact with UI to trigger breakpoint
- 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:
- Open Network tab
- Filter by "Fetch/XHR"
- Interact with UI
- 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
React DevTools¶
Installation¶
Install browser extension: - Chrome - Firefox
Components Tab¶
Inspect React component tree:
- Open DevTools
- Go to "Components" tab
- Select component from tree
- 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:
- Go to "Profiler" tab
- Click "Record"
- Interact with UI
- Click "Stop"
- 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¶
- Install Redux DevTools extension
- Open DevTools
- Go to "Redux" tab
- Select store from dropdown (AuthStore, CanvassStore)
- 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:
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¶
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¶
- Reproduce:
- Can you consistently reproduce the issue?
-
What are the exact steps?
-
Isolate:
- Does it happen in all environments?
-
Is it specific to one user/data/scenario?
-
Gather Information:
- Check logs (API, frontend, database)
- Check network requests (DevTools)
-
Check error messages
-
Form Hypothesis:
- What do you think is causing it?
-
What evidence supports this?
-
Test Hypothesis:
- Set breakpoints
- Add logging
-
Test specific scenario
-
Fix:
- Make minimal change to fix issue
-
Don't fix multiple issues at once
-
Verify:
- Re-test original scenario
- Test related functionality
-
Check for side effects
-
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>;
}
Related Documentation¶
- Setup: Local Development Setup
- Docker: Docker Workflow
- Testing: Testing Guide
- Troubleshooting: Troubleshooting Guide
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