In the last one month lot of great things happening in F# world around Suave, a simple web development F# library. Scott Hanselman blogged about it, Tomas Petricek kick-started an awesome hands on session and followed up with a great talk on Channel 9. Last but not the least, Tomasz Heimowski authored a clear and crisp book on Suave.
I got super excited after learning suave from these resources and started playing with it. One of the great things about the F# community is, if you want to improve any existing library all you need is just send a pull request with the feature you would like to have. Yes, it’s as simple as that! It worked for me and I am sure for you too, if you wish.
In this blog post, you are going to learn how to build a REST api using suave. The REST api that we are going to create here follows the standard being used in the StrongLoop, a node.js REST api library. To keep things simple, we are not going to see validation and error handling as part of this post and I will be covering that in an another post.
Let’s get started
Setting up the project
Create a new “F# Console Application” project in Visual Studio with the name SuaveRestApi
and rename Program.fs to App.fs. This file is going to contain the application bootstrap logic. Add two more files Db.fs and RestFul.fs which would contain database access and restful api implementation code respectively. Ensure these files are in the order as shown in below.
After creating, install the following nuget packages
WebPart
The basic building block of Suave is WebPart
. It is an alias of function type HttpContext -> Async<HttpContext option>
. This simple function type actually model whole set of Request and Response model of Http protocol. From the Tomasz Heimowski’s book here is the definition of the WebPart
Based on the http context, we give you a promise (async) of optional resulting http context, where the resulting context is likely to have its response set with regards to the logic of the WebPart itself
A WebPart represents a HTTP request and response. Since it is a function, we can combine multiple WebParts together and build a complex web application which handles multiple requests and responses. This is the beauty of functional programming.
You just think in terms of one function at a time and make it work. Once you are done, all you need to do is just gluing them together. Solving problems using this functional abstraction will make things much easier and help you to write less code compare to its Object Oriented version.
The OK
WebPart is a very simple one in Suave which takes a string
and returns a HTTP response with the status code 200
and the given string
. Add the following code in the App.fs and run the console app.
open Suave.Web
open Suave.Successful
[<EntryPoint>]
let main argv =
startWebServer defaultConfig (OK "Hello, Suave!")
0
The is the simplest Suave application that greets all visitors with the string “Hello, Suave!”. The startWebServer
is a blocking function that takes a configuration (port number, ssl, etc.,) and starts a http web server in the port 8083
upon calling.
The Rest Api that we are going to design here is a WebPart
which will be replacing this OK
webpart.
HTTP GET
Let’s begin by defining a record type representing a restful resource. Open Restful.fs and update it as below
namespace SuaveRestApi.Rest
[<AutoOpen>]
module RestFul =
type RestResource<'a> = {
GetAll : unit -> 'a seq
}
This RestResource
is a container representing all the operations on a restful resource. This record type abstracts the actual resource from the rest api web part. This enables the reuse of rest api web part across multiple resources.
Let’s create a function in RestFul.fs which takes a resource name and this RestResource
and returns a WebPart
representing the restful api.
// string -> RestResource<'a> -> WebPart
let rest resourceName resource =
// TODO
We are going to use the following building blocks of the suave library to implement the rest
function.
The
path
is a function of type:string -> WebPart
. It means that if we give it a string it will returnWebPart
. Under the hood, the function looks at the incoming request and returnsSome
if the paths match, andNone
otherwise.The
GET
is a static inbuiltWebPart
which matches the HTTP GET requests.The
>=>
operator composes two WebParts into one by first evaluating theWebPart
on the left, and applying theWebPart
on the right only if the first one returnedSome
.
To return a json response we need to change the mime type to application/json
of the response. There is an inbuilt function in Suave to do this but it is using .Net’s DataContractJsonSerializer
. I feel it’s not ideal to use as we need to write decorator attribute DataMember
to serialize the types.
As Newtonsoft.Json provides serialization support for fsharp types without any need of decorating attributes, we will be using them there.
// 'a -> WebPart
let JSON v =
let jsonSerializerSettings = new JsonSerializerSettings()
jsonSerializerSettings.ContractResolver <- new CamelCasePropertyNamesContractResolver()
JsonConvert.SerializeObject(v, jsonSerializerSettings)
|> OK
>=> Writers.setMimeType "application/json; charset=utf-8"
As the signature indicates JSON
function takes a generic type, serialize it using Newtonsoft.Json
and return the JSON response. Writers.setMimeType
takes a mime type and a WebPart
returns the WebPart
with the given mime type.
Now we have everything to implement the HTTP GET request
let rest resourceName resource =
let resourcePath = "/" + resourceName
let gellAll = resource.GetAll () |> JSON
path resourcePath >=> GET >=> getAll
The one caveat here is the path resolution of Suave library. Based on the webpart given, the startWebServer
function configures it’s internal routing table during the application startup and it is static after the application has been started.
So, the getAll
WebPart
will be created only during application startup. To avoid this, we need to wrap the getAll webpart with a warbler
function which ensures that it is called only when the incoming request matches the given resource path.
let rest resourceName resource =
let resourcePath = "/" + resourceName
let gellAll = warbler (fun _ -> resource.GetAll () |> JSON)
path resourcePath >=> GET >=> getAll
Now we have the middleware to create a web part. Let’s create other things and glue them together.
Open Db.fs file and add the following code
namespace SuaveRestApi.Db
open System.Collections.Generic
type Person = {
Id : int
Name : string
Age : int
Email : string
}
module Db =
let private peopleStorage = new Dictionary<int, Person>()
let getPeople () =
peopleStorage.Values |> Seq.map (fun p -> p)
Since it’s a sample application, I am just using an in memory dictionary to store the details of people. You can easily replace this with any data source. The Person
type represents the People
resource of the rest api.
The next step is wiring the Restful web part with the application. Open App.fs and update it as below
open SuaveRestApi.Rest
open SuaveRestApi.Db
open Suave.Web
open Suave.Http.Successful
[<EntryPoint>]
let main argv =
let personWebPart = rest "people" {
GetAll = Db.getPeople
}
startWebServer defaultConfig personWebPart
0
That’s it! Now we have the HTTP GET Request up and running
HTTP POST
HTTP POST uses the same workflow as HTTP GET. To implement this, we are going to use the following features in Suave.
The
choose
function takes a list of WebParts, and chooses the first one that applies (i.e which returnsSome
), or if none WebPart applies, then choose will returnNone
The
request
function takes a function of typeHttpRequest -> WebPart
and returns theWebPart
. We will use thisHttpRequest
to get the POST content.The POST is a static in-built
WebPart
which matches the HTTP POST requests.
The first step in implementing POST request is updating the RestResource
.
type RestResource<'a> = {
GetAll : unit -> 'a seq
Create : 'a -> 'a
}
Then add the following utility functions in Restful.fs to get the resource from the HttpRequest
let fromJson<'a> json =
JsonConvert.DeserializeObject(json, typeof<'a>) :?> 'a
let getResourceFromReq<'a> (req : HttpRequest) =
let getString rawForm =
System.Text.Encoding.UTF8.GetString(rawForm)
req.rawForm |> getString |> fromJson<'a>
The rawForm
field in the HttpRequest
has the POST content as a byte array, we are just deserializing to a fsharp type.
The next step is updating the rest
function to support POST
let rest resourceName resource =
let resourcePath = "/" + resourceName
let gellAll = warbler (fun _ -> resource.GetAll () |> JSON)
path resourcePath >=> choose [
GET >=> getAll
POST >=> request (getResourceFromReq >> resource.Create >> JSON)
]
Thanks to the awesome function composition feature, we have combined all these tiny functions and implemented a new request and response.
Then update Db.fs and App.fs respectively as follows
module Db =
let createPerson person =
let id = peopleStorage.Values.Count + 1
let newPerson = {
Id = id
Name = person.Name
Age = person.Age
Email = person.Email
}
peopleStorage.Add(id, newPerson)
newPerson
let personWebPart = rest "people" {
GetAll = Db.getPeople
Create = Db.createPerson
}
The HTTP POST function in action
HTTP PUT
As we did for GET & POST we will be starting by updating the RestResource
type RestResource<'a> = {
GetAll : unit -> 'a seq
Create : 'a -> 'a
Update : 'a -> 'a option
}
We have added a bit of error handling here. This Update
function tries to update a resource and returns the updated resource if the resource exists. If it didn’t it returns None
.
To support HTTP PUT we will be using the following from Suave library
- The
BAD_REQUEST
function takes a string and returns a WebPart representing HTTP status code 400 response with given string as it response. - The
PUT
is a static in-builtWebPart
which matches the HTTP PUT requests.
let rest resourceName resource =
let resourcePath = "/" + resourceName
let badRequest = BAD_REQUEST "Resource not found"
let gellAll = warbler (fun _ -> resource.GetAll () |> JSON)
let handleResource requestError = function
| Some r -> r |> JSON
| _ -> requestError
path resourcePath >=>
choose [
GET >=> getAll
POST >=>
request (getResourceFromReq >> resource.Create >> JSON)
PUT >=>
request (getResourceFromReq >> resource.Update >> handleResource badRequest)
]
Then update Db.fs and App.fs as usual
module Db =
let updatePersonById personId personToBeUpdated =
if peopleStorage.ContainsKey(personId) then
let updatedPerson = {
Id = personId
Name = personToBeUpdated.Name
Age = personToBeUpdated.Age
Email = personToBeUpdated.Email
}
peopleStorage.[personId] <- updatedPerson
Some updatedPerson
else
None
let updatePerson personToBeUpdated =
updatePersonById personToBeUpdated.Id personToBeUpdated
let personWebPart = rest "people" {
GetAll = Db.getPeople
Create = Db.createPerson
Update = Db.updatePerson
}
Updating an existing resource is now live
When we try to update a non-existing resource, we will get the HTTP Bad Request response.
HTTP DELETE
Let’s begin by updating the RestResource
type RestResource<'a> = {
GetAll : unit -> 'a seq
Create : 'a -> 'a
Update : 'a -> 'a option
Delete : int -> unit
}
HTTP DELETE is little different from the other implemented requests as we will be retrieving the id of the resource to be deleted from the URL.
For example, DELETE /people/1 will delete a person with the id 1.
To retrieve the id from the url, we will be using the pathScan
function in Suave. This function similar to printf
which takes a PrintfFormat and a function which takes the output of the printfformat and returns the WebPart. You can get more details about it from the suave-music-store book.
In addition to this, we will be using the following from suave
- The NO_CONTENT is a static WebPart represents the HTTP Response with the status code 204
- The DELETE matches the HTTP request of type DELETE
let rest resourceName resource =
let resourcePath = "/" + resourceName
let resourceIdPath =
new PrintfFormat<(int -> string),unit,string,string,int>(resourcePath + "/%d")
let badRequest = BAD_REQUEST "Resource not found"
let gellAll = warbler (fun _ -> resource.GetAll () |> JSON)
let handleResource requestError = function
| Some r -> r |> JSON
| _ -> requestError
let deleteResourceById id =
resource.Delete id
NO_CONTENT
choose [
path resourcePath >=> choose [
GET >=> getAll
POST >=>
request (getResourceFromReq >> resource.Create >> JSON)
PUT >=>
request (getResourceFromReq >> resource.Update >> handleResource badRequest)
]
DELETE >=> pathScan resourceIdPath deleteResourceById
]
One thing to notice here is we have wrapped the existing choose
function with an another choose
function. It may be little complex to understand but if you understand what each thing mean, it is easier.
Here the inner choose function represents the handler functions for GET, POST & PUT requests having the url /{resourceName} and the DELETE webpart represents the HTTP DELETE handler for the url /{resourceName}/{resourceId}.
The outer choose
function chooses one of these based on the incoming request.
// Db.fs
let deletePerson personId =
peopleStorage.Remove(personId) |> ignore
// App.fs
let personWebPart = rest "people" {
GetAll = Db.getPeople
Create = Db.createPerson
Update = Db.updatePerson
Delete = Db.deletePerson
}
HTTP GET & HTTP PUT by id
I hope by this time you know how to wire things up in Suave to create an API. Let’s add features for getting and updating resource by id.
type RestResource<'a> = {
GetAll : unit -> 'a seq
Create : 'a -> 'a
Update : 'a -> 'a option
Delete : int -> unit
GetById : int -> 'a option
UpdateById : int -> 'a -> 'a option
}
let rest resourceName resource =
// .. Existing code ...
let getResourceById =
resource.GetById >> handleResource (NOT_FOUND "Resource not found")
let updateResourceById id =
request (getResourceFromReq >> (resource.UpdateById id) >> handleResource badRequest)
choose [
path resourcePath >=> choose [
GET >=> getAll
POST >=>
request (getResourceFromReq >> resource.Create >> JSON)
PUT >=>
request (getResourceFromReq >> resource.Update >> handleResource badRequest)
]
DELETE >=> pathScan resourceIdPath deleteResourceById
GET >=> pathScan resourceIdPath getResourceById
PUT >=> pathScan resourceIdPath updateResourceById
]
// Db.fs
let getPerson id =
if peopleStorage.ContainsKey(id) then
Some peopleStorage.[id]
else
None
// App.fs
let personWebPart = rest "people" {
GetAll = Db.getPeople
Create = Db.createPerson
Update = Db.updatePerson
Delete = Db.deletePerson
GetById = Db.getPerson
UpdateById = Db.updatePersonById
}
HTTP HEAD
Http HEAD request checks whether the request with the given id is there or not. Its implementation is straight forward.
type RestResource<'a> = {
GetAll : unit -> 'a seq
Create : 'a -> 'a
Update : 'a -> 'a option
Delete : int -> unit
GetById : int -> 'a option
UpdateById : int -> 'a -> 'a option
IsExists : int -> bool
}
let rest resourceName resource =
// .. Existing code ...
let isResourceExists id =
if resource.IsExists id then OK "" else NOT_FOUND ""
choose [
path resourcePath >=> choose [
GET >=> getAll
POST >=>
request (getResourceFromReq >> resource.Create >> JSON)
PUT >=>
request (getResourceFromReq >> resource.Update >> handleResource badRequest)
]
DELETE >=> pathScan resourceIdPath deleteResourceById
GET >=> pathScan resourceIdPath getResourceById
PUT >=> pathScan resourceIdPath updateResourceById
HEAD >=> pathScan resourceIdPath isResourceExists
]
// Db.fs
let isPersonExists = peopleStorage.ContainsKey
let personWebPart = rest "people" {
GetAll = Db.getPeople
GetById = Db.getPerson
Create = Db.createPerson
Update = Db.updatePerson
UpdateById = Db.updatePersonById
Delete = Db.deletePerson
IsExists = Db.isPersonExists
}
That’s all.. We have successfully implemented a REST API using Suave
Extending with a new Resource
The beautiful aspect of this functional REST API design we can easily extend it to support other resources.
Here is the rest API implementation of the albums
resource in the music-store application. You can find the source code of MusicStoreDb here.
[<EntryPoint>]
let main argv =
let personWebPart = rest "people" {
GetAll = Db.getPeople
GetById = Db.getPerson
Create = Db.createPerson
Update = Db.updatePerson
UpdateById = Db.updatePersonById
Delete = Db.deletePerson
IsExists = Db.isPersonExists
}
let albumWebPart = rest "albums" {
GetAll = MusicStoreDb.getAlbums
GetById = MusicStoreDb.getAlbumById
Create = MusicStoreDb.createAlbum
Update = MusicStoreDb.updateAlbum
UpdateById = MusicStoreDb.updateAlbumById
Delete = MusicStoreDb.deleteAlbum
IsExists = MusicStoreDb.isAlbumExists
}
startWebServer defaultConfig (choose [personWebPart;albumWebPart])
0
Conclusion
In the amazing presentation on Functional Programming Design Patterns, Scott Wlaschin had this slide
I wondered how this can be applied in real-time. By creating a rest API using suave I’ve understood this.
Just create functions and combine it to build a bigger system.
What a nice way to develop a system!
You can get the source code associated with this blog post in my GitHub repository.