Building a server inventory REST API with F# and .NET Core

Across cloud engineering teams in Sydney and Melbourne, keeping a reliable inventory of bare-metal and virtual servers tends to become an audit-grade concern, especially once APRA CPS 234 or the Australian Cyber Security Centre's Essential Eight posture comes into scope. A lightweight REST service, written in F# on top of .NET Core, is a pragmatic way to expose that inventory to dashboards, change-management workflows and the occasional weekend incident call.

This walkthrough shows how to design and ship such an API, covering the domain model, request handling, persistence and the small touches that make the service friendly to operate from a Brisbane data centre or an AARNet-connected regional office. The code stays close to idiomatic F#, leaning on discriminated unions and pattern matching to express resource lifecycles in a way C# boilerplate usually hides.

Why F# is a productive fit for inventory services

F# treats data and behaviour as first-class concerns, which matters when a server inventory has many overlapping shapes: physical hosts, virtual machines, container-bearing nodes, retired boxes in cold storage. Discriminated unions let you express "this host is currently provisioning, online, in maintenance or decommissioned" without scattering string comparisons through the codebase.

The .NET Core runtime makes F# a calm choice for backend work. You get Kestrel performance that powers high-traffic ASP.NET services, the same NuGet ecosystem and a CLI workflow identical to what C# developers already know. For a team covering on-call rotations across AEDT and AWST, the static typing and compile-time exhaustiveness checks remove a lot of middle-of-the-night surprises.

For teams used to functional-first thinking, F#'s Result type and computation expressions map well onto HTTP error handling. A 404 is a value rather than an exception, and the type system nudges you to handle it before the request leaves the controller.

Setting up the project and dependencies

Start with a minimal F# .NET Core web API project. The dotnet new webapi -lang F# template scaffolds a Program.fs entry point and a startup file that wires up the dependency container. From there, the typical additions are Giraffe or Saturn for routing, FSharp.Data for JSON contracts and either Dapper or EF Core for the storage layer.

dotnet new webapi -lang F# -n ServerInventory
cd ServerInventory
dotnet add package Giraffe
dotnet add package FSharp.Data
dotnet add package Dapper

A small appsettings.json keeps connection strings tidy and lets the same binary run in a Melbourne dev environment, a Sydney staging cluster and an Azure Australia East production region without code changes. Environment overrides via ASPNETCORE_ENVIRONMENT are the usual lever, and pinning the .NET SDK in global.json keeps CI builds reproducible when runners sit on the AARNet backbone.

Modelling the inventory with discriminated unions

The heart of this service is a domain model that captures the lifecycle of a server without resorting to a forest of nullable fields. A discriminated union called ServerStatus covers the operational states, while a record type Server carries the immutable descriptors such as hostname, serial, rack, role and owner.

type ServerStatus =
    | Provisioning
    | Online
    | Maintenance
    | Decommissioned
    | Retired

This shape means pattern matching in endpoints is exhaustive. Adding a new state, like Quarantined for hosts flagged by an EDR scan, surfaces every place the code needs to react, which is a genuine safety net when the data feeds compliance reports.

A pragmatic field set for an Australian infrastructure team looks like:

  • Hostname and fully qualified domain name
  • Asset tag and serial number, useful for insurance and finance audits
  • Datacentre location, drawn from Sydney, Melbourne, Brisbane or Perth sites
  • Operating system, hypervisor and current patch baseline
  • Owner email and cost centre, kept current for internal billing
  • Last-seen timestamp, driven by the agent heartbeat

When the JSON contract is generated, lowerCamelCase keys play nicely with the JavaScript dashboards most operations teams use, and a [<JsonConverter>] attribute handles any awkward enums without polluting the F# source.

Building HTTP endpoints the safe way

With Giraffe, each endpoint is a small function that returns an HTTP handler. The surface area settles into a familiar shape: list endpoints with filters, single-resource lookups, create, update and lifecycle transitions. Keeping each route short makes the codebase easier to hand between team members when someone takes long service leave.

The standard endpoint set covers the usual CRUD verbs plus a few audit-friendly extras:

  • GET /api/servers with query filters for status and datacentre
  • GET /api/servers/{hostname} returning a single record
  • POST /api/servers for newly racked hardware
  • PUT /api/servers/{hostname} updating mutable fields
  • POST /api/servers/{hostname}/status for lifecycle transitions
  • DELETE /api/servers/{hostname} to mark a host as decommissioned

Error mapping is handled through a small helper that turns Result<Server, InventoryError> into the right status code. A missing host is a 404 with a problem-details payload, taking a Retired host back online is a 409 conflict, and a validation failure is a 422. The branches are short, but the discipline pays off when the Office of the Australian Information Commissioner asks how the service reports bad input.

Persistence, caching and the data sovereignty question

Where the inventory data physically lives matters in Australia. Healthcare-adjacent workloads often fall under the Privacy Act 1988 and the Notifiable Data Breaches scheme, while government suppliers work against the PSPF and IRAP-aligned zones. An F# service has no special opinions here, but the choice of database does.

A reasonable default is PostgreSQL, hosted in AWS Sydney (ap-southeast-2) or Azure Australia East, with row-level security keeping different tenants cleanly separated. Read-heavy inventory pages benefit from a short-lived in-memory cache, and writes flow through a transactional outbox so a separate worker can publish change events to a Service Bus topic that the rest of the platform already consumes.

Schema migrations stay manageable with a tool like Flyway or DbUp, run as part of the CI pipeline before the API container rolls out. Keeping migrations in plain SQL also makes it easy for the DBAs in a colocated Brisbane rack to review what is about to land.

Operating the API in production

Once the API is live, the operational concerns are mostly the same as for any other .NET Core service: structured logging, health checks, metrics and graceful shutdown. Serilog with the CompactJsonFormatter produces friendly events for an ELK stack hosted in the same Australian region as the database, which keeps log shipping inside the country.

OpenTelemetry traces from the Giraffe handlers attach cleanly to incoming request IDs, and a /health/live and /health/ready pair covers Kubernetes probes without leaking implementation details. The readiness check should confirm both the database connection and the cache layer, so a degraded node stops taking traffic rather than serving stale inventory.

For teams running on prem and in the cloud at the same time, container images built once in GitHub Actions can be promoted through environments with a simple kubectl apply -k overlays/prod. The same pipeline can run vulnerability scans against the .NET base image, an easy win against the Essential Eight application-hardening mitigation. When the broader container estate starts to outgrow manual operations, infrastructure planning and architecture work is available through Karl Katzke's Blog.

Get in touch or read further

If you are weighing up a similar F# service for your own inventory, drop a line and we can run through the trade-offs together. Engagements are scoped around infrastructure planning, architecture reviews and hands-on cloud work, typically delivered from an Australian timezone with the occasional site visit to a Sydney or Melbourne datacentre. For an external take on the collaboration-platform side of operations, related reading is worth a look.

Experience

Information Technology Consulting

Independent Practice

Provides IT consulting services focused on infrastructure planning, cloud migration strategy, and systems architecture. Engagements draw on years of hands-on sysadmin and development experience across Linux, Windows, and hybrid environments.

K9 Search & Rescue Volunteer

Ongoing

Active participant in K9 Search & Rescue operations, combining technical logistics skills with field support for canine search teams.

Karl Katzke's Blog

October 2006 – May 2014

Published a long-running personal technology blog covering cloud vs. in-house infrastructure, F# and Mono on OSX, hardware vendor critiques, RAID card performance analysis, and sysadmin storytelling. Notable posts include "When Sysadmins Ruled the Earth" (May 15, 2014) and "Getting Started with F# and Mono on OSX" (December 22, 2012).

Credentials

A small badge icon with a shield shape in muted blue tones on a light background

Systems Administration

Deep experience with Linux (RHEL, SLES, CentOS), high-availability clusters, and STONITH configurations.

A small badge icon with a gear shape in muted blue tones on a light background

Cloud Infrastructure

Practical knowledge of AWS EC2, reserved instances, and cost analysis for cloud vs. on-premises deployments.

A small badge icon with a code symbol in muted blue tones on a light background

Development

Proficient in F#, PHP (Symfony), and cross-platform tooling including Mono and MonoDevelop on OSX.

Studies

F# & Functional Programming

Self-directed, 2012

Explored strongly typed functional programming with F# on OSX using the Mono runtime. Published a detailed getting-started guide covering toolchain setup and cross-platform game development research.

High-Availability & Cluster Management

Professional Development, 2009

Configured and documented crm_mon email alerting for STONITH events on SLES11-HAE clusters, integrating with Nagios monitoring for production environments.

Hardware & Storage Performance

Ongoing

Conducted hands-on benchmarking of SATA/SAS RAID controllers including HighPoint RocketRaid 2740 and LSI/SuperMicro AOC-USASLP2-H8iR, comparing against software RAID configurations.

Skills

A small icon representing a server with clean geometric lines in slate blue

Linux Administration

RHEL, SLES, CentOS — package management, kernel tuning, HA clustering, and monitoring integration.

A small icon representing a cloud shape with clean geometric lines in slate blue

Cloud Architecture

AWS EC2, reserved-instance planning, cost modeling, and hybrid infrastructure strategy.

A small icon representing code brackets with clean geometric lines in slate blue

F# & .NET/Mono

Functional programming on OSX, MonoDevelop toolchain, and cross-platform game-dev exploration.

A small icon representing a database cylinder with clean geometric lines in slate blue

PHP & Symfony

Web application development with the Symfony framework and the broader PHP ecosystem.

A small icon representing a storage drive with clean geometric lines in slate blue

Storage & RAID

SATA/SAS controller evaluation, md RAID configuration, and performance benchmarking.

A small icon representing a shield with clean geometric lines in slate blue

High Availability

Pacemaker, STONITH, crm_mon alerting, and Nagios integration for production cluster monitoring.