100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

F# and Web Development

F# builds web applications through plain ASP.NET Core, the functional Giraffe framework, the higher-level Saturn DSL, and Fable-compiled frontends that share domain types with the backend.

Practical F#Intermediate9 min readJul 10, 2026
Analogies

F# Options for Building Web Applications

F# can build full web applications on top of ASP.NET Core in several ways: writing ordinary ASP.NET Core controllers or minimal API endpoints directly in F#, using Giraffe — a lightweight functional web framework that models request handling as composable functions — or using Saturn, which layers a higher-level, more opinionated DSL on top of Giraffe for teams who want an MVC-like structure. On the frontend side, Fable compiles F# to JavaScript, letting teams share domain types and validation logic between server and browser instead of maintaining duplicate models in two languages.

🏏

Cricket analogy: A cricket academy might let a batter train under the standard national coaching curriculum, a specialized spin-bowling clinic, or a full boarding program with structured routines — three different levels of scaffolding for the same goal, just as F# web development offers plain ASP.NET Core controllers, the lightweight Giraffe framework, or the more structured Saturn framework.

Giraffe: Functional HTTP Handlers

Giraffe represents each unit of request handling as an HttpHandler, a function of type HttpFunc -> HttpContext -> HttpFuncResult, and composes handlers with the >=> (Kleisli composition) operator so that route "/users" >=> GET >=> handleGetUsers reads as 'match this route, then this verb, then run this handler,' short-circuiting to the next branch if any step fails to match. This composition-first design means routing, content negotiation, authentication checks, and business logic are all just functions combined with the same operator, avoiding the inheritance-heavy controller base classes that traditional ASP.NET Core MVC relies on.

🏏

Cricket analogy: A fielding captain sets a field plan as a sequence of conditions — if it's a left-hander, then move point squarer, then check for the reverse sweep — short-circuiting to the next check if the batter isn't left-handed, exactly like Giraffe's route "/users" >=> GET >=> handleGetUsers chaining match-then-match-then-handle.

Saturn: A Structured Framework on Giraffe

Saturn adds computation expressions like application { }, router { }, and controller { } on top of Giraffe's primitives, giving teams a more familiar, convention-based structure — a controller { } block can define index, show, create, and update members that map to RESTful routes automatically, similar to Rails or ASP.NET Core MVC controllers but expressed as F# computation expressions rather than class inheritance. This makes Saturn a good fit for teams migrating from MVC-style frameworks who still want F#'s type safety and Giraffe's composable HttpHandler model underneath.

🏏

Cricket analogy: A franchise league that adopts the BCCI's standard tournament format — fixed group stages, playoff brackets, and net-run-rate tiebreakers — instead of inventing its own rules from scratch mirrors how Saturn's controller { } gives F# developers RESTful conventions (index, show, create, update) instead of manually wiring every Giraffe route.

Fable and Shared Domain Types

Fable compiles a subset of F# to readable JavaScript, and the most common pattern pairs it with the Elmish library implementing the Model-View-Update (MVU) architecture popularized by Elm: a Model type, an update: Msg -> Model -> Model function, and a view: Model -> Dispatch<Msg> -> ReactElement function drive the whole UI as a pure, testable state machine. Because both the Fable frontend and the ASP.NET Core/Giraffe backend are F#, a single project can define a DTO or validation function once in a shared project and reference it from both client and server code, eliminating an entire class of drift between frontend and backend models.

🏏

Cricket analogy: A single official scorecard format used identically by the stadium's big screen, the TV broadcast graphics, and the official app avoids three different teams maintaining three different scoring systems, just as a shared F# project lets both the Fable frontend and the Giraffe backend reference the exact same DTO definitions.

fsharp
open Giraffe
open Microsoft.AspNetCore.Http

type User = { Id: int; Name: string }

let getUserHandler (id: int) : HttpHandler =
    fun (next: HttpFunc) (ctx: HttpContext) ->
        task {
            let user = { Id = id; Name = "Ada Lovelace" }
            return! json user next ctx
        }

let webApp : HttpHandler =
    choose [
        route "/health" >=> GET >=> text "OK"
        routef "/users/%i" (fun id -> GET >=> getUserHandler id)
        RequestErrors.NOT_FOUND "Not found"
    ]

Giraffe's HttpHandler is 'just a function,' which means you can unit test a handler by calling it directly with a fake HttpContext and asserting on the response — no in-memory test server or web host required for most business-logic tests, unlike testing a traditional MVC controller action in isolation.

Fable does not compile arbitrary .NET libraries to JavaScript — only F# source (and a curated set of Fable-compatible packages) can run in the browser. Reaching for a server-only library like Entity Framework Core or System.IO.File inside Fable code will fail to compile, so shared projects should stick to plain data types and pure functions with no BCL I/O dependencies.

  • F# can build ASP.NET Core apps directly, or via Giraffe (functional, composable HttpHandlers) or Saturn (higher-level DSL on top of Giraffe).
  • Giraffe's HttpHandler is a plain function composed with the >=> operator for routing, verb matching, and business logic.
  • Saturn's controller { } and router { } computation expressions give a convention-based, MVC-like structure over Giraffe's primitives.
  • Fable compiles F# to JavaScript, commonly paired with Elmish's Model-View-Update (MVU) architecture.
  • A shared F# project can define DTOs and validation logic once, referenced by both a Fable frontend and a Giraffe/ASP.NET Core backend.
  • Giraffe handlers can be unit tested as plain functions without spinning up an in-memory web host.
  • Fable only compiles F# source and Fable-compatible packages — server-only BCL APIs like File I/O won't compile for the browser.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#FStudyNotes#FAndWebDevelopment#Web#Development#Options#Building#WebDevelopment#StudyNotes#SkillVeris