Product Search with HTTP QUERY (OpenAPI 3.2)
Demonstrates the OpenAPI 3.2 HTTP QUERY method with structured request body filters, pagination parameters, and response content negotiation.
Full source
File: pwsh/tutorial/examples/10.19-OpenAPI-Hello-Query.ps1
param(
[int]$Port = 5000,
[IPAddress]$IPAddress = [IPAddress]::Loopback
)
# --- Logging / Server ---
New-KrLogger | Add-KrSinkConsole |
Set-KrLoggerLevel -Value Debug |
Register-KrLogger -Name 'console' -SetAsDefault
New-KrServer -Name 'OpenAPI Hello World'
Add-KrEndpoint -Port $Port -IPAddress $IPAddress
# =========================================================
# TOP-LEVEL OPENAPI
# =========================================================
Add-KrOpenApiInfo -Title 'Products API' `
-Version '1.0.0' `
-Description 'Demonstrates HTTP QUERY for filtered product search (OpenAPI 3.2).'
# Reusable query parameters (components)
[OpenApiParameterComponent(In = 'Query', Description = 'Page number')]
[ValidateRange(1, 1000)]
[int]$page = 1
[OpenApiParameterComponent(In = 'Query', Description = 'Page size')]
[ValidateRange(1, 100)]
[int]$pageSize = 25
# Reusable schema components
[OpenApiSchemaComponent(RequiredProperties = ('id', 'name', 'price'))]
class Product {
[OpenApiPropertyAttribute(Description = 'Unique product identifier', Minimum = 1, Example = 101)]
[long]$id
[OpenApiPropertyAttribute(Description = 'Product name', Example = 'Laptop Pro')]
[string]$name
[OpenApiPropertyAttribute(Description = 'Product price', Format = 'double', Example = 1299.99, Minimum = 0)]
[double]$price
[OpenApiPropertyAttribute(Description = 'Inventory count', Minimum = 0)]
[int]$stock
}
[OpenApiSchemaComponent()]
class ProductSearchRequest {
[OpenApiPropertyAttribute(Description = 'Free-text search', Example = 'laptop')]
[string]$q
[OpenApiPropertyAttribute(Description = 'Category filter', Example = 'electronics')]
[string]$category
[OpenApiPropertyAttribute(Description = 'Minimum price', Format = 'double', Minimum = 0, Example = 500)]
[double]$minPrice
[OpenApiPropertyAttribute(Description = 'Maximum price', Format = 'double', Minimum = 0, Example = 2000)]
[double]$maxPrice
[OpenApiPropertyAttribute(Description = 'Only products in stock', Example = $true)]
[bool]$inStock
}
# =========================================================
# ROUTES / OPERATIONS
# =========================================================
Enable-KrConfiguration
Add-KrApiDocumentationRoute -DocumentType Swagger -OpenApiEndpoint "/openapi/v3.2/openapi.yaml"
Add-KrApiDocumentationRoute -DocumentType Redoc -OpenApiEndpoint "/openapi/v3.2/openapi.yaml"
Add-KrApiDocumentationRoute -DocumentType RapiDoc -OpenApiEndpoint "/openapi/v3.2/openapi.yaml"
Add-KrApiDocumentationRoute -DocumentType Scalar -OpenApiEndpoint "/openapi/v3.2/openapi.yaml"
Add-KrApiDocumentationRoute -DocumentType Elements -OpenApiEndpoint "/openapi/v3.2/openapi.yaml"
# =========================================================
# PRODUCT SEARCH (HTTP QUERY)
# =========================================================
<#
.SYNOPSIS
Search products using HTTP QUERY.
.DESCRIPTION
Demonstrates the OpenAPI 3.2 QUERY method with a structured request body
for filters and standard query parameters for pagination.
.PARAMETER page
Page number (query parameter component).
.PARAMETER pageSize
Page size (query parameter component).
.PARAMETER filters
Filter criteria passed in the QUERY request body.
#>
function searchProducts {
[OpenApiPath(HttpVerb = 'query', Pattern = '/v1/products/search')]
param(
[OpenApiParameterRef(ReferenceId = 'page')]
[int]$page,
[OpenApiParameterRef(ReferenceId = 'pageSize')]
[int]$pageSize,
[OpenApiRequestBody(Description = 'Search filters', ContentType = 'application/json')]
[ProductSearchRequest]$filters
)
$all = @(
[Product]@{ id = 101; name = 'Laptop Pro'; price = 1299.99; stock = 4 }
[Product]@{ id = 102; name = 'Laptop Air'; price = 999.00; stock = 0 }
[Product]@{ id = 103; name = 'Phone Max'; price = 1099.50; stock = 15 }
[Product]@{ id = 104; name = 'Desk Chair'; price = 199.99; stock = 50 }
[Product]@{ id = 105; name = 'USB-C Hub'; price = 79.99; stock = 120 }
[Product]@{ id = 106; name = 'Noise Headset'; price = 249.00; stock = 30 }
)
$filtered = $all
if ($filters.q) {
$q = $filters.q.ToLower()
$filtered = $filtered | Where-Object { $_.name.ToLower().Contains($q) }
}
if ($filters.category) {
# Demo: pretend electronics are items priced >= $100
if ($filters.category -eq 'electronics') {
$filtered = $filtered | Where-Object { $_.price -ge 100 }
}
}
if ($filters.minPrice) { $filtered = $filtered | Where-Object { $_.price -ge $filters.minPrice } }
if ($filters.maxPrice) { $filtered = $filtered | Where-Object { $_.price -le $filters.maxPrice } }
if ($filters.inStock) { $filtered = $filtered | Where-Object { $_.stock -gt 0 } }
$skip = ($page - 1) * $pageSize
$paged = $filtered | Select-Object -Skip $skip -First $pageSize
$result = [ordered]@{
page = $page
pageSize = $pageSize
total = ($filtered | Measure-Object).Count
items = $paged
}
# Negotiate response by Accept header (JSON/XML/YAML when supported)
Write-KrResponse -InputObject $result -StatusCode 200
}
# =========================================================
# OPENAPI DOC ROUTE / BUILD
# =========================================================
Add-KrOpenApiRoute -DefaultVersion '3.2' # Default pattern '/openapi/{version}/openapi.{format}'
# =========================================================
# RUN SERVER
# =========================================================
Start-KrServer -CloseLogsOnExit
Step-by-step
-
Server setup: Create the host with New-KrServer and configure an endpoint with Add-KrEndpoint.
-
OpenAPI metadata + schemas: Register OpenAPI info with Add-KrOpenApiInfo, then define request/response schemas as PowerShell classes:
Product: item id, name, price, stock.ProductSearchRequest: filters likeq,category,minPrice,maxPrice,inStock.
-
Reusable pagination parameters: Define pagination parameter components with
[OpenApiParameterComponent](including validation, e.g.,ValidateRange). -
Define the QUERY operation: Create a route function decorated with
[OpenApiPath(HttpVerb = 'query', Pattern = '/v1/products/search')]. -
Wire up inputs: Reference pagination components via
[OpenApiParameterRef]and accept the structured filter body via[OpenApiRequestBody]using theProductSearchRequestschema. -
Implement filtering + pagination: Apply text/category/price/stock filters, then compute offset/limit from
page/pageSizeand return the appropriate slice. -
Return a negotiated response: Use Write-KrResponse (not
Write-KrJsonResponse) to honor theAcceptheader (JSON, YAML, XML). -
Finalize configuration and start: Register documentation UIs (Add-KrApiDocumentationRoute) and the OpenAPI document route (Add-KrOpenApiRoute,
DefaultVersion '3.2'), commit staged changes with Enable-KrConfiguration, then start the server with Start-KrServer.
Try it
Save the sample and run:
# From repository root
Import-Module .\src\PowerShell\Kestrun\Kestrun.psd1 -Force
.\docs\_includes\examples\pwsh\10.19-OpenAPI-Hello-Query.ps1 -Port 5000
Then open http://127.0.0.1:5000/docs/swagger in your browser to view the interactive API documentation.
Test the QUERY endpoint
Using curl:
curl -X QUERY "http://127.0.0.1:5000/v1/products/search?page=1&pageSize=2" `
-H "Content-Type: application/json" `
-H "Accept: application/json" `
-d "{""q"":""lap"",""category"":""electronics"",""minPrice"":800,""inStock"":true}"
Using PowerShell:
$body = @{
q = 'lap'
category = 'electronics'
minPrice = 800
inStock = $true
} | ConvertTo-Json -Depth 5
$result = Invoke-WebRequest -Uri 'http://127.0.0.1:5000/v1/products/search?page=1&pageSize=2' `
-CustomMethod 'QUERY' `
-ContentType 'application/json' `
-Body $body `
-Headers @{ Accept = 'application/json' } `
-SkipCertificateCheck
$result.Content | ConvertFrom-Json | Format-Table
Stop the server with Ctrl+C.
Troubleshooting
- Port already in use: Choose a different port with
-Port 5001or similar. - QUERY not recognized: Ensure PowerShell 7.4+ (which supports
-CustomMethod 'QUERY'inInvoke-WebRequest). - OpenAPI spec mismatch: Verify
HttpVerb = 'query'in the function’s[OpenApiPath]attribute; OpenAPI 3.2 usesquery(lowercase) as the operation key. - No documentation UIs: Check that Add-KrApiDocumentationRoute is called before Enable-KrConfiguration.
- Negotiated response returns JSON only: Ensure you use Write-KrResponse (not
Write-KrJsonResponse); the latter forces JSON regardless ofAcceptheader.
References
- New-KrServer
- Add-KrEndpoint
- Add-KrOpenApiInfo
- Add-KrApiDocumentationRoute
- Add-KrOpenApiRoute
- Enable-KrConfiguration
- Write-KrResponse
- Start-KrServer
- OpenAPI-Guide
Previous / Next
Previous: Multiple OpenAPI Documents Next: SSE