So you want to build a website and, since you are a god amongst men, you want to use F#. Good, then I don’t have to tell you how excellent Nancy is, however, if you’ve used it with F# you’ve probably winced a little:

type Module() as this =
  inherit NancyModule()

  do
    this.Get.["/"] <- fun p -> this.View.["Home"] |> box

This upsets me, so let’s start again with what we’d like to see:

let get() = View "Home"

And we just need a Response type:

type Response =
    | View of string

Easy. That view name is a source of concern, one could easily mistype that. Thankfully we have the FSharp.Management file type provider to aid us:

type Views = RelativePath< ".\\Views", watch=true >

Let’s try again:

let get() = View Views.``Home.cshtml``

Nice. Next we need to handle view models, which are optional, so let’s update the Response:

type Response<'a> =
  | View of string * 'a option

let get() = View(Views.``Home.cshtml``, None)

Next, those dynamic parameters. F# has the ? operator for just this job, and we can clean up those dirty nulls:

let (?) (p : obj) prop =
    let ddv = (p :?> DynamicDictionary).[prop] :?> DynamicDictionaryValue
    match ddv.HasValue with
    | false -> None
    | _ -> ddv.TryParse<'a>() |> Some

This is much more pleasing, and we can build up a testable library of functions without even thinking too much about Nancy. Here are some example Modules.

Next we just have to wire them up. Fear not, I’ve written the boiler plate for you so we just have to do a bit of composition:

type Routes() as this =
  inherit NancyFsModule()
  do
    (fun _ -> Home.get()) |> this.CreateRoute GET "/"
    (fun _ -> this.Bind<NameModel>() |> Home.post) |> this.CreateRoute POST "/"
    (fun _ -> this.Request.Query?name |> About.get) |> this.CreateRoute GET "/about"
    StaticFile.get |> this.CreateRoute GET "/{file}"
    (fun p -> p?redirect |> Redirect.get) |> this.CreateRoute GET "/redirect/{redirect}"

Async? Got you covered there too, just return an async from your function and use:

this.CreateAsyncRoute GET "/route"

In this case the function will also require a cancellationToken parameter.

Before you embark on this adventure, ensure you have F# Power Tools installed for folder creation and manipulation, and you’ve installed these registry extensions so you can create files. Now clone this and get going:

https://github.com/tinybluerobots/nancyfs

Have a look at Nancy.fs and you’ll see the Response type with a few implementations; simply adjust to your needs. NancyFsModule is the carpet under which we are sweeping all the unpleasantness, and the Nancify method is where we convert the Response into something Nancy can consume.

We’re using this in production, and with FsPickler and fszmq to talk to the back end we get the benefit of F# everywhere.

Let me know what you think and if you can improve on what we’ve done.