Error Handling

Error Handling

VIEW

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:

text
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:

text
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(<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:

typescript
import { formatErrorForConsole } from '@lockness/core'

formatErrorForConsole(error, status, path, {
    showStackTrace: status >= 500, // Show trace for server errors
})

Output Examples:

404 Not Found (minimal):

text
⚠️ 404 Not Found: /api/users/999

401 Unauthorized (simple):

text
🔒 401 Unauthorized: /admin/dashboard

500 Internal Server Error (detailed):

text
❌ 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 (
        <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:

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<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:

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(<ServerErrorPage />, 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(
        <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:

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
<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:

text
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