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
- 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:
# .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:
- 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
// View in console
localStorage.getItem('auth-token');
sessionStorage.getItem('cart');
React DevTools
Installation
Install browser extension:
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:
- Start Admin dev server:
npm run dev - Select "Debug Admin (Chrome)" in VSCode
- Press F5
- Chrome opens with debugger attached
- Set breakpoints in VSCode
- 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:
- Token expired
- Invalid token
- Missing Authorization header
- 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:
- Unhandled exception
- Database error
- 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:
- Incorrect CORS_ORIGIN setting
- Missing CORS headers
- 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:3000in .env - Restart API:
docker compose restart api
Database Connection Errors
Symptoms: API fails to connect to database.
Causes:
- PostgreSQL not running
- Incorrect DATABASE_URL
- 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:
- Redis not running
- Incorrect REDIS_URL
- 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:
- Volume mount missing
- File watcher not detecting changes
- 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