Suave bootstrapper upgraded
Why am I doing this?
I recently started building a web API at work. I am building it in F# as a .Net Web API project. This is nice and all but not wholly a functional programming kind of thing. So I thought I would try Suave.io, which is F# all the way through and encourages a functional style, and all the cool kids are doing it.
The bootstrapper
Good ol' Scott Hanselman put together a Suave bootstrapper. With this bootstrapper you can launch your own Suave website in Azure in a few clicks. It works great, but the versions of Suave and FSharp are out of date. It is basically what you would get if you followed these instructions Deploying Suave to Azure App Service
It's a nice and simple bootstrapper that I worked with in VS Code, pretty easily.
The bootstrapper uses some cool things, like:
- Paket for managing packages
- FAKE for the build. I love FAKE and we use it at work for our build scripts.
- Kudu Sync for auto-syncing with Azure. This makes it so you can update your repo and it automatically deploys to Azure.
The upgrade
I forked the suavebootstrapper. You can find it here.
All I had to do was change the package versions in packet.lock file to the latest that I want to use. To get the libraries updated, I ran deploy.cmd in the cmd terminal in VS Code.
Running in VS Code
It took me a bit to figure out how to run the Suave web server in VS Code. I did figure it out and added a section to my repo's README.
You'll need to install the Ionide tooling for this to work.
See this Getting Started with VS Code
- Open site/webserver.fsx
- Select all of the file to highlight it
- Hit Alt + Enter
- You should see a bunch of stuff happening in the TERMINAL tab
- Go to http://localhost:8083
Pro tip: If you have an empty line at the end of the file, you might find the web server won't start for some reason. So make sure to delete any empty lines at the end of the file.
Suave routing example
Once I got the web server up and running I started trying out other things. I want to build a REST API so I started with HTTP routing. The file webserverwithRoutes.fsx shows an example. The documentation was helpful here.
Let's go through the anatomy of my web server code.
reference Fake and Suave
#r "../packages/FAKE/tools/FakeLib.dll"
#r "../packages/Suave/lib/net40/Suave.dll"
open the namespaces we want to use
open Fake
open Suave
open System.Net
open Suave.Successful
open Suave.Operators
open Suave.Filters
configure the web server
This makes our webserver run on listens on port 8083
let serverConfig =
let port = getBuildParamOrDefault "port" "8083" |> Sockets.Port.Parse
{ defaultConfig with bindings = [ HttpBinding.create HTTP IPAddress.Loopback port ] }
setup the routes
choose does some cool stuff for us in a neat functional way. On each request choose will decide if the action is a GET or a POST and the path/resource. Then it will return something called a WebPart.
This means that WebPart is actually a function, which takes objects of type HttpContext as its first argument and returns an "asynchronous workflow" of HttpContext option.
Here is the Hello World!
let app =
choose [
GET >=> choose
[
path "/hello" >=> OK "Hello GET"
]
POST >=> choose
[
path "/hello" >=> CREATED "Hello POST"
]
]
app is type WebPart
I really like this for some reason and it reminds me of how I setup routes in Falcon Framework (which I highly recommend).
OK and CREATED are functions that take a string, the response, and return a WebPart. There are a number of functions available that represent the HTTP response codes.
start the web server
startWebserver is a function that takes a config and WebPart
startWebServer serverConfig app
Putting it all together we get this
// --------------------------------------------------------------------------------------
// Start up Suave.io
// --------------------------------------------------------------------------------------
#r "../packages/FAKE/tools/FakeLib.dll"
#r "../packages/Suave/lib/net40/Suave.dll"
open Fake
open Suave
open System.Net
open Suave.Successful
open Suave.Operators
open Suave.Filters
let serverConfig =
let port = getBuildParamOrDefault "port" "8083" |> Sockets.Port.Parse
{ defaultConfig with bindings = [ HttpBinding.create HTTP IPAddress.Loopback port ] }
let app =
choose [
GET >=> choose
[
path "/hello" >=> OK "Hello GET"
]
POST >=> choose
[
path "/hello" >=> CREATED "Hello POST"
]
]
startWebServer serverConfig app
Thoughts
Working with Suave is, uhm, kind of hard. The documentation isn't bad but it isn't great either, and the entire API doesn't seem to be documented. I should probably contribute some documentation. I was able to figure stuff out just by using Visual Studio to explore, though. I guess this isn't so bad because Suave is intended to be really simple, and it does seem like it is.
I really like writing in a functional style.
I like simple things. There is no boilerplate. It's almost disconcerting. But. Hey, that's F#!
It took me a while to figure out this simple example, but I think I am starting to get how this works. Hopefully this post will help people who are struggling like me. I haven't found any examples using current versions of Suave. They were all in sub 1.0 so that was a little frustrating. Maybe my Google-fu is broken. Hopefully this little experiment of mine will help out others who were struggling like me.
The deploy to Azure thing is pretty rad and the auto-syncing with a Webhook to Azure andth Kudu was really exciting.
Full Stack .NET Programmer and Ham