Error Handling
Lockness provides a comprehensive error handling system with automatic discovery, elegant error pages, and clean console output.
🎯 Overview
The framework handles errors through three layers:
- Auto-Discovery: Automatically detects custom error handlers
- Error Formatting: Clean, color-coded console output
- Default Pages: Built-in error pages with no dependencies
🔍 Auto-Discovery
Lockness automatically discovers custom error handlers without requiring manual registration in your kernel. This eliminates boilerplate and follows the zero-configuration philosophy.
How it works:
When your application starts, @lockness/core checks for a custom error handler
at:
app/view/pages/errors/error_handler.tsx
If found, it's used automatically. If not, the framework uses its built-in default error handler.
Benefits:
- ✅ No imports needed in
kernel.tsx - ✅ No manual registration with
useErrorHandler() - ✅ Convention over configuration
- ✅ Automatic fallback to defaults
🎨 Creating Custom Error Pages
Generate custom error pages using the CLI:
deno task cli make:error-pages
This creates:
app/view/pages/errors/
├── error_handler.tsx # Main error handler (auto-discovered)
├── not_found.tsx # 404 page
├── unauthorized.tsx # 401 page
├── forbidden.tsx # 403 page
└── server_error.tsx # 500 page
Generated templates use:
- Inline CSS (no framework dependencies)
- System fonts (no external font loading)
- Responsive design
- Clean, professional styling
📝 Custom Error Handler
Example of a custom error handler:
// app/view/pages/errors/error_handler.tsx
import { Context, formatErrorForConsole, HTTPException } from '@lockness/core'
import { NotFoundPage } from './not_found.tsx'
import { UnauthorizedPage } from './unauthorized.tsx'
import { ForbiddenPage } from './forbidden.tsx'
import { ServerErrorPage } from './server_error.tsx'
export function errorHandler(error: Error, c: Context) {
const status = error instanceof HTTPException ? error.status : 500
const path = c.req.path
// Use centralized error formatter for clean console output
formatErrorForConsole(error, status, path, {
showStackTrace: status >= 500,
})
// Return appropriate error page
if (error instanceof HTTPException) {
switch (status) {
case 404:
return c.html(<NotFoundPage />, 404)
case 401:
return c.html(<UnauthorizedPage />, 401)
case 403:
return c.html(<ForbiddenPage />, 403)
default:
return c.html(<ServerErrorPage />, status)
}
}
return c.html(<ServerErrorPage />, 500)
}
🖥️ Error Console Formatting
The formatErrorForConsole() utility provides clean, color-coded console
output:
import { formatErrorForConsole } from '@lockness/core'
formatErrorForConsole(error, status, path, {
showStackTrace: status >= 500, // Show trace for server errors
})
Output Examples:
404 Not Found (minimal):
⚠️ 404 Not Found: /api/users/999
401 Unauthorized (simple):
🔒 401 Unauthorized: /admin/dashboard
500 Internal Server Error (detailed):
❌ 500 Internal Server Error: /api/users
Error: Database connection failed
at UserRepository.findAll (file:///app/repository/user_repository.ts:42:15)
at UserService.getUsers (file:///app/service/user_service.ts:18:30)
Benefits:
- ✅ Color-coded by severity (warning, error, critical)
- ✅ Minimal output for expected errors (404)
- ✅ Full stack traces for server errors (500+)
- ✅ Consistent formatting across the application
🎨 Custom Error Pages
404 Not Found Page
// app/view/pages/errors/not_found.tsx
export function NotFoundPage() {
return (
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta
name='viewport'
content='width=device-width, initial-scale=1.0'
/>
<title>404 - Page Not Found</title>
<style>
{`
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, sans-serif;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.container {
text-align: center;
color: white;
padding: 2rem;
}
h1 { font-size: 6rem; margin-bottom: 1rem; }
p { font-size: 1.5rem; margin-bottom: 2rem; }
a {
display: inline-block;
padding: 1rem 2rem;
background: white;
color: #667eea;
text-decoration: none;
border-radius: 0.5rem;
font-weight: 600;
}
`}
</style>
</head>
<body>
<div class='container'>
<h1>404</h1>
<p>Page not found</p>
<a href='/'>Go Home</a>
</div>
</body>
</html>
)
}
API Error Responses
For API endpoints, return JSON error responses:
@Controller('/api')
export class ApiController {
@Get('/users/:id')
async show(c: Context) {
const id = c.req.param('id')
const user = await this.userService.findById(id)
if (!user) {
return c.json({
success: false,
error: 'User not found',
code: 'USER_NOT_FOUND',
}, 404)
}
return c.json({ success: true, data: user })
}
}
🚀 Default Error Handler
If you don't create custom error pages, Lockness provides elegant default pages:
Features:
- Clean, responsive design
- Inline CSS (no external dependencies)
- System fonts (zero external requests)
- Appropriate HTTP status codes
- User-friendly error messages
Default pages include:
- 404 Not Found
- 401 Unauthorized
- 403 Forbidden
- 500 Internal Server Error
🎭 HTTP Exceptions
Throw HTTP exceptions from anywhere in your code:
import { HTTPException } from '@lockness/core'
// 404 Not Found
throw new HTTPException(404, { message: 'User not found' })
// 401 Unauthorized
throw new HTTPException(401, { message: 'Invalid credentials' })
// 403 Forbidden
throw new HTTPException(403, { message: 'Access denied' })
// 500 Internal Server Error
throw new HTTPException(500, { message: 'Database connection failed' })
🔧 Advanced Usage
Custom Error Types
Create custom error classes for domain-specific errors:
export class ValidationError extends Error {
constructor(public fields: Record<string, string[]>) {
super('Validation failed')
this.name = 'ValidationError'
}
}
// In error handler
if (error instanceof ValidationError) {
return c.json({
success: false,
errors: error.fields,
}, 422)
}
Error Logging
Integrate with external logging services:
import { formatErrorForConsole } from '@lockness/core'
export function errorHandler(error: Error, c: Context) {
const status = error instanceof HTTPException ? error.status : 500
// Console output
formatErrorForConsole(error, status, c.req.path)
// External logging (e.g., Sentry)
if (status >= 500) {
Sentry.captureException(error)
}
return c.html(<ServerErrorPage />, status)
}
Development vs Production
Show different error details based on environment:
export function errorHandler(error: Error, c: Context) {
const status = error instanceof HTTPException ? error.status : 500
const isDev = Deno.env.get('APP_ENV') === 'development'
formatErrorForConsole(error, status, c.req.path)
return c.html(
<ServerErrorPage
error={isDev ? error.message : 'Something went wrong'}
stack={isDev ? error.stack : undefined}
/>,
status,
)
}
📚 Best Practices
1. Use HTTP Exceptions
Prefer throwing HTTPException over returning error responses:
// ✅ Good
if (!user) {
throw new HTTPException(404, { message: 'User not found' })
}
// ❌ Avoid
if (!user) {
return c.json({ error: 'User not found' }, 404)
}
2. Consistent Error Format
Use a consistent format for API errors:
interface ErrorResponse {
success: false
error: string
code?: string
details?: unknown
}
3. Appropriate Status Codes
Use correct HTTP status codes:
- 404: Resource not found
- 401: Authentication required
- 403: Authenticated but not authorized
- 422: Validation failed
- 500: Server error
4. Clean Console Output
Let formatErrorForConsole() handle console logging:
// ✅ Good
formatErrorForConsole(error, status, path)
// ❌ Avoid
console.error('Error:', error)
console.error(error.stack)
5. Framework-Agnostic Templates
Keep error pages framework-agnostic with inline CSS:
// ✅ Good - Inline CSS
<style>{`body { font-family: sans-serif; }`}</style>
// ❌ Avoid - External framework
<link rel="stylesheet" href="/css/tailwind.css" />
🔍 Troubleshooting
Error Handler Not Found
Symptom: Custom error pages not displaying
Solution: Ensure file exists at correct path:
app/view/pages/errors/error_handler.tsx
Stack Traces in Production
Symptom: Full error details visible to users
Solution: Check showStackTrace option:
formatErrorForConsole(error, status, path, {
showStackTrace: status >= 500 && Deno.env.get('APP_ENV') === 'development',
})
CSS Not Loading on Error Pages
Symptom: Unstyled error pages
Solution: Use inline CSS in error page components (already done by default in generated templates).