Bike Rental Shop Application
Build a realistic Kestrun application that manages bike inventory, rental checkout, staff operations, and an optional standalone web client.
Full source
Files:
pwsh/tutorial/examples/BikeRentalShop/README.mdpwsh/tutorial/examples/BikeRentalShop/Synchronized/Service.ps1pwsh/tutorial/examples/BikeRentalShop/Synchronized/Private/State.ps1pwsh/tutorial/examples/BikeRentalShop/Synchronized/Private/Routes.ps1
# Use the real BikeRentalShop synchronized backend from the tutorial examples.
pwsh .\docs\_includes\examples\pwsh\BikeRentalShop\Synchronized\Service.ps1 -Port 5443
Step-by-step
- Entry point: The synchronized backend source lives at
pwsh/tutorial/examples/BikeRentalShop/Synchronized/Service.ps1; run the repo-local script from the checkout when you want to execute it. - Runtime folders: Each service prepares
data/,logs/, anddata/certs/so state, logs, and the local HTTPS certificate copy survive restarts; the shared development root is also mirrored underBikeRentalShop/certs/for sibling samples. - Shared state:
Private/State.ps1initializes the bike catalog and rental records beforeEnable-KrConfigurationso every route runspace sees the same application state. - Security: The backend binds HTTPS, configures the
BikeRentalStaffApiKeyscheme, and can optionally allow browser origins for a separate web client. - Public API: Catalog, rental creation, rental lookup, and health routes are exposed under
/api/*for regular customers. - Staff API: Protected endpoints provide the operations dashboard, bike inventory changes, and rental returns by requiring
X-Api-Key: bike-shop-demo-key. - Documentation: The sample publishes Swagger plus raw OpenAPI JSON so the contract stays visible as the app grows.
- Extensions: The same API shape can be swapped to the
Concurrentbackend or paired with the standaloneWeb/Razor Pages client without redesigning the routes.
Try it
Start the synchronized backend:
pwsh .\docs\_includes\examples\pwsh\BikeRentalShop\Synchronized\Service.ps1 -Port 5443
Exercise the rental flow:
# Browse the available bikes
curl -k https://127.0.0.1:5443/api/bikes
# Create a rental
$payload = @{
bikeId = 'bk-100'
customerName = 'Ava Flores'
phone = '+1-202-555-0148'
plannedHours = 3
} | ConvertTo-Json
$rental = Invoke-RestMethod -Uri 'https://127.0.0.1:5443/api/rentals' `
-Method Post `
-ContentType 'application/json' `
-Body $payload `
-SkipCertificateCheck
# Track the rental
Invoke-RestMethod -Uri "https://127.0.0.1:5443/api/rentals/$($rental.rentalId)" -SkipCertificateCheck
# Inspect the staff dashboard
Invoke-RestMethod -Uri 'https://127.0.0.1:5443/api/staff/dashboard' `
-Headers @{ 'X-Api-Key' = 'bike-shop-demo-key' } `
-SkipCertificateCheck
# Return the rental
$returnPayload = @{ conditionNotes = 'Returned clean and ready for the next rider.' } | ConvertTo-Json
Invoke-RestMethod -Uri "https://127.0.0.1:5443/api/staff/rentals/$($rental.rentalId)/return" `
-Method Post `
-ContentType 'application/json' `
-Body $returnPayload `
-Headers @{ 'X-Api-Key' = 'bike-shop-demo-key' } `
-SkipCertificateCheck
Run the standalone web client against the same backend:
pwsh .\docs\_includes\examples\pwsh\BikeRentalShop\Synchronized\Service.ps1 -Port 5443 `
-AllowedCorsOrigins @('https://127.0.0.1:5445', 'https://localhost:5445')
pwsh .\docs\_includes\examples\pwsh\BikeRentalShop\Web\Service.ps1 -Port 5445 -Backend Synchronized
Application layout
pwsh/tutorial/examples/BikeRentalShop/Synchronized/Service.ps1is the package-ready backend entry point for the tutorial.pwsh/tutorial/examples/BikeRentalShop/Synchronized/Private/State.ps1owns storage initialization, persistence, and certificate helpers.pwsh/tutorial/examples/BikeRentalShop/Synchronized/Private/Routes.ps1maps the customer and staff APIs and keeps the route logic separate from host setup.pwsh/tutorial/examples/BikeRentalShop/Concurrent/Service.ps1keeps the same API contract but swaps the in-memory model to concurrent dictionaries.pwsh/tutorial/examples/BikeRentalShop/Web/Service.ps1adds a separate Razor Pages client that talks to the backend over HTTP.
Rental lifecycle
- A customer calls
POST /api/rentalswith a bike ID, phone number, and planned hours. - The service checks whether that bike exists and is still
available. - If the bike is available, the service creates a rental ID, pickup code, due date, and estimated total, then marks the bike as
rented. - Customers can read the live state through
GET /api/rentals/{rentalId}, while staff can see aggregate counts throughGET /api/staff/dashboard. - Staff complete the workflow by calling
POST /api/staff/rentals/{rentalId}/return, which records return details and makes the bike available again.
Why this sample matters
- It shows how to split a medium-sized application into host setup, state management, OpenAPI metadata, and route handlers without losing readability.
- It demonstrates a realistic auth boundary: public customer routes alongside staff-only operations.
- It shows how to keep the API contract stable while changing the backing state model or adding a browser client.
- It is ready to package with
New-KrServicePackage, which makes it a good reference for real deployments instead of only toy tutorials.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
POST /api/rentals returns 409 | The selected bike is already rented | Call GET /api/bikes?status=available first and choose an available bike ID |
Staff routes return 401 or 403 | The X-Api-Key header is missing or incorrect | Send X-Api-Key: bike-shop-demo-key with dashboard, add-bike, remove-bike, and return requests |
| Browser calls fail from the standalone web client | The backend was started without a matching CORS origin | Start the backend with -AllowedCorsOrigins @('https://127.0.0.1:5445', 'https://localhost:5445') |
| State resets unexpectedly | The sample is running from a different folder or its data/ directory was cleared | Run the sample from the repository and check the BikeRentalShop example’s data/ folder |
References
- New-KrServer
- Add-KrEndpoint
- Set-KrServerLimit
- Add-KrApiKeyAuthentication
- New-KrCorsPolicyBuilder
- Add-KrCorsPolicy
- Add-KrOpenApiRoute
- Add-KrApiDocumentationRoute
- New-KrServicePackage
- Add-KrPowerShellRazorPagesRuntime
- Razor Guide
- OpenAPI Guide
Previous / Next
Previous: File and Form Uploads Next: Bike Rental Shop Web Client