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
File: pwsh/tutorial/examples/2.5-Route-Group.ps1
<#
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
#>
param(
[int]$Port = 5000,
[IPAddress]$IPAddress = [IPAddress]::Loopback
)
New-KrLogger |
Set-KrLoggerLevel -Value Debug |
Add-KrSinkConsole |
Register-KrLogger -Name 'DefaultLogger' -SetAsDefault
# Create a new Kestrun server
New-KrServer -Name "Simple Server"
# Add a listener on the configured port and IP address
Add-KrEndpoint -Port $Port -IPAddress $IPAddress
# 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'; ScriptCode=@{ ExtraImports = 'System.Linq' } }
Add-KrRouteGroup -Options $base -ScriptBlock {
Add-KrMapRoute -Verbs Get -Pattern '/summary' -ScriptBlock { Write-KrTextResponse 'summary' }
}
Step-by-step
- Base setup:
Initialize-KrRoot,New-KrServer,Add-KrEndpoint, thenEnable-KrConfiguration. - (Optional) Logging: create and register a default logger to observe requests while exploring groups.
- Open a group:
Add-KrRouteGroup -Prefix '/input'creates a scope with a shared URL prefix. Routes defined inside inherit the prefix; patterns can still start with/and are resolved relative to it. - Path route: pattern
/{value}becomes final path/input/{value}; read with Get-KrRequestRouteParam. - Query, Body, Header, Cookie: same ideas as earlier, but now sharing the
/inputbase with different verbs (PATCH, POST, PUT, DELETE) to avoid collisions. - Responses: use Write-KrTextResponse for simplicity.
- Start:
Start-KrServerto begin processing requests.
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
Save the sample locally so it’s easy to run. Copy the contents of pwsh/tutorial/examples/2.5-Route-Group.ps1 into a new file in an empty working folder (for example, route-groups.ps1), then run:
# From your working folder
pwsh .\route-groups.ps1
Now test each route using different verbs:
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
References
- Add-KrRouteGroup
- Add-KrMapRoute
- Get-KrRequestRouteParam
- Get-KrRequestQuery
- Get-KrRequestBody
- Get-KrRequestHeader
- Get-KrRequestCookie
- Write-KrTextResponse
- Start-KrServer
- Enable-KrConfiguration
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 |
Previous / Next
Go back to Route Options or continue to Static Files.