In the realm of software development, we often view errors as unwelcome guests—interruptions in the perfect flow of our applications. But what if we could transform our relationship with errors? What if, instead of seeing them as failures, we could view them as opportunities for dialogue between our systems and their users?
In the world of software, errors are not bugs to be squashed but messages to be understood. Like a Zen master learning from each challenge, we should view errors as opportunities for:
Error messages trigger emotional responses in both users and developers:
For Users:
For Developers:
Every error tells a story. Consider these two approaches:
// A story without context:
throw new Error("Database connection failed");
// A story that guides and empowers:
return err({
code: 'DB_CONNECTION_ERROR',
context: {
attempted: retryCount,
lastError: lastException,
impact: 'user_data_sync'
},
userMessage: 'We're having trouble connecting to our services',
actionable: true,
suggestedActions: [
'Check your internet connection',
'Try again in a few minutes',
'Contact support if the issue persists'
]
});
Error handling involves ethical considerations:
Good error handling shapes system architecture:
How do we translate these philosophical principles into practical implementation? This is where modern tools and practices come into play.
Enter neverthrow
, a powerful TypeScript library with over 5,000 GitHub stars that transforms our philosophical approach into code. It helps us treat errors not as exceptional cases but as expected outcomes, bringing the principles of Railway Oriented Programming to TypeScript:
import { Result, ok, err } from 'neverthrow';
interface UserData {
id: string;
email: string;
}
interface ValidationError {
code: string;
message: string;
}
async function validateUser(data: unknown): Promise<Result<UserData, ValidationError>> {
try {
const validated = await userSchema.validate(data);
return ok({ id: validated.id, email: validated.email });
} catch (error) {
return err({
code: 'VALIDATION_ERROR',
message: 'Invalid user data provided'
});
}
}
This approach allows us to handle errors with grace and intention, making them part of our normal flow rather than exceptional cases.
To truly understand our errors’ stories, we need a way to collect and analyze them. Sentry provides this crucial narrative layer:
import * as Sentry from '@sentry/sveltekit';
export function logErrorWithTrace(
error: unknown,
context?: ErrorContext,
severity: ErrorSeverity = ErrorSeverity.ERROR
): { error: Error; traceId: string; message: string } {
const errorObj = error instanceof Error ?
error :
createApplicationError(formatErrorMessage(error));
const message = formatErrorMessage(error);
const traceId = Sentry.captureException(errorObj);
// Add context to Sentry
Sentry.withScope((scope) => {
scope.setLevel(severity);
if (context?.userId) scope.setUser({ id: context.userId });
});
return { error: errorObj, traceId, message };
}
The final piece is how we present these errors to our users. SvelteKit’s error page system provides the perfect canvas:
<script lang="ts">
import { page } from '$app/state';
import { logErrorWithTrace, ErrorSeverity } from '$lib/utilities/errorHandlers';
let errorTraceId = $state('');
let errorMessage = $state('');
$effect(() => {
if (page?.error) {
const { traceId, message } = logErrorWithTrace(page.error, {
component: 'ErrorPage',
operation: 'pageError',
data: { url: page.url.pathname }
}, ErrorSeverity.ERROR);
errorTraceId = traceId;
errorMessage = message;
}
});
</script>
<div class="error-container">
<div class="error-message">{errorMessage}</div>
<div class="error-reference">Reference ID: {errorTraceId}</div>F
</div>
Error handling is a journey from philosophy to implementation. Through the combination of thoughtful design principles and modern tools like SvelteKit, Sentry, and neverthrow, we can create error handling systems that don’t just catch errors—they tell stories, guide users, and help our applications evolve.
As we build more complex systems, remember that every error is an opportunity for dialogue. By approaching error handling with both technical precision and human empathy, we create applications that not only function better but also build trust with their users.