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
- Logging: Register console logger as default.
- Server: Create server named ‘Responses 9.7’.
- Listener: Listen on 127.0.0.1:5000.
- Runtime: Add PowerShell runtime.
/fail: returns validation-style error (400) with details./boom: throws and catches exception, returns 500 with stack trace.- 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