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:

# Use the real BikeRentalShop synchronized backend from the tutorial examples.
pwsh .\docs\_includes\examples\pwsh\BikeRentalShop\Synchronized\Service.ps1 -Port 5443

Step-by-step

  1. 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.
  2. Runtime folders: Each service prepares data/, logs/, and data/certs/ so state, logs, and the local HTTPS certificate copy survive restarts; the shared development root is also mirrored under BikeRentalShop/certs/ for sibling samples.
  3. Shared state: Private/State.ps1 initializes the bike catalog and rental records before Enable-KrConfiguration so every route runspace sees the same application state.
  4. Security: The backend binds HTTPS, configures the BikeRentalStaffApiKey scheme, and can optionally allow browser origins for a separate web client.
  5. Public API: Catalog, rental creation, rental lookup, and health routes are exposed under /api/* for regular customers.
  6. Staff API: Protected endpoints provide the operations dashboard, bike inventory changes, and rental returns by requiring X-Api-Key: bike-shop-demo-key.
  7. Documentation: The sample publishes Swagger plus raw OpenAPI JSON so the contract stays visible as the app grows.
  8. Extensions: The same API shape can be swapped to the Concurrent backend or paired with the standalone Web/ 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

Rental lifecycle

  1. A customer calls POST /api/rentals with a bike ID, phone number, and planned hours.
  2. The service checks whether that bike exists and is still available.
  3. If the bike is available, the service creates a rental ID, pickup code, due date, and estimated total, then marks the bike as rented.
  4. Customers can read the live state through GET /api/rentals/{rentalId}, while staff can see aggregate counts through GET /api/staff/dashboard.
  5. 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


Previous / Next

Previous: File and Form Uploads Next: Bike Rental Shop Web Client