Route Groups
Group related routes under a shared URL prefix to remove repetition, keep concerns localized, and make refactoring simpler. While inside the Add-KrRouteGroup
script block all Add-KrMapRoute
calls inherit the group’s settings (prefix + shared options) so you define them once. Groups can be nested: inner groups inherit and may override unless you opt out with -NoInherit
.
Prerequisites: see Introduction.
Full source
<#
Sample Kestrun Server on how to use route parameters.
These examples demonstrate how to access route parameters and query strings in a Kestrun server.
FileName: 2.3-Route-Parameters.ps1
#>
# Import the Kestrun module
Install-PSResource -Name Kestrun
New-KrLogger |
Set-KrMinimumLevel -Value Debug |
Add-KrSinkConsole |
Register-KrLogger -Name 'DefaultLogger' -SetAsDefault
# Create a new Kestrun server
New-KrServer -Name "Simple Server"
# Add a listener on port 5000 and IP address 127.0.0.1 (localhost)
Add-KrListener -Port 5000 -IPAddress ([IPAddress]::Loopback)
# Add the PowerShell runtime
# !!!!Important!!!! this step is required to process PowerShell routes and middlewares
Add-KrPowerShellRuntime
# Enable Kestrun configuration
Enable-KrConfiguration
# Route group with a common prefix
Add-KrRouteGroup -Prefix "/input" -ScriptBlock {
# Path parameter example
Add-KrMapRoute -Verbs Get -Pattern "/{value}" -ScriptBlock {
$value = Get-KrRequestRouteParam -Name 'value'
Write-KrTextResponse -InputObject "The Path Parameter 'value' was: $value" -StatusCode 200
}
# Query string example
Add-KrMapRoute -Verbs Patch -ScriptBlock {
$value = Get-KrRequestQuery -Name 'value'
Write-KrTextResponse -InputObject "The Query String 'value' was: $value" -StatusCode 200
}
# Body parameter example
Add-KrMapRoute -Verbs Post -ScriptBlock {
$body = Get-KrRequestBody
Write-KrTextResponse -InputObject "The Body Parameter 'value' was: $($body.value)" -StatusCode 200
}
# Header parameter example
Add-KrMapRoute -Verbs Put -ScriptBlock {
$value = Get-KrRequestHeader -Name 'value'
Write-KrTextResponse -InputObject "The Header Parameter 'value' was: $value" -StatusCode 200
}
# Cookie parameter example
Add-KrMapRoute -Verbs Delete -ScriptBlock {
$value = Get-KrRequestCookie -Name 'value'
Write-KrTextResponse -InputObject "The Cookie Parameter 'value' was: $value" -StatusCode 200
}
}
# Start the server asynchronously
Start-KrServer
Why use a route group?
- Single place to change a base path (e.g., move
/input
->/api/input
). - Eliminates repeating the prefix string in every pattern.
- Logically clusters related endpoints (easier scanning & code review).
- Future friendly: middlewares / filters or authorization scopes applied per group (when available).
- Share authorization, imports, assemblies, or custom argument data with every route in the group.
- Clearer separation reduces accidental pattern typos.
Capabilities in practice
Route groups give you a lightweight composition mechanism rather than a long list of flags. Think of them as a contextual block where you can:
- Declare a base URL once and have every enclosed route pick it up automatically.
- Share cross‑cutting concerns (auth requirements, common imports, referenced assemblies) without repeating them on each route.
- Inject shared helper objects or services through a small key/value bag that children can refine (later definitions win for the same key).
- Organize large APIs by splitting inner definitions into separate files that still inherit the surrounding context.
- Start a completely fresh mini‑area (skip inheritance) when a sub‑group is intentionally different.
Internally inheritance is hierarchical: outer context → inner context. Each level layers on top of what came before; opting out simply starts a new layer from scratch. You rarely need to think about the implementation—just place related routes inside the same block, and move or rename the prefix later with one edit.
Additional examples
Nested groups with override
Add-KrRouteGroup -Prefix '/api' -AuthorizationPolicy 'RequireUser' -Arguments @{ Version = 'v1' } -ScriptBlock {
# /api/health
Add-KrMapRoute -Verbs Get -Pattern '/health' -ScriptBlock { Write-KrTextResponse 'ok' }
# Nested group for todo area
Add-KrRouteGroup -Prefix '/todos' -Arguments @{ Area = 'todos' } -ScriptBlock {
# Effective arguments: Version = v1, Area = todos
Add-KrMapRoute -Verbs Get -Pattern '/' -ScriptBlock {
Write-KrJsonResponse -InputObject @{ area = 'todos'; version = $Arguments.Version }
}
}
}
Fresh scope (skip inheritance)
Add-KrRouteGroup -Prefix '/api' -AuthorizationPolicy 'RequireUser' -ScriptBlock {
Add-KrRouteGroup -Prefix '/public' -NoInherit -ScriptBlock {
# Does NOT require 'RequireUser' policy; inheritance skipped
Add-KrMapRoute -Verbs Get -Pattern '/' -ScriptBlock { Write-KrTextResponse 'public info' }
}
}
Loading routes from a file
Add-KrRouteGroup -Prefix '/todoitems' -FileName '.\Routes\TodoItems.ps1'
Where TodoItems.ps1
contains only the inner route definitions (they still inherit the prefix).
Using a pre-built options template
$base = New-KrMapRouteOption -Property @{ Pattern = '/reports'; ExtraImports = 'System.Linq' }
Add-KrRouteGroup -Options $base -ScriptBlock {
Add-KrMapRoute -Verbs Get -Pattern '/summary' -ScriptBlock { Write-KrTextResponse 'summary' }
}
Step-by-step
- Base setup (module install, server, listener, PowerShell runtime, configuration) matches earlier chapters.
- Logging pipeline: demonstrates creating and registering a default logger before mapping routes (optional but helpful when exploring groups).
Add-KrRouteGroup -Prefix "/input"
: opens a scope in which all enclosed routes inherit that base prefix. Patterns inside can still begin with/
– they are treated as relative to the group prefix.- Path parameter route: pattern
/{value}
becomes final path/input/{value}
and uses Get-KrRequestRouteParam. - Query, Body, Header, Cookie examples mirror the dedicated chapter but now share the same base path
/input
with different verbs (PATCH, POST, PUT, DELETE) to avoid collisions. - Responses: all use Write-KrTextResponse for brevity.
- Start the server:
Start-KrServer
begins processing asynchronously allowing you to interact immediately.
Verb strategy: Using unique verbs for the same endpoint shape lets you test multiple request data sources without conflicting route matches. You can change verbs to all GET if you differentiate paths instead.
Try it
Start the server
. .\examples\PowerShell\Tutorial\2.5-Route-Group.ps1
Path parameter
curl http://127.0.0.1:5000/input/HelloPath
Invoke-WebRequest -Uri 'http://127.0.0.1:5000/input/HelloPath' | Select-Object -ExpandProperty Content
Query string
curl -X PATCH "http://127.0.0.1:5000/input?value=HelloQuery"
Invoke-WebRequest -Method Patch -Uri 'http://127.0.0.1:5000/input?value=HelloQuery' | Select-Object -ExpandProperty Content
Body (POST JSON)
curl -X POST http://127.0.0.1:5000/input -H "Content-Type: application/json" -d '{"value":"HelloBody"}'
Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:5000/input' -Body (@{ value = 'HelloBody' } | ConvertTo-Json) -ContentType 'application/json'
Header
curl -X PUT -H "value: HelloHeader" http://127.0.0.1:5000/input
Invoke-WebRequest -Method Put -Uri 'http://127.0.0.1:5000/input' -Headers @{ value = 'HelloHeader' } | Select-Object -ExpandProperty Content
Cookie
curl -X DELETE --cookie "value=HelloCookie" http://127.0.0.1:5000/input
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$cookie = New-Object System.Net.Cookie 'value','HelloCookie','/','127.0.0.1'
$session.Cookies.Add($cookie)
Invoke-WebRequest -Method Delete -Uri 'http://127.0.0.1:5000/input' -WebSession $session | Select-Object -ExpandProperty Content
Cmdlet / API references
- Add-KrRouteGroup
- Add-KrMapRoute
- Get-KrRequestRouteParam
- Get-KrRequestQuery
- Get-KrRequestBody
- Get-KrRequestHeader
- Get-KrRequestCookie
- Write-KrTextResponse
- Start-KrServer
- Enable-KrConfiguration
- Add-KrPowerShellRuntime
Troubleshooting
Symptom | Likely Cause | Fix |
---|---|---|
404 for expected /input/... route | Route defined outside or before group? | Ensure definitions are inside the Add-KrRouteGroup block |
Value $null for path parameter | Pattern typo (/{vale} ) or wrong segment | Correct the placeholder spelling |
Body $null | Missing Content-Type: application/json header | Add the header / send JSON or examine Get-KrRequestBody |
Conflicts with standalone /input | Ungrouped route already registered same verb/path | Use distinct verbs or adjust patterns |
Logger output missing | Logger not registered before route group | Run logging pipeline prior to mapping routes |
ExtraImports not applied | Defined after entering group | Provide -ExtraImports on the group itself |
Arguments value unexpected | Key overridden by child group | Review merge order; use unique keys or inspect parents |
Authorization not enforced | Policy/scheme missing or -NoInherit used | Add policy on current group or remove -NoInherit |
Next
Continue to Static Files or explore more advanced patterns with middleware later in the tutorial.