# 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:
1. **Auto-Discovery**: Automatically detects custom error handlers
2. **Error Formatting**: Clean, color-coded console output
3. **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:
```bash
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:
```typescript
// 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(, 404)
case 401:
return c.html(, 401)
case 403:
return c.html(, 403)
default:
return c.html(, status)
}
}
return c.html(, 500)
}
```
## 🖥️ Error Console Formatting
The `formatErrorForConsole()` utility provides clean, color-coded console
output:
```typescript
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
```typescript
// app/view/pages/errors/not_found.tsx
export function NotFoundPage() {
return (
404 - Page Not Found
)
}
```
### API Error Responses
For API endpoints, return JSON error responses:
```typescript
@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:
```typescript
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:
```typescript
export class ValidationError extends Error {
constructor(public fields: Record) {
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:
```typescript
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(, status)
}
```
### Development vs Production
Show different error details based on environment:
```typescript
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(
,
status,
)
}
```
## 📚 Best Practices
### 1. Use HTTP Exceptions
Prefer throwing `HTTPException` over returning error responses:
```typescript
// ✅ 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:
```typescript
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:
```typescript
// ✅ 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:
```typescript
// ✅ Good - Inline CSS
// ❌ Avoid - External framework
```
## 🔍 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:
```typescript
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).
## 📚 Next Steps
- [Learn about Validation](https://lockness.land/docs/validation)
- [Configure Logging](https://lockness.land/docs/logging)
- [Deploy to Production](https://lockness.land/docs/deployment)