Routing

Routes are PowerShell functions decorated with attributes. This is where schemas, parameters, request bodies, responses, tags, and extensions all come together.

[OpenApiPath]

Defines the HTTP method, route pattern, summary, description, and tags.

[OpenApiPath(
    HttpVerb = 'post',
    Pattern = '/users',
    Summary = 'Create a user',
    Tags = @('Users')
)]

[OpenApiResponse]

Defines possible responses. You can reference a class type directly or a component name.

# Reference by type
[OpenApiResponse(StatusCode = '200', Schema = [User])]

# Reference by component name
[OpenApiResponse(StatusCode = '400', Schema = "ErrorResponse")]

# Inline description
[OpenApiResponse(StatusCode = '204', Description = 'No Content')]

[OpenApiRequestBody]

Defines the expected request body.

# Option A: typed body
param(
  [OpenApiRequestBody(ContentType = 'application/json')]
  [CreateUserRequest]$Body
)

# Option B: explicit reference
param(
  [OpenApiRequestBodyRef(ReferenceId = 'CreateUserRequest', Required = $true)]
  [object]$Body
)

# Inline definition
param(
  [OpenApiRequestBody(Description = 'Raw text', ContentType = 'text/plain')]
  [string]$Body
)

[OpenApiParameter]

Defines individual parameters if not using a reusable component.

[OpenApiParameter(Name = 'id', In = 'path', Required = $true, Type = [long])]

or better yet, use the common attributes used by PowerShell for validation:

[OpenApiParameter( In = 'path', Required = $true)]
[Parameter(Mandatory)]
[long]$id

Using reusable parameter components in routes

function listItems {
    [OpenApiPath(HttpVerb = 'get', Pattern = '/items')]
    param(
        [OpenApiParameterRef(ReferenceId = 'page')]
        [int]$page,

        [OpenApiParameterRef(ReferenceId = 'limit')]
        [int]$limit
    )
}

Path parameter example

function getItem {
  [OpenApiPath(HttpVerb = 'get', Pattern = '/items/{id}')]
  param(
    [OpenApiParameter(In = [OaParameterLocation]::Path, Required = $true)]
    [int]$id
  )
}

Request body components in routes

function createProduct {
    [OpenApiPath(HttpVerb = 'post', Pattern = '/products')]
    [OpenApiResponse(StatusCode = '201')]
    param(
        [OpenApiRequestBody(ContentType = 'application/json')]
        [CreateProductRequest]$Body
    )
}

If you cannot or do not want to type the runtime parameter, use a request-body reference:

function createProduct {
    [OpenApiPath(HttpVerb = 'post', Pattern = '/products')]
    [OpenApiResponse(StatusCode = '201')]
    param(
        [OpenApiRequestBodyRef(ReferenceId = 'CreateProductRequest', Required = $true)]
        [object]$Body
    )
}

Response components in routes

function getProduct {
    [OpenApiPath(HttpVerb = 'get', Pattern = '/products/{id}')]
    [OpenApiResponse(StatusCode = '200', Schema = [ProductSchema], ContentType = ('application/json', 'application/xml'))]
    [OpenApiResponseRef(StatusCode = '404', ReferenceId = 'NotFound')]
    param()
}

Tags, external docs, and hierarchical tags

$ordersExternalDocs = New-KrOpenApiExternalDoc -Description 'Order docs' -Url 'https://example.com/orders'

Add-KrOpenApiTag -Name 'operations' -Description 'Common operational endpoints' -Kind 'category'
Add-KrOpenApiTag -Name 'orders' -Description 'Order operations' -Parent 'operations' -Kind 'resource' -ExternalDocs $ordersExternalDocs
Add-KrOpenApiTag -Name 'orders.read' -Description 'Read-only order operations' -Parent 'orders' -Kind 'operation'

HTTP QUERY method (OpenAPI 3.2+)

The HTTP QUERY method is a semantically clearer alternative to GET for search/filter operations that require a request body.

Example

[OpenApiParameterComponent(In = 'Query', Description = 'Page number')]
[ValidateRange(1, 1000)]
[int]$page = 1

[OpenApiParameterComponent(In = 'Query', Description = 'Page size')]
[ValidateRange(1, 100)]
[int]$pageSize = 25

[OpenApiSchemaComponent()]
class ProductSearchFilters {
    [OpenApiPropertyAttribute(Description = 'Search query', Example = 'laptop')]
    [string]$q

    [OpenApiPropertyAttribute(Description = 'Min price', Example = 500)]
    [double]$minPrice
}

function searchProducts {
    [OpenApiPath(HttpVerb = 'query', Pattern = '/products/search')]
    [OpenApiResponse(StatusCode = '200', Description = 'Paginated results')]
    param(
        [OpenApiParameterRef(ReferenceId = 'page')]
        [int]$page,

        [OpenApiParameterRef(ReferenceId = 'pageSize')]
        [int]$pageSize,

        [OpenApiRequestBody(Description = 'Search filters')]
        [ProductSearchFilters]$filters
    )

    Write-KrResponse -InputObject @{
        page = $page
        pageSize = $pageSize
        total = 42
        items = @()
    } -StatusCode 200
}

For OpenAPI 3.0 and 3.1, Kestrun emits the fallback extension x-oai-additionalOperations.QUERY.

RFC 6570 path expressions (OpenAPI 3.2)

In OpenAPI 3.2, path templates are RFC 6570 URI templates.

Supported mapping patterns:

  • {var} - single path segment
  • {+var} - reserved expansion, can represent multiple segments
  • {var*} - explode, can represent multiple segments

Multi-segment variable mapping

OpenAPI 3.2 template:  /files/{+path}
Kestrun pattern:       /files/{**path}
ASP.NET Core route:    /files/{*path}

Kestrun maps the captured ASP.NET Core route values to RFC 6570 variables with rules that keep routing and OpenAPI aligned.