Build a Simple Single-File Web API with F# / Giraffe

Date: 2024-09-23 | create | tech | fsharp | giraffe | dotnet | webapp |

In this post we're going to build a minimal single-file web API using F# / Giraffe.

This will serve as a good entrypoint into how F# / Giraffe works and can be used as a basis for building your own web apps with F#.

F# / Giraffe Overview

Giraffe is a functional-first micro web framework that plays nice with F#'s functional-first nature. It is a thin wrapper on top of dotnet's ASP.NET web framework (most often used with C#) so you get all of its features and reliability without needing to use C#.

We won't go into too much detail about how Giraffe itself works so if you want to learn more, checkout:

Single-File F# / Giraffe Web API

Here we're building a very minimal API. It has 4 endpoints:

  • GET /user/INPUT -> Returns INPUT
  • GET / -> Returns ""
  • GET /get-length/INPUT -> Returns length of INPUT
  • POST /user -> Returns ""

The idea is to give you a basic idea of how to build endpoints rather than showcase all possibilities.

Note: This is the same basic setup that runs F# / Giraffe's web frameworks benchmark candidate (code) (benchmark) (benchmark requirements)

We declare these Giraffe endpoints, their handlers, and plug them into the core ASP.NET web service in our single file - Program.fs.

Program.fs

open System

open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.Logging
open Giraffe
open Giraffe.EndpointRouting

// ---------------------------------
// Web app
// ---------------------------------

let getStringLengthHttpHandler 
    (s: string)
    =
    handleContext( fun (ctx: HttpContext) -> 
        task {
            let length = s.Length

            return! ctx.WriteTextAsync (string length)
        }
    )

let webApp =
    [ 
        GET [ 
            routef "/user/%s" text
            route "/" (text "") 
            routef "/get-length/%s" (
                fun s -> getStringLengthHttpHandler s
            )        
        ]
        POST [ 
            route "/user" (text "") 
        ] 
    ]

// ---------------------------------
// Config and Main
// ---------------------------------

let configureApp (app: IApplicationBuilder) =
    app
        .UseRouting()
        .UseEndpoints(fun e -> e.MapGiraffeEndpoints(webApp))
    |> ignore

let configureServices (services: IServiceCollection) = 
    services.AddRouting() |> ignore

    // Add Giraffe dependencies
    services.AddGiraffe() |> ignore

Host
    .CreateDefaultBuilder()
    .ConfigureWebHost(fun webHost ->
        webHost
            .UseKestrel(fun c -> c.AddServerHeader <- false)
            .ConfigureServices(configureServices)
            .Configure(configureApp)
        |> ignore)
    .Build()
    .Run()
  • webApp - Declares the Giraffe endpoints and their functionality
  • configureApp - Configures endpoints into the web app
  • configureServices - Tells ASP.NET it needs to use routing and Giraffe
  • Host - Tells ASP.NET how it should run

For the most part you can use this configuration code as boilerplate as-is. You will need to change this in the future for some edge cases but this is a decent default for most webapps.

In order for this file to be understood and run by dotnet, we need to declare an .fsproj which basically declares all dependencies and how to build / run the app.

web.fsproj

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <DebugType>portable</DebugType>
    <AssemblyName>web</AssemblyName>
    <OutputType>Exe</OutputType>
    <EnableDefaultContentItems>false</EnableDefaultContentItems>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Update="FSharp.Core" Version="8.0.*" />
    <PackageReference Include="Giraffe" Version="7.0.*" />
  </ItemGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
  </ItemGroup>

</Project>

With both of these files present, we can run the app with dotnet run (assuming you've installed dotnet on your system).

Full Project Source Code (github) is available to HAMINIONs members - along with dozens of other example projects from guides like this one.

Next

That should get you started with F# / Giraffe.

If you're curious what a real-world fullstack F# / Giraffe web app looks like - CloudSeed (my F# project boilerplate) gets you setup with a fullstack webapp in 10 minutes.

If you liked this post you might also like:

Want more like this?

The best / easiest way to support my work is by subscribing for future updates and sharing with your network.