United Kingdom: +44 (0)208 088 8978

Why we love SAFE Stack – Giraffe: the basics

Continuing the series on our favourite parts of SAFE Stack, we have a look at Giraffe, the core of the server in SAFE stack

We're hiring Software Developers

Click here to find out more

Two weeks ago, we had a look at Fable remoting: the bridge between the server and client in SAFE Stack. In this blog post, we'll have a closer look at Giraffe, the F# server framework used by SAFE.

Being a functional layer on top of ASP.NET Core, Giraffe provides a lean alterative in a world of magical OOP frameworks inspired by Ruby On Rails, such as ASP.NET Core MVC. HTTP has a very simple flow: you receive a request, and return a response. In Giraffe, you write HttpHandlers to take in the information from the request, and build up the appropriate response. We do this by passing through a HttpContext object.

The anatomy of a HTTP handler

Check out the signature of the HttpHandler type below:

type HttpFuncResult = Task<HttpContext option>
type HttpFunc = HttpContext -> HttpFuncResult
type HttpHandler = HttpFunc -> HttpContext -> HttpFuncResult

Let's look at these elements individually, starting at the end.

The return value: HttpFuncResult

The return value of a HttpHandler is an HttpFuncResult. This is a task, as requests are dealt with asynchronously. More interestingly, it's an option: the handler has the opportunity to return None, signalling that it can not handle the request. An example of a handler using this possibility is the route handler. (I slighly modified it here to make it more explicit what it does).

let route (path : string) : HttpHandler =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        if (SubRouting.getNextPartOfPath ctx).Equals path
        then next ctx
        else task {return None}

If the route matches, we let whatever handler is next (more about that later) handle the request. Otherwise, we'll return None: This signals we were not able to deal with the request, but still allows other handlers to give it a go.

The context is provided as one of the arguments, but we don't have to pass it through directly: it's the way we can manipulate the response returned by this handler. For example, we can set the response body, as done by the text handler.

The second argument: The HTTP context

The HTTP context is our main source of information when handling the request. As you can see in the example above, it gives us access to the path the request was made to, but it holds many other things, such as the session, the HTTP response that is being built up etc.

The first argument: the next function

To understand the first argument, it's important to realize that Giraffe revolves around composing handlers using the >=> operator. Check out the following example using the route function, and the text function which sets the response body to the supplied string. The required outcome here is that only the route "/helloWorld" returns the text.

route "/helloWorld" >=> text "Hey there!"

The >=> operator passes the text handler to the route handler as an argument in curried form. You might be wondering what was passed into the text handler as the next handler: at the end of the pipeline, Giraffe inserts its own special handler, that makes sure the request is dealt with appropriately.

Back to the next argument in the route handler: as you can see, in case the route matches, the handler simply calls the provided next argument, passing it the context. Because the HttpFunction and HttpHandler have the same return type, this simply works, and you'll notice that a lot of handlers will end by calling the next handler this way.

Giraffe in practice

Let's extend a SAFE app with a simple Giraffe-based API

let myPublicApi =
    subRoute
        "/myApi"
        (choose [
            subRoute "/helloWorld" GET >=> (text "Hello there!")
            subRoute "/bye"  PUT >=> 
                choose [
                    subRoutef "/%s" byeHandler
                    byeHandler "Everyone"
                ]
            setStatusCode 400 >=> text "Whoops! We don't know that one!"
        ])

The choose handler lets each handler in a list attempt to handle the request, until one of them returns a response. Let's use a very simple choose handler to add our new api to the webApp. Note that the application CE we are updating here is NOT part of Giraffe, but is part of Saturn, which will be described more in-depth in another post in this series.

let app = application {
    use_router  (choose [webApp; myPublicApi])
    memory_cache
    use_static "public"
    use_gzip
}

The app will now try to handle all requests by the webApp (the default Fable.Remoting API that SAFE ships with) first. If that fails, myPublicApi gets a go.

Let's have a closer look at the API we just created. The subRoute handler allows us to filter on the first part of the route of the api. The second time you call it in the same handler, it matches on the next part of the route, skipping the part you already filtered on. This makes it very easy to create complex routes.

Another interesting example of choose is the one handling our "bye" route. If we provide a name, we give a specific goodbye message. Otherwise we go for a generic "everyone". Order matters here! If we flipped around the two routes provided to choose, providing the name would not work anymore.

The GET and PUT handlers filters requests on their HTTP method. You can insert them at any point in the chain. Here we use them to control the methods we expect on specific endpoints, but if your whole API expects only GET requests, you can put the handler at the very beginning of the chain.

Conclusion

As you can see, Giraffe provides us with some pretty neat and simple handlers, that we can combine to define complex behavior. Check out the docs for all the nifty things Giraffe can do for you! Since handlers have a standard form that we can combine with >=>, you'll find that learning how to use the different handlers is a breeze. Now that you are familiar with the structure of a HttpHandler, writing your own should not be a problem either!

Giraffe and provides it's own templates for dotnet new, so it's also very easy to start a project with it outside of the SAFE ecosystem.