streamflow/docs/ERROR_HANDLING_QUICK_REFERENCE.md
2025-12-17 00:42:43 +00:00

6.8 KiB

Error Handling Quick Reference

Backend

Import Error Handler

const { ErrorResponses, asyncHandler, sanitizeError } = require('../utils/errorHandler');
const logger = require('../utils/logger');

Standard Route Pattern

router.get('/resource/:id', asyncHandler(async (req, res) => {
  const resource = await findResource(req.params.id);
  
  if (!resource) {
    throw ErrorResponses.notFound('Resource not found');
  }
  
  res.json(resource);
}));

Error Types

ErrorResponses.badRequest('Invalid input')           // 400
ErrorResponses.unauthorized('Auth required')          // 401
ErrorResponses.forbidden('No permission')             // 403
ErrorResponses.notFound('Not found')                  // 404
ErrorResponses.conflict('Already exists')             // 409
ErrorResponses.unprocessable('Cannot process')        // 422
ErrorResponses.tooManyRequests('Rate limited')        // 429
ErrorResponses.internal('Server error')               // 500
ErrorResponses.serviceUnavailable('Unavailable')      // 503

Logging

// Standard logging
logger.info('Operation completed', { userId, action });
logger.error('Operation failed', { error, context });
logger.warn('Unusual activity', { details });

// Security logging
logger.security('login_attempt', { userId, ip, success: true });

// Performance logging
const start = Date.now();
await operation();
logger.performance('operation_name', Date.now() - start, { details });

DO NOT

// ❌ Bad - Exposes stack trace
res.status(500).json({ error: error.message });

// ❌ Bad - Exposes file path
console.error('Failed:', error.stack);

// ❌ Bad - Exposes database details
res.json({ error: `Database error: ${err.code}` });

// ✅ Good - Sanitized
throw ErrorResponses.internal('Operation failed');

Frontend

Import Error Handler

import { useErrorNotification } from '../components/ErrorNotificationProvider';
import { getErrorMessage, isAuthError } from '../utils/errorHandler';

Component Pattern

function MyComponent() {
  const { showError, showSuccess } = useErrorNotification();
  
  const handleAction = async () => {
    try {
      const response = await api.post('/endpoint', data);
      showSuccess('Operation successful');
      return response.data;
    } catch (error) {
      showError(error);
      // Or with custom message:
      showError(error, { 
        defaultMessage: 'Custom error message',
        title: 'Operation Failed'
      });
    }
  };
}

Error Checking

import { isAuthError, isPermissionError, isValidationError } from '../utils/errorHandler';

try {
  await api.post('/endpoint', data);
} catch (error) {
  if (isAuthError(error)) {
    // Handle auth error
  } else if (isPermissionError(error)) {
    // Handle permission error
  } else {
    // Handle other errors
  }
}

Retry Pattern

import { retryRequest } from '../utils/errorHandler';

const data = await retryRequest(
  () => api.get('/endpoint'),
  { maxRetries: 3, retryDelay: 1000 }
);

Custom Error Messages

const { showError, showWarning, showInfo } = useErrorNotification();

// Error notification
showError(error, { 
  defaultMessage: 'Failed to load data',
  duration: 6000 
});

// Warning notification
showWarning('You are offline', { 
  title: 'Connection Lost',
  duration: 5000 
});

// Info notification
showInfo('Saving in progress...', {
  duration: 3000
});

Translation Keys

English (en.json)

"errors": {
  "general": { "unexpected", "tryAgain", "contactSupport" },
  "network": { "title", "message", "timeout" },
  "auth": { "title", "required", "sessionExpired" },
  "permission": { "title", "message", "adminRequired" },
  "validation": { "title", "invalidInput" },
  "server": { "title", "unavailable" }
}

Romanian (ro.json)

All error keys have Romanian translations

Common Patterns

File Upload with Error Handling

// Backend
router.post('/upload', asyncHandler(async (req, res) => {
  if (!req.files || !req.files.file) {
    throw ErrorResponses.badRequest('No file uploaded');
  }
  
  const file = req.files.file;
  
  if (file.size > MAX_SIZE) {
    throw ErrorResponses.badRequest('File too large');
  }
  
  await saveFile(file);
  res.json({ success: true });
}));

// Frontend
const handleUpload = async (file) => {
  try {
    const formData = new FormData();
    formData.append('file', file);
    await api.post('/upload', formData);
    showSuccess('File uploaded successfully');
  } catch (error) {
    showError(error, { defaultMessage: 'Failed to upload file' });
  }
};

Database Query with Error Handling

// Backend
router.get('/users', asyncHandler(async (req, res) => {
  const users = await new Promise((resolve, reject) => {
    db.all('SELECT * FROM users', [], (err, rows) => {
      if (err) reject(err);
      else resolve(rows);
    });
  });
  
  res.json(users);
}));
// Errors are automatically caught and sanitized by asyncHandler

Form Validation

// Backend
const { body, validationResult } = require('express-validator');

router.post('/users', [
  body('email').isEmail(),
  body('username').isLength({ min: 3 })
], asyncHandler(async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    throw ErrorResponses.badRequest('Validation failed');
  }
  
  // Process valid data
}));

// Frontend
const handleSubmit = async (data) => {
  // Client-side validation
  if (!data.email) {
    showError({ response: { status: 400 } }, { 
      defaultMessage: 'Email is required' 
    });
    return;
  }
  
  try {
    await api.post('/users', data);
    showSuccess('User created');
  } catch (error) {
    showError(error);
  }
};

Testing

Test Error Responses

# Should return sanitized error
curl http://localhost:12345/api/nonexistent

# Should not expose stack traces or file paths
curl http://localhost:12345/api/users -H "Authorization: Bearer invalid"

# Check logs (internal details preserved)
docker exec streamflow tail -f /app/logs/error.log

Test Frontend Error Handling

// In browser console
try {
  await fetch('/api/nonexistent');
} catch (error) {
  console.log('Error handled:', error);
}
// Should show user-friendly message, not technical details

Security Checklist

  • Never send error.stack to client
  • Never expose file paths
  • Never expose database queries
  • Always use asyncHandler for async routes
  • Always use ErrorResponses for errors
  • Always sanitize user input
  • Log full details internally only
  • Use translations for user messages
  • Test in both development and production modes

Need Help?

See full documentation: docs/ERROR_HANDLING_SECURITY.md