streamflow/docs/ERROR_HANDLING_QUICK_REFERENCE.md

289 lines
6.8 KiB
Markdown
Raw Normal View History

# Error Handling Quick Reference
## Backend
### Import Error Handler
```javascript
const { ErrorResponses, asyncHandler, sanitizeError } = require('../utils/errorHandler');
const logger = require('../utils/logger');
```
### Standard Route Pattern
```javascript
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
```javascript
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
```javascript
// 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
```javascript
// ❌ 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
```javascript
import { useErrorNotification } from '../components/ErrorNotificationProvider';
import { getErrorMessage, isAuthError } from '../utils/errorHandler';
```
### Component Pattern
```javascript
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
```javascript
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
```javascript
import { retryRequest } from '../utils/errorHandler';
const data = await retryRequest(
() => api.get('/endpoint'),
{ maxRetries: 3, retryDelay: 1000 }
);
```
### Custom Error Messages
```javascript
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)
```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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```bash
# 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
```javascript
// 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`