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

Introduction

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

According to the website (https://github.com/giraffe-fsharp/Giraffe), Giraffe is "an F# micro web framework for building rich web applications" and was written by Dustin Moris Gorski (https://twitter.com/dustinmoris).

Giraffe is a thin functional wrapper around AspNetCore. If you want something more opinionated and want to use F# everywhere, have a look at Saturn (https://saturnframework.org/) and the Safe Stack (https://safe-stack.github.io/).

Getting Started

I suggest that you use VSCode with the ionide F# extension. They work on Windows, MacOS and Linux.

Create a new folder called GiraffeExample 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
dotnet add package Giraffe.ViewEngine

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

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

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 Urls and you should see some Json:

https://localhost:5001/api
https://localhost:5001/api/Ian [Replace my name with yours]

Reviewing the Code

Start with the main function. If you've done any AspNetCore, this will look very familiar because Giraffe is a thin functional wrapper over AspNetCore. Everything you can use in C# is useable in F# for configuration.

The interesting parts will be the routing, handlers and views.

Views

The GiraffeViewEngine is a DSL that generates HTML.

let indexView =
    html [] [
        head [] [
            title [] [ str "Giraffe Sample" ]
        ]
        body [] [
            h1 [] [ str "I |> F#" ]
            p [ _class "some-css-class"; _id "someId" ] [
                str "Hello World"
            ]
        ]
    ]

Each element like html has the following structure:

html [] []

Each element has two supporting lists: The first is for attributes and the second for data. You may wonder why we need a DSL to generate HTML but it makes sense as it helps prevent badly formed structures. All of the features you need like master pages, partial views and model binding are included. We will have a deeper look at views in the third post in this series.

Routes

Rather than use MVC-style controller routing, Giraffe uses individual route handlers.

let webApp =
    choose [
        GET >=> choose [
            route "/" >=> htmlView indexView
            subRoute "/api"
                (choose [
                        route "" >=> json { Response = "Hello world!!" }
                        routef "/%s" sayHelloNameHandler
                ])
        ]
        setStatusCode 404 >=> text "Not Found"
    ]

The Fish operator (>=>) [compose combinator] allows the routing to support the pipelining of handlers on a route by composing two HttpHandlers together. The choose combinator tries to match the incoming request with a route.

We can easily move sets of routes to their own handler and include it in the main routing structure like this:

let apiRoutes : HttpHandler =
    subRoute "/api"
        (choose [
            GET >=> choose [
                route "" >=> json { Response = "Hello world!!" }
                routef "/%s" sayHelloNameHandler
            ]
        ])

let webApp =
    choose [
        GET >=> choose [
            route "/" >=> htmlView indexView
        ]
        apiRoutes
        setStatusCode 404 >=> text "Not Found"
    ]

We will look at using different types of middleware over the course of the next three posts.

Handlers

The handlers are usually the functions that do the work. Notice that the querystring item is automatically bound to the handler function if we have a matching input parameter of the same type.

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

This function could be written like the following but it is convention to use the style above.

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

The task {} is a Computation Expression of type System.Threading.Tasks.Task<'T data-preserve-html-node="true">. F# has another asynchronous feature called async but we won't use that as Giraffe has chosen to work directly with Task instead.

You will see a lot more of task {} in the next post in this series as we expand the API side of this site.

Summary

I hope that you found this introduction to web programming in F# with Giraffe useful and interesting. We have only scratched the surface of what is possible with Giraffe.

In the next post we will expand the API side of the application.

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

Zurück
Zurück

Understanding F# applicatives and custom operators

Weiter
Weiter

Introduction to Functional Programming in F# – Part 11