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

  1. Base setup (module install, server, listener, PowerShell runtime, configuration) matches earlier chapters.
  2. Logging pipeline: demonstrates creating and registering a default logger before mapping routes (optional but helpful when exploring groups).
  3. 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.
  4. Path parameter route: pattern /{value} becomes final path /input/{value} and uses Get-KrRequestRouteParam.
  5. 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.
  6. Responses: all use Write-KrTextResponse for brevity.
  7. 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'
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
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

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.