Errors

Use Write-KrErrorResponse for consistent failure payloads. Two parameter sets: message or exception.

Full source

File: pwsh/tutorial/examples/9.7-Errors.ps1


<#
    Sample: Errors
    Purpose: Demonstrate error responses in a Kestrun server.
    File:    9.7-Errors.ps1
    Notes:   Shows validation and exception error payloads.
#>
param(
    [int]$Port = 5000,
    [IPAddress]$IPAddress = [IPAddress]::Loopback
)
# 1. Logging
New-KrLogger | Add-KrSinkConsole | Register-KrLogger -Name 'console' -SetAsDefault

# 2. Server
New-KrServer -Name 'Responses 9.7'

# 3. Listener
Add-KrEndpoint -IPAddress $IPAddress -Port $Port


# Finalize configuration
Enable-KrConfiguration

# Validation error route
Add-KrMapRoute -Pattern '/fail' -Verbs GET -ScriptBlock {
    Write-KrErrorResponse -Message 'Missing parameter' -StatusCode 400 -Details 'id required'
}

# Exception error route
Add-KrMapRoute -Pattern '/boom' -Verbs GET -ScriptBlock {
    try {
        throw [System.InvalidOperationException]::new('Exploded')
    } catch {
        Write-KrErrorResponse -Exception $_.Exception -StatusCode 500 -IncludeStack
    }
}

# Start the server
Start-KrServer

Step-by-step

  1. Logging: Register console logger as default.
  2. Server: Create server named ‘Responses 9.7’.
  3. Listener: Listen on 127.0.0.1:5000.
  4. Runtime: Add PowerShell runtime.
  5. /fail: returns validation-style error (400) with details.
  6. /boom: throws and catches exception, returns 500 with stack trace.
  7. Enable configuration and start server.

Try it

curl -i http://127.0.0.1:5000/fail | jq
curl -i http://127.0.0.1:5000/boom | jq

PowerShell equivalents:

# Test validation error (400)
Invoke-WebRequest -Uri http://127.0.0.1:5000/fail -SkipHttpErrorCheck | Select-Object StatusCode, Content

# Test exception error (500)
Invoke-WebRequest -Uri http://127.0.0.1:5000/boom -SkipHttpErrorCheck | Select-Object StatusCode, Content

# Parse JSON error responses
Invoke-RestMethod -Uri http://127.0.0.1:5000/fail -SkipHttpErrorCheck
Invoke-RestMethod -Uri http://127.0.0.1:5000/boom -SkipHttpErrorCheck

# Handle errors in try/catch
try {
    Invoke-RestMethod -Uri http://127.0.0.1:5000/fail
} catch {
    Write-Host "Error: $($_.Exception.Message)"
    $_.Exception.Response.StatusCode
}

Error Response Types

Validation Errors (4xx)

Add-KrMapRoute -Pattern '/validate' -Verbs POST -ScriptBlock {
    $id = $Context.Request.Query['id']
    if (-not $id) {
        Write-KrErrorResponse -Message 'Validation failed' -StatusCode 400 -Details 'id parameter is required'
        return
    }

    if ($id -notmatch '^\d+$') {
        Write-KrErrorResponse -Message 'Invalid format' -StatusCode 400 -Details 'id must be a number'
        return
    }

    Write-KrJsonResponse @{ id = $id; status = 'valid' }
}

Authentication Errors (401/403)

Add-KrMapRoute -Pattern '/secure' -Verbs GET -ScriptBlock {
    $auth = $Context.Request.Headers['Authorization']
    if (-not $auth) {
        Write-KrErrorResponse -Message 'Authentication required' -StatusCode 401 -Details 'Missing Authorization header'
        return
    }

    if ($auth -ne 'Bearer valid-token') {
        Write-KrErrorResponse -Message 'Access denied' -StatusCode 403 -Details 'Invalid or expired token'
        return
    }

    Write-KrJsonResponse @{ message = 'Access granted' }
}

Server Errors (5xx)

Add-KrMapRoute -Pattern '/database' -Verbs GET -ScriptBlock {
    try {
        # Simulate database operation
        $data = Get-DatabaseData -Id 123
        Write-KrJsonResponse $data
    }
    catch [System.Data.SqlException] {
        Write-KrErrorResponse -Exception $_.Exception -StatusCode 503 -Message 'Database unavailable' -IncludeStack
    }
    catch {
        Write-KrErrorResponse -Exception $_.Exception -StatusCode 500 -Message 'Internal server error' -IncludeStack
    }
}

Custom Error Formats

Add-KrMapRoute -Pattern '/api-error' -Verbs GET -ScriptBlock {
    # Custom error object structure
    $errorResponse = @{
        error = @{
            code = 'RESOURCE_NOT_FOUND'
            message = 'The requested resource was not found'
            timestamp = (Get-Date).ToString('o')
            path = $Context.Request.Path
            details = @{
                resource_id = '12345'
                available_endpoints = @('/api/users', '/api/products')
            }
        }
    }

    $Context.Response.StatusCode = 404
    $errorResponse | Write-KrJsonResponse
}

Error Response Structure

When using Write-KrErrorResponse, the response follows this structure:

{
  "error": true,
  "message": "Error description",
  "details": "Additional context",
  "timestamp": "2025-09-16T10:30:00.000Z",
  "statusCode": 400,
  "exception": "System.InvalidOperationException: ...",
  "stackTrace": "at MyMethod() in file.ps1:line 10..."
}

Parameters

Parameter Type Description
-Message String Primary error description
-StatusCode Int HTTP status code (400, 401, 404, 500, etc.)
-Details String Additional context or help text
-Exception Exception .NET exception object for technical details
-IncludeStack Switch Include stack trace in response

Common HTTP Error Codes

Code Name When to Use
400 Bad Request Client sent invalid data or missing required fields
401 Unauthorized Authentication is required but not provided
403 Forbidden User is authenticated but lacks permission
404 Not Found Requested resource doesn’t exist
409 Conflict Request conflicts with current state (duplicate creation)
422 Unprocessable Entity Request is well-formed but semantically incorrect
429 Too Many Requests Rate limiting triggered
500 Internal Server Error Unexpected server-side failure
502 Bad Gateway Upstream service failure
503 Service Unavailable Temporary service disruption

Troubleshooting

Symptom Cause Fix
Error not returned Missing return after error Add return after Write-KrErrorResponse
Stack trace in production -IncludeStack always on Only use -IncludeStack in development
Generic error messages Not catching specific exceptions Use typed catch blocks
Client can’t parse errors Inconsistent error format Standardize on Write-KrErrorResponse

References


Previous / Next

Previous: Redirects Next: Caching & Revalidation