1007 lines
20 KiB
Markdown

# 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`:
```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:
```typescript
// 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
```typescript
// 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
```typescript
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):**
```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:
```bash
# .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:
```typescript
// 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:
```typescript
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:
```typescript
// 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
```bash
# 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
```bash
# 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:
```typescript
// 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
```typescript
// View in console
localStorage.getItem('auth-token');
sessionStorage.getItem('cart');
```
### React DevTools
#### Installation
Install browser extension:
- [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools)
- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/)
#### 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:
```typescript
// 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](https://chrome.google.com/webstore/detail/redux-devtools)
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
```json
{
"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:
```bash
# 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:
```bash
# 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:**
```sql
-- 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
```sql
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:
```sql
-- 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:
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# Real-time stats
docker stats
# Specific container
docker stats api
# Format output
docker stats --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"
```
### Network Debugging
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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
```typescript
// 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
```typescript
// 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
```typescript
// 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](local-setup.md)
- **Docker:** [Docker Workflow](docker-workflow.md)
- **Testing:** [Testing Guide](testing.md)
- **Troubleshooting:** [Troubleshooting Guide](../troubleshooting.md)
## 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:**
```bash
# 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
```