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

  1. Base setup: Initialize-KrRoot, New-KrServer, Add-KrEndpoint, then Enable-KrConfiguration.
  2. (Optional) Logging: create and register a default logger to observe requests while exploring groups.
  3. 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.
  4. Path route: pattern /{value} becomes final path /input/{value}; read with Get-KrRequestRouteParam.
  5. Query, Body, Header, Cookie: same ideas as earlier, but now sharing the /input base with different verbs (PATCH, POST, PUT, DELETE) to avoid collisions.
  6. Responses: use Write-KrTextResponse for simplicity.
  7. Start: Start-KrServer to 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'
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

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

Previous / Next

Go back to Route Options or continue to Static Files.