Introduction to Web Programming in F# with Giraffe — Part 2

Introduction

In this post, we will creating a simple API. In later posts, we will delve a lot deeper into Giraffe but this will be a gentle but useful start.
If you haven't already done so, read the first post in this series.

Getting Started

Ideally, you should have .NET 5 SDK installed. If you haven't, it will still work for .NET 3.1 SDK. I suggest that you use VSCode with the ionide F# extension. They work on Windows, MacOS and Linux.

Create a new folder called GiraffeApi and open it in VSCode.

Using the Terminal in VSCode type in the following command to create the project:

dotnet new console -lang F#

After a few seconds, the ionide extension will spring to life. When it does, add the following NuGet packages from the terminal:

dotnet add package Giraffe -v 5.0.0-rc-6

open Program.fs and replace the code with the code from this gist:

https://gist.github.com/ianrussellsoftwarepark/8b02b1e07c65e956d921eac882d08f2b

Running the Sample Code

In the Terminal, type the following to run the project:

dotnet run

Go to your browser and type in the following Url:

https://localhost:5001

You should see some text.

Now try the following Uri and you should see some Json returned:

https://localhost:5001/api

You will need a tool to run HTTP calls (GET, POST, PUT, and DELETE). I use Postman but any tool including those available in the IDEs will work.

Our Task

We are going to create a simple API that we can view, create, update and delete Todo items.

Data

Rather than work against a real data store, we are going to create a simple store with a dictionary and use that in our handlers.

Create a new file above Program.fs called TodoStore.fs and add the following code to it:

module Todos

open System
open System.Collections.Concurrent

type TodoId = Guid

type NewTodo = {
    Description: string
}

type Todo = {
    Id: TodoId
    Description: string
    Created: DateTime
    IsCompleted: bool
}

type TodoStore() =
    let data = ConcurrentDictionary()

    member _.Create todo = data.TryAdd(todo.Id, todo)
    member _.Update todo = data.TryUpdate(todo.Id, todo, data.[todo.Id])
    member _.Delete id = data.TryRemove id
    member _.Get id = data.[id]
    member _.GetAll () = data.ToArray()

This is not production code. TodoStore is a simple class type that wraps a concurrent dictionary that we can test our API out with. It will not persist between runs. To plug it in, we need to make a change to configureServices in Program.fs:

let configureServices (services : IServiceCollection) =
    services.AddGiraffe()
            .AddSingleton(TodoStore()) |> ignore
let webApp =
    choose [
        GET >=> route "/" >=> htmlView todoView
        subRoute "/api"
            (choose [
                GET >=> route "" >=> json { Response = "ToDo List API" }
                GET >=> routef "/%s" sayHelloNameHandler
            ])
        setStatusCode 404 >=> text "Not Found"
    ]

We will hit the route that calls the sayHelloNameHandler handler. If we call a POST, no routes will match, so we will fall through to the last line and return a 404 - Not found.

The routes we need to add are:

GET /api/todo // Get a list of todos
GET /api/todo/id // Get one todo
PUT /api/todo // Create a todo
POST /api/todo // Update a todo
DELETE /api/todo/id // Delerte a todo

Let's create our routes with the correct HTTP verbs:

let apiTodoRoutes : HttpHandler =
    subRoute "/todo"
        (choose [
            GET >=> choose [
                routef "/%O" Handlers.viewTaskHandler
                route "" >=> Handlers.viewTasksHandler
            ]
            POST >=> route "" >=> Handlers.updateTaskHandler
            PUT >=> route "" >=> Handlers.createTaskHandler
            DELETE >=> routef "/%O" Handlers.deleteTaskHandler
        ])

let webApp =
    choose [
        GET >=> route "/" >=> htmlView todoView
        subRoute "/api"
            (choose [
                apiTodoRoutes
                GET >=> route "" >=> json { Response = "ToDo List API" }
                GET >=> routef "/%s" Handlers.sayHelloNameHandler
            ])
        setStatusCode 404 >=> text "Not Found"
    ]

Our todo routes live under the api subroute. We need to put the apiTodoRoutes route handler before the existing ones because the sayHelloNameHandler will be selected instead of our GET /api/todos route and return:

Next we have to implement the new handlers.

Handlers

Create a module above the routes called Handlers and add the sayHelloNameHandler function to it.

module Handlers =
    let sayHelloNameHandler (name:string) =
        fun (next : HttpFunc) (ctx : HttpContext) ->
            task {
                let msg = sprintf "Hello, %s" name
                return! json { Response = msg } next ctx
            }

Let's add the GET routes to our module:

let viewTasksHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            let store = ctx.GetService()
            let todos = store.GetAll()
            return! json todos next ctx
        }

let viewTaskHandler (id:Guid) =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            let store = ctx.GetService()
            let todo = store.Get(id)
            return! json todo next ctx
        }

We are using the context (ctx) to gain access to the TodoStore instance we set up earlier.

Let's add the remaining handler for create, update and delete:


 
let createTaskHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            let! newTodo = ctx.BindJsonAsync()
            let store = ctx.GetService()
            let created = store.Create({ Id = Guid.NewGuid(); Description = newTodo.Description; Created = DateTime.UtcNow; IsCompleted = false })
            return! json created next ctx
        }

let updateTaskHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            let! todo = ctx.BindJsonAsync()
            let store = ctx.GetService()
            let created = store.Update(todo)
            return! json created next ctx
        }

let deleteTaskHandler (id:Guid) =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            let store = ctx.GetService()
            let existing = store.Get(id)
            let deleted = store.Delete(KeyValuePair(id, existing))
            return! json deleted next ctx
        }

The most interesting thing here is that we use a built-in function to gain strongly-typed access to the body passed into the handler.

Using the API

Run the app and use a tool like Postman to work with the API.

To get a list of all Todos, we call GET /api/todo. This should return an empty json array.

We create a Todo by calling PUT /api/todo with a json body like this:

You will receive a reponse of true. If you now call the list again, you will receive a json response like this:

I'll leave the other routes for you to investigate.

The code for this post is available here:

https://gist.github.com/ianrussellsoftwarepark/f1c0815efe309ee6dd4bebf397d75f8d

Summary

I hope that you found this post in the Introduction to Web Programming in F# with Giraffe series useful and interesting. We have only scratched the surface of what is possible with Giraffe for creating APIs.

In the next post we will start to investigate HTML views with the Giraffe View Engine.

If you have any comments on this series of posts or suggestions for new ones, send me a tweet (@ijrussell) and let me know.

Blog 3/10/21

Introduction to Web Programming in F# with Giraffe – Part 1

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 3/12/21

Introduction to Web Programming in F# with Giraffe – Part 3

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 5/18/22

Introduction to Functional Programming in F#

Dive into functional programming with F# in our introductory series. Learn how to solve real business problems using F#'s functional programming features. This first part covers setting up your environment, basic F# syntax, and implementing a simple use case. Perfect for developers looking to enhance their skills in functional programming.

Blog 11/30/22

Introduction to Partial Function Application in F#

Partial Function Application is one of the core functional programming concepts that everyone should understand as it is widely used in most F# codebases.In this post I will introduce you to the grace and power of partial application. We will start with tupled arguments that most devs will recognise and then move onto curried arguments that allow us to use partial application.

Blog 10/11/22

Introduction to Functional Programming in F# – Part 5

Master F# asynchronous workflows and parallelism. Enhance application performance with advanced functional programming techniques.

Blog 10/1/22

Introduction to Functional Programming in F# – Part 4

Unlock F# collections and pipelines. Manage data efficiently and streamline your functional programming workflow with these powerful tools.

Blog 8/8/23

Introduction to Functional Programming in F# – Part 12

Explore reflection and meta-programming in F#. Learn how to dynamically manipulate code and enhance flexibility with advanced techniques.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 9

Explore Active Patterns and Computation Expressions in F#. Enhance code clarity and functionality with these advanced techniques.

Blog 7/12/23

Introduction to Functional Programming in F# – Part 11

Learn type inference and generic functions in F#. Boost efficiency and flexibility in your code with these essential programming concepts.

Blog 9/13/22

Introduction to Functional Programming in F# – Part 2

Explore functions, types, and modules in F#. Enhance your skills with practical examples and insights in this detailed guide.

Blog 9/15/22

Introduction to Functional Programming in F# – Part 3

Dive into F# data structures and pattern matching. Simplify code and enhance functionality with these powerful features.

Blog 5/17/23

Introduction to Functional Programming in F# – Part 10

Discover Agents and Mailboxes in F#. Build responsive applications using these powerful concurrency tools in functional programming.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 7

Explore LINQ and query expressions in F#. Simplify data manipulation and enhance your functional programming skills with this guide.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 6

Learn error handling in F# with option types. Improve code reliability using F#'s powerful error-handling techniques.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 8

Discover Units of Measure and Type Providers in F#. Enhance data management and type safety in your applications with these powerful tools.

Blog 8/10/22

So, I wrote a book

Join me as I share the story of writing a book on F#. Discover the challenges, insights, and triumphs along the way.

Blog 9/17/21

How to gather data from Miro

Learn how to gather data from Miro boards with this step-by-step guide. Streamline your data collection for deeper insights.

Blog 7/14/23

Event Sourcing with Apache Kafka

For a long time, there was a consensus that Kafka and Event Sourcing are not compatible with each other. So it might look like there is no way of working with Event Sourcing. But there is if certain requirements are met.

Blog 5/1/21

Ways of Creating Single Case Discriminated Unions in F#

There are quite a few ways of creating single case discriminated unions in F# and this makes them popular for wrapping primitives. In this post, I will go through a number of the approaches that I have seen.

Blog 12/3/21

Using Discriminated Union Labelled Fields

A few weeks ago, I re-discovered labelled fields in discriminated unions. Despite the fact that they look like tuples, they are not.