Download as pdf or txt
Download as pdf or txt
You are on page 1of 276

Table of Contents

Introduction 1.1

Chapter 1 - Setting Up FsTweet Project 1.2

Chapter 2 - Setting Up Server Side Rendering using DotLiquid 1.3

Chapter 3 - Serving Static Asset Files 1.4

Chapter 4 - Handling User signup Form 1.5


Chapter 5 - Validating New User Signup Form 1.6
Chapter 6 - Setting Up Database Migration 1.7

Chapter 7 - Orchestrating User Signup 1.8


Chapter 8 - Transforming Async Result to Webpart 1.9
Chapter 9 - Persisting New User 1.10

Chapter 10 - Sending Verification Email 1.11


Chapter 11 - Verifying User Email 1.12

Chapter 12 - Reorganising Code and Refactoring 1.13


Chapter 13 - Adding Login Page 1.14
Chapter 14 - Handling Login Request 1.15
Chapter 15 - Creating User Session and Authenticating User 1.16
Chapter 16 - Posting New Tweet 1.17

Chapter 17 - Adding User Feed 1.18


Chapter 18 - Adding User Profile Page 1.19
Chapter 19 - Following a User 1.20
Chapter 20 - Fetching Followers and Following Users 1.21
Chapter 21 - Deploying to Azure App Service 1.22

Chapter 22 - Adding Logs using Logary 1.23


Chapter 23 - Wrapping Up 1.24

2
3
Introduction

Hi,

Thanks for buying, F# Applied Part II.

The objective of this book is to answer the most significant question that developers
encounter while learning or working with F#.

How can I build a real-world, production-ready, end-to-end application in F# using


the functional programming principles?

Immutability, Type Safety, Pattern Matching, Pure Functions and all other functional
programming things sound good on paper but How can I build something useful by
applying it?

This book has been created exclusively to answer these questions in a developer-
friendly way!

Sounds interesting?

Let’s have a look at what we will be learning from this book.

We are going to build a clone of the Twitter application, FsTweet. Right from starting
an empty directory for the application to deploying it on Azure App Service, we are
going to learn step by step. It was narrated in such a way that I will be doing pair
programming by sitting next to you and collaboratively building the application from
scratch.

The features that will be building includes

Handling new user signups and verifying their email address


Authenticating them to access their twitter wall and profile page
Following other Twitter users
Publishing the tweets to the followers and get real-time updates.

4
Introduction

On the technical side, we are going to learn a lot of things and to quote a few; we’ll
be learning

The Application of functional programming principles in a real-world application


Server-Side Rendering in Suave using DotLiquid
Authentication and Authorisation in Suave
Application Logging using Logary
Error handling in asynchronous operations.
Database access using SQLProvider
Managing Stream using GetStream.io

Overall, It’s going to be a lot of fun, and I believe it will add value in your functional
programming journey in F#.

Acknowledgement
This entire book was initially planned to be released as a video course in FSharp.TV
after my Build a Web Server based on Suave course. Due to some personal
reasons, we couldn't complete it. Thank you, Mark, for the courteous support to
release the course material as a book.

I'd like to thank the entire F# Community for their open source contributions, support,
and thought-provoking blog posts, articles, and tutorials.

Improvements
We all need people who will give us feedback. That's how we improve - Bill
Gates.

Any suggestions or error reports from you are welcome. You can report them as
issues in the FsTweet's GitHub repository.

5
Introduction

Dedicated to

My Wife Abinaya and


My Twin Daughters, Abayambika & Avanthika

6
Chapter 1 - Setting Up FsTweet Project

In this first chapter, we will be kickstarting the project from a boilerplate F# project
and setting up the tools that are going to assist us in building the application.

DotNet Version
At the time of writing this book, Microsoft announced the release of .NET Core 2.0.
and its support in the F# ecosystem had some rough edges. Especially the type
providers, which we are going to use extensively in the later chapters, were not
adequately supported.

So, we are going to use the .NET Framework version 4.6.1 for our development.

Installing Forge
Forge is a command line tool that provides the command line interface for creating
and managing F# projects. In this book, we are going to make use of Forge to
develop our application, FsTweet.

We can also use IDEs like Visual Studio, Rider or VS Code with Ionide to
create and manage F# projects. In case if you choose to use an IDE that you
are already comfortable with instead of Forge, you are indeed welcome to do.

Under the assumption that you will be aware of how to translate the forge
commands into its equivalent IDE actions (if you are choosing an IDE over
forge), we won't be seeing anything specific to IDEs in this book.

As the project formats were changed between .NET core 2.0 and its early versions,
the latest version of Forge didn't support projects from earlier .NET version. So, we
are going to make use of the version 1.4.2 of Forge to manage our projects.

Let's get started by installing it on a windows machine. If you are using a non-
windows machine, you can skip the following section and jump to the next section.

On Windows
Download the Forge zip file from this releases link.

Unzip the "forge" directory to some local directory (for example c:\tools ).

7
Chapter 1 - Setting Up FsTweet Project

Add the bin directory path of forge( c:\tools\forge\bin ) to the Path environment
variable.

Now open the command prompt and execute the following command to verify
the installation.

> forge -h

Ensure you are getting the below response

If you already installed forge either uninstall it (if you are not using the latest
version) or rename the "Forge.exe" file in the bin directory to something else
and use the same name to execute the forge commands

8
Chapter 1 - Setting Up FsTweet Project

On Non-Windows
On Non-Windows the prerequisite is Mono, and I believe you will be already having it
on your machine.

The first step is downloading the Forge zip file from this releases link.

Unzip the "forge" directory to some local directory (for example ~/tools ).

Then add an alias in the .bashrc file like the below one

alias forge="mono ~/tools/forge/bin/Forge.exe"

Now open the terminal and execute the following command to verify the
installation.

$ forge --help

Ensure you are getting the below response

Available parameters:
USAGE: forge.exe [--help] [new] [add] [move] [remove] [rename] [list]
[update-version] [paket] [fake] [refresh] [exit]

OPTIONS:
...

If you already installed forge either uninstall it (if you are not using the latest
version) or set the alias to something else and use the same alias to execute
the forge commands

Command Line Interface


We will be using the command line interfaces(CLI) a lot throughout the book, and it
assumes that you are using a bash shell.

On Windows, it is recommended to use cmder in bash mode. You can switch to


bash mode in cmder by using the bash command.

9
Chapter 1 - Setting Up FsTweet Project

For Non-Windows, the first preference is oh-my-zsh.

If you are using any other CLIs (like Powershell or DOS Prompt) some of the
commands may not available/work as intended.

If you are not a command line fan, you can safely replace the command line
instructions in the book with the corresponding manual UI action.

The FsTweet Boilerplate


Alright, we have all the required tools in place and let's dive in.

As a first step, clone the FsTweetBoilerplate project from GitHub.

> git clone git@github.com:demystifyfp/FsTweetBoilerplate.git FsTweet

The above command creates a new directory FsTweet and clones the
FsTweetBoilerplate project there.

You can also download the boilerplate project from GitHub instead of cloning
and put it inside a directory with the name FsTweet .

10
Chapter 1 - Setting Up FsTweet Project

A Peek Into The Boilerplate


FsTweet
├── Forge.toml
├── build.cmd
├── build.fsx
├── build.sh
├── paket.dependencies
├── paket.lock
└── src
└── FsTweet.Web
├── FsTweet.Web.fs
├── FsTweet.Web.fsproj
└── paket.references

FsTweet.Web.fs
This file contains the entry point of the application.

module FsTweetWeb.Main

open Suave
open Suave.Successful

[<EntryPoint>]
let main argv =
startWebServer defaultConfig (OK "Hello World!")
0

It is a most straightforward Suave application with an HTTP server that greets all
visitors with the string "Hello World!"

paket.dependencies and paket.lock


The boilerplate project uses Paket for managing external packages. Our current
dependencies are FAKE (to orchestrating the build process), Fsharp.Core and
Suave.

11
Chapter 1 - Setting Up FsTweet Project

paket.dependencies

source https://www.nuget.org/api/v2
framework: net461
nuget FAKE
nuget FSharp.Core
nuget Suave

paket.lock

RESTRICTION: == net461
NUGET
remote: https://www.nuget.org/api/v2
FAKE (4.63)
FSharp.Core (4.2.2)
Suave (2.1.1)
FSharp.Core (>= 4.0.0.1)

Fake Build Script - build.fsx


To begin with, the Build Script defines three targets.

One to clear the build directory, another one to build the application and the last one
for running the application.

#r "./packages/FAKE/tools/FakeLib.dll"
open Fake

let buildDir = "./build/"


let appReferences =
!! "/**/*.csproj"
++ "/**/*.fsproj"

// Targets
Target "Clean" (fun _ ->
CleanDirs [buildDir]
)

Target "Build" (fun _ ->


// compile all projects below src/app/
MSBuildDebug buildDir "Build" appReferences
|> Log "AppBuild-Output: "
)

12
Chapter 1 - Setting Up FsTweet Project

Target "Run" (fun _ ->


ExecProcess
(fun info -> info.FileName <- "./build/FsTweet.Web.exe")
(System.TimeSpan.FromDays 1.)
|> ignore
)

// Build order
"Clean"
==> "Build"
==> "Run"

// start build
RunTargetOrDefault "Build"

In the Run target, we are running the FsTweet.Web console application using the
ExecProcess function from FAKE. It is only intended to run the application during our
development.

You can also find two more files, build.cmd & build.sh to execute the FAKE build
script in command prompt and bash shell respectively.

Test driving the boilerplate


To see the boilerplate code in action, we first need to restore the Paket
dependencies. Using forge, we can achieve it with the following command

> forge paket restore

After successful packages restore, we can run the application by executing FAKE
build script's Run target.

> forge fake Run

This command will build the application and start the Suave standalone web server
on port 8080 . You can verify whether it is working or not by visiting the URL
http://127.0.0.1:8080/ in the browser.

13
Chapter 1 - Setting Up FsTweet Project

As we will be executing the forge fake Run command very often while developing the
application, to save some keystrokes and time, the boilerplate project has the
Forge.toml file with a Forge alias for this command

# Forge.toml
[alias]
run='fake Run'

With this alias in place, we can build and run our application using this simplified
command

> forge run

From here on, the term run/test drive the application in the following chapters
implies running this forge run command.

Summary
In this chapter, we installed forge, then CLI and then we bootstrapped the project
from the boilerplate. In the upcoming chapters, we will be leveraging this.

14
Chapter 2 - Setting Up Server Side Rendering using DotLiquid

In this second chapter, we are going to extend our FsTweet app to render Hello,

World! as HTML document from the server side using DotLiquid

Adding Packages References


Suave has good support for doing server side rendering using DotLiquid. To make
use of this in our project, we need to refer the associated NuGet packages in
FsTweet.Web.fsproj.

Let's use Forge to add the required packages using Paket

> forge paket add DotLiquid -V 2.0.64 -p src/FsTweet.Web/FsTweet.Web.fsproj


> forge paket add Suave.DotLiquid -V 2.2 -p src/FsTweet.Web/FsTweet.Web.fsproj

This commands download the references of the specified NuGet packages and add
their references to the FsTweet.Web.fsproj file.

At the time of this writing, there are some incompatibility changes in the latest
versions of DotLiquid and Suave.DotLiquid. So we are sticking to their old
versions here.

Initializing DotLiquid
Now we have the required NuGet packages onboard

DotLiquid requires the following global initilization settings to enable us to render the
liquid templates.

A directory path which contains all our views


Naming Convention to be used when referring view models in the views.

The Suave.DotLiquid has helper functions to do this for us.

Let's have a directory called views in the FsTweet.Web project to put the liquid
template files

> mkdir src/FsTweet.Web/views

15
Chapter 2 - Setting Up Server Side Rendering using DotLiquid

The add a new function called initDotLiquid , which invokes the required helper
functions to initialize DotLiquid to use this views directory for templates.

// FsTweet.Web.fs
// ...
open Suave.DotLiquid
open System.IO
open System.Reflection

let currentPath =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)

let initDotLiquid () =
let templatesDir = Path.Combine(currentPath, "views")
setTemplatesDir templatesDir

[<EntryPoint>]
let main argv =
initDotLiquid ()
setCSharpNamingConvention ()
// ...

By default, DotLiquid uses Ruby Naming Convention to refer the view model
properties in the liquid template. For example, if you are passing a record type
having a property UserId as a view model while using it in the liquid template, we
have to use user_id instead of UserId to access the value.

We are overriding this default convention by calling the setCSharpNamingConvention

function from the Suave.DotLiquid library.

Updating Build Script To Copy Views


Directory
With the above DotLiquid configuration in place, while running the FsTweet.Web

application, we need to have the views directory in the current directory.

├── build
│ ├── ...
│ ├── FsTweet.Web.exe
│ └── views/

16
Chapter 2 - Setting Up Server Side Rendering using DotLiquid

We can achieve it in two ways.

1. Adding the liquid templates files in the views directory to FsTweet.Web.fsproj file
with the Build Action property as Content and Copy to Output property to either
Copy always or Copy if newer as mentioned in the project file properties
documentation.

2. The second option is leveraging our build script to copy the entire views

directory to the build directory.

We are going to use the latter one as it is a one time work rather than fiddling with
the properties whenever we add a new liquid template file.

To do this let's add a new Target in the FAKE build script called Views and copy the
directory the FAKE's CopyDir function

let noFilter = fun _ -> true

Target "Views" (fun _ ->


let srcDir = "./src/FsTweet.Web/views"
let targetDir = combinePaths buildDir "views"
CopyDir targetDir srcDir noFilter
)

Then modify the build order to invoke Views Target before Run

// Build order
"Clean"
==> "Build"
==> "Views"
==> "Run"

That's it!

Now it's time to add some liquid templates and see it in action

Defining And Rending DotLiquid


Templates
The first step is defining a master page template with some placeholders.

17
Chapter 2 - Setting Up Server Side Rendering using DotLiquid

Add a new file master_page.liquid in the views directory and update it as below

<!-- src/FsTweet.Web/views/master_page.liquid -->


<!DOCTYPE HTML>
<html>
<head>
{% block head %}
{% endblock %}
</head>
<body>
<div id="content">
{% block content %}
{% endblock %}
</div>
<div id="scripts">
{% block scripts %}
{% endblock %}
</div>
</body>
</html>

This master_page template defines three placeholders head , content and scripts

which will be filled by its child pages.

The next step is adding a child page liquid template guest/home.liquid with some title
and content

{% extends "master_page.liquid" %}

{% block head %}
<title> FsTweet - Powered by F# </title>
{% endblock %}

{% block content %}
<p>Hello, World!</p>
{% endblock %}

This guest home page template extends the master_page template and provides
values for the head and content placeholders.

18
Chapter 2 - Setting Up Server Side Rendering using DotLiquid

Rendering Using Suave.DotLiquid


The final step is rendering the liquid templates from Suave.

The Suave.DotLiquid package has a function called page which takes a relative file
path (from the templates root directory) and a view model and returns a WebPart

We just need to define the app using this page function. As the page is not using a
view model we can use an empty string for the second parameter.

Let's also add a path filter in Suave to render the page only if the path is a root ( / )

// FsTweet.Web.fs
// ...
open Suave.Operators
open Suave.Filters
// ...
[<EntryPoint>]
let main argv =
// ...
let app =
path "/" >=> page "guest/home.liquid" ""
startWebServer defaultConfig app
0

Now if you build and run the application using the forge run command, you can see
an HTML document with the Hello, World! content in the browser on
http://localhost:8080/

Summary
In this chapter, we have seen how to set up a Suave application to render server
side views using DotLiquid and also how to make use of FAKE build script to
manage static files.

The source code is available on GitHub repository.

19
Chapter 3 - Serving Static Asset Files

In this third capter, we will be changing the guest homepage from displaying Hello,

World! to a production ready landing page!

Preparing Static Asset Files


As a first step let's create an assets directory in FsTweet.Web and place our static
asset files. The asset files can be downloaded from the repository

20
Chapter 3 - Serving Static Asset Files

└── FsTweet.Web
├── FsTweet.Web.fs
├── FsTweet.Web.fsproj
├── assets
│ ├── css
│ │ └── styles.css
│ └── images
│ ├── FsTweetLogo.png
│ └── favicon.ico
├── ...

Modifying Master Page and Guest Home


Templates
Then we need to change our liquid templates to use these assets

<!-- view/master_page.liquid -->


<head>
<!-- ... -->
<link rel="stylesheet" href="assets/css/styles.css">
</head>

<!-- view/guest/home.liquid -->


<!-- ... -->
{% block content %}
<!-- ... -->
<div class="jumbotron">
<img src="assets/images/FsTweetLogo.png" width="400px"/>
<p class="lead">Communicate with the world in a different way!</p>
<!-- ... -->
</div>
{% endblock %}

For simplicity, I am leaving the other static content that is modified in the templates,
and you can find all the changes in this diff

21
Chapter 3 - Serving Static Asset Files

Updating Build Script To Copy Assets


Directory
As we saw during the dot liquid setup, we need to add an another Target Assets to
copy the assets directory to the build directory

let copyToBuildDir srcDir targetDirName =


let targetDir = combinePaths buildDir targetDirName
CopyDir targetDir srcDir noFilter

Target "Assets" (fun _ ->


copyToBuildDir "./src/FsTweet.Web/assets" "assets"
)

Then modify the build order to run this Target before the Run Target.

// Build order
"Clean"
==> "Build"
==> "Views"
==> "Assets"
==> "Run"

Serving Asset Files


Now we have the assets available in the build directory. The next step is serving
them Suave in response to the request from the browser.

Suave has a lot of useful functions to handle files, and in our case, we are going to
make use of the browseHome function to serve the assets

'browse' the file in the sense that the contents of the file are sent based on the
request's Url property. Will serve from the current as configured in directory.
Suave's runtime. - Suave Documentation

The current directory in our case is the directory in which the FsTweet.Web.exe
exists. i.e build directory.

22
Chapter 3 - Serving Static Asset Files

// FsTweet.Web.fs
// ...
open Suave.Files

// ...
let serveAssets =
pathRegex "/assets/*" >=> browseHome

[<EntryPoint>]
let main argv =
// ...
let app =
choose [
serveAssets
path "/" >=> page "guest/home.liquid" ""
]

startWebServer defaultConfig app

We have made two changes here.

The serveAssets defines a new WebPart using the pathRegex. It matches all the
requests for the assets and serves the corresponding files using the browseHome

function.

As we are handling more than one requests now, we need to change our app

to handle all of them. Using the choose function, we are defining the app to
combine both serveAssets WebPart and the one that we already had for serving
the guest home page.

Serving favicon.ico
While serving our FsTweet application, the browser automatically makes a request
for favicon. As the URL path for this request is /favicon.ico our serveAssets

webpart cannot match this.

To serve it we need to use an another specific path filter and use the file function to
get the job done.

23
Chapter 3 - Serving Static Asset Files

// FsTweet.Web.fs
// ...
let serveAssets =
let faviconPath =
Path.Combine(currentPath, "assets", "images", "favicon.ico")
choose [
pathRegex "/assets/*" >=> browseHome
path "/favicon.ico" >=> file faviconPath
]
//...

Summary
In this chapter, we learned how to serve static asset files in Suave. The source code
can be found in the GitHub repository

24
Chapter 4 - Handling User signup Form

Hi,

In the last chapter, we added a cool landing page for FsTweet to increase the user
signups. But the signup form and its backend are not ready yet!

In this chapter, we will be extending FsTweet to serve the signup page and
implement its backend scaffolding

A New File For User Signup


Let's get started by creating a new file UserSignup.fs in the FsTweet.Web.fsproj file
using Forge.

> forge new file -t fs -p src/FsTweet.Web/FsTweet.Web.fsproj \


-n src/FsTweet.Web/UserSignup

You can ignore the \ character from the above command if you are typing
everything in a single line

The next step is moving this file above FsTweet.Web.fs file as we will be referring
UserSignup in the Main function.

Using Forge, we can achieve it using the following command

> forge move file -p src/FsTweet.Web/FsTweet.Web.fsproj \


-n src/FsTweet.Web/UserSignup.fs -u

Though working with the command line is productive than its visual counterpart, the
commands that we typed for creating and moving a file is verbose.

Forge has an advanced feature called alias using which we can get rid of the
boilerplate to a large extent.

As we did for the forge Run alias during the project setup, let's add few three more
alias

# ...
web='-p src/FsTweet.Web/FsTweet.Web.fsproj'
newFs='new file -t fs'
moveUp='move file -u'

25
Chapter 4 - Handling User signup Form

The web is an alias for the project argument in the Forge commands. The newFs

and moveUp alias are for the new file and move file operations respectively.

If we had this alias beforehand, we could have used the following commands to do
what we just did

> forge newFs web -n src/FsTweet.Web/UserSignup


> forge moveUp web -n src/FsTweet.Web/UserSignup.fs

We can generalize the alias as

forge {operation-alias} {project-alias} {other-arguments}

If you feel some of the things like, adding a new file, moving the file up/down,
etc., are better using your favourite IDE/editor than forge, you can ignore
these steps and use their equivalents in the IDE.

Serving User Signup Page


The first step is to serve the user signup page in response to the /signup request
from the browser.

As we will be capturing the user details during signup, we need to use an view model
while using the dotliquid template for the signup page.

In the UserSignup.fs, delete all the initial content, then define a namespace
UserSignup and a module Suave with a webPart function.

// FsTweet.Web/UserSignup.fs
namespace UserSignup

26
Chapter 4 - Handling User signup Form

module Suave =

open Suave.Filters
open Suave.Operators
open Suave.DotLiquid

//
let webPart () =
path "/signup"
>=> page "user/signup.liquid" ???

The namespace represents the use case or the feature that we are about to
implement. The modules inside the namespace represent the different layers of the
use case implementation.

The Suave module defines the Web layer of the User Signup feature. You can learn
about organizing modules from this blog post.

The ??? symbol is a placeholder that we need to fill in with a view model.

The view model has to capture user's email address, password, and username.

// FsTweet.Web/UserSignup.fs
module Suave =
type UserSignupViewModel = {
Username : string
Email : string
Password: string
Error : string option
}
let emptyUserSignupViewModel = {
Username = ""
Email = ""
Password = ""
Error = None
}
let webPart () =
path "/signup"
>=> page "user/signup.liquid" emptyUserSignupViewModel

As the name indicates, emptyUserSignupViewModel provide the default values for the
view model.

The Error property in the UserSignupViewModel record type is to communicate an


error with the view.

27
Chapter 4 - Handling User signup Form

The next step is creating a dotliquid template for the signup page.

<!-- FsTweet.Web/views/user/signup.liquid -->


{% extends "master_page.liquid" %}

{% block head %}
<title> Sign Up - FsTweet </title>
{% endblock %}

{% block content %}
<form method="POST" action="/signup">
{% if model.Error %}
<p>{{ model.Error.Value }}</p>
{% endif %}
<input type="email" name="Email" value={{ model.Email }}>
<input type="text" name="Username" value={{ model.Username }}>
<input type="password" name="Password">
<button type="submit">Sign up</button>
</form>
{% endblock %}

For brevity, the styles and some HTML tags are ignored.

In the template, the value of the name attribute should match its corresponding view
model property's name to do the model binding on the server side.

And another thing to notice here is the if condition that displays the error only if it
is available.

The last step in serving the user signup page is adding this new webpart in the
application.

To do this, we just need to call the webPart function while defining the app in the
main function.

// FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let app =
choose [

28
Chapter 4 - Handling User signup Form

// ...
UserSignup.Suave.webPart ()
]
// ...

That's it!

If we run the application and hit http://localhost:8080/signup in the browser, we can


see the signup page

Handling Signup Form POST request


To handle the POST request during the signup form submission, we have to make
some changes.

29
Chapter 4 - Handling User signup Form

For the GET request on the /signup path, we are serving the signup page. And for
the POST request, we need a function to handle the POST request.

// FsTweet.Web/UserSignup.fs
module Suave =
// ...
open Suave
// ...
let webPart () =
path "/signup"
>=> choose [
GET >=> page "user/signup.liquid" emptyUserSignupViewModel
POST >=> ???
]

The function that we are going to write to fill the placeholder ??? has to satisfy two
criteria.

1. It has to return a WebPart so that it can be composed using >=> operator (or
infix function).
2. The other requirement is its interaction with the database should be
asynchronous (non-blocking) otherwise it'd block the Suave Web Server.

Let's have a look at the first criteria, returning a WebPart.

In Suave, a WebPart is a type alias of the below function signature

HttpContext -> Async<HttpContext option>

And the signature of the >=> operator is

HttpContext -> Async<HttpContext option> ->


HttpContext -> Async<HttpContext option>

and it can be simplified using the WebPart type alias as

WebPart -> WebPart -> WebPart

With this knowledge, Let's name the function that is going to handle the user signup
post request as handleUserSignup .

30
Chapter 4 - Handling User signup Form

// HttpContext -> Async<HttpContext option>


let handleUserSignup ctx = async {
printfn "%A" ctx.request.form
return Some ctx
}

This is a naive implementation of the handleUserSignup which just prints the whatever
value there in the request's form type in the console and return the HttpContext as it
is.

As the signature of the handleUserSignup is same as that of the WebPart , it can be


combined like

POST >=> handleUserSignup

The second criteria for asynchronous are already satisfied as the handleUserSignup

returns the Async<HttpContext option> .

To get a feel for how we will be interacting with the database in this function, let's
redirect the user to the signup page again instead of returning the HttpContext as it
is.

We can do page redirection using the FOUND function in the Redirection module of
Suave.

The FOUND function takes a path (of type string ) to redirect the browser to and
returns a WebPart

string -> WebPart

When we expand the WebPart alias, it become

string -> HttpContext -> Async<HttpContext option>

Now we can say that this function takes a string and an HttpContext and
asynchronously returns HttpContext option .

31
Chapter 4 - Handling User signup Form

If we are using this function in handleUserSignup , we need to wait for the


asynchronous operation to complete and then take the return value of the
HttpContext option and return it.

let handleUserSignup ctx = async {

printfn "%A" ctx.request.form

// HttpContext option
let! redirectionResponse =
Redirection.FOUND "/signup" ctx

return redirectionResponse
}

The async computation expression takes care of waiting and returning the value
from an asynchronous operation without blocking the main thread.

We'll be using the similar technique to perform the database operations.

The usage of let! and followed by return can be simplified using a syntactic sugar
return! which does the both

let handleUserSignup ctx = async {


printfn "%A" ctx.request.form
return! Redirection.FOUND "/signup" ctx
}

The final implementation would look like this

32
Chapter 4 - Handling User signup Form

// FsTweet.Web/UserSignup.fs
module Suave =
// ...
let handleUserSignup ctx = async {
printfn "%A" ctx.request.form
return! Redirection.FOUND "/signup" ctx
}

let webPart () =
path "/signup"
>=> choose [
// ...
POST >=> handleUserSignup
]

When we rerun the program with this new changes, we can find the values being
posted in the console upon submitting the signup form.

[("Email", Some "demystifyfp@gmail.com"); ("Username", Some "demystifyfp");


("Password", Some "secret")]

Model Binding Using Suave.Experimental


In the previous section, the handleUserSignup WebPart got the form data that were
posted using the form member of the request .

The form member is of type (string * string option) list .

We already have view model in place UserSignupViewModel to represent the same


data. The next step is converting the data

from {(string * string option) list} to {UserSignupViewModel}

In other words, we need to bind the request form data to the UserSignupViewModel .

There is an inbuilt support for doing this Suave using Suave.Experimental package.

33
Chapter 4 - Handling User signup Form

Let's add this to our FsTweet.Web project using paket and forge.

> forge paket add Suave.Experimental --version 2.2 \


-p src/FsTweet.Web/FsTweet.Web.fsproj

After we add the reference, we can make use of the bindEmptyForm function to carry
out the model binding for us.

val bindEmptyForm<'a> : (req : HttpRequest) -> Choice<'a, string>

The bindEmptyForm function takes a request and returns either the value of the given
type or an error message.

// ...
module Suave =
// ...
open Suave.Form
// ...

let handleUserSignup ctx = async {


match bindEmptyForm ctx.request with
| Choice1Of2 (userSignupViewModel : UserSignupViewModel) ->
printfn "%A" userSignupViewModel
return! Redirection.FOUND "/signup" ctx
| Choice2Of2 err ->
let viewModel = {emptyUserSignupViewModel with Error = Some err}
return! page "user/signup.liquid" viewModel ctx
}
// ...

As the bindEmptyForm function returns a generic type as its first option, we need to
specify the type to enable the model binding explicitly.

If the model binding succeeds, we just print the view model and redirects the user to
the signup page as we did in the previous section.

If it fails, we modify the viewModel with the error being returned and render the
signup page again.

When we rerun the program and do the form post again, we will get the following
output.

34
Chapter 4 - Handling User signup Form

{Username = "demystifyfp";
Email = "demystifyfp@gmail.com";
Password = "secret";
Error = None;}

Summary
In this chapter, We started with rendering the signup form, and then we learned how
to do view model binding using the Suave.Experimental library.

The source code is available on GitHub

35
Chapter 5 - Validating New User Signup Form

In the last chapter, we created the server side representation of the user submitted
details. The next step is validating this view model against a set of constraints before
persisting them in a data store.

Transforming View Model To Domain


Model
In F#, a widely used approach is defining a domain model with the illegal states
unrepresentable and transform the view model to the domain model before
proceeding with the next set of actions.

Let's take the Username property of the UserSignupViewModel for example.

It is of type string . The reason why we have it as a string is to enable model


binding with ease. That means, Username can have null , "" or even a very long
string!

Let's assume that we have a business requirement stating the username should not
be empty, and it can't have more than 12 characters. An ideal way to represent this
requirement in our code is to type called Username and when we say a value is of
type Username it is guaranteed that all the specified requirements for Username has
been checked and it is a valid one.

It is applicable for the other properties as well.

Email should have a valid email address, and Password has to meet the
application's password policy.

Let's assume that we have a function tryCreate that takes UserSignupViewModel as its
input, performs the validations based on the requirements and returns either a
domain model UserSignupRequest or a validation error of type string .

36
Chapter 5 - Validating New User Signup Form

The subsequent domain actions will take UserSignupRequest as its input without
bothering about the validness of the input!

If we zoom into the tryCreate function, it will have three tryCreate function being
called sequentially. Each of these functions takes care of validating the individual
properties and transforming them into their corresponding domain type.

If we encounter a validation error in any of these internal functions, we can short


circuit and return the error that we found.

37
Chapter 5 - Validating New User Signup Form

In some cases, we may need to capture all the errors instead of short
circuiting and returning the first error that we encountered.

This validation and transformation approach is an implementation of a functional


programming abstraction called Railway Oriented Programming.

The Chessie Library


Chessie is an excellent library for doing Railway Oriented Programming in fsharp.

Let's get started with the validation by adding the Chessie package.

> forge paket add Chessie -p src/FsTweet.Web/FsTweet.Web.fsproj

38
Chapter 5 - Validating New User Signup Form

Making The Illegal States Unrepresentable


As a first step, create a new module Domain in the UserSignup.fs and make sure it is
above the Suave module.

namespace UserSignup
module Domain =
// TODO

Then define a single case discriminated union with a private constructor for the
domain type Username

module Domain =
type Username = private Username of string

The private constructor ensures that we can create a value of type Username only
inside the Domain module.

Then add the tryCreate function as a static member function of Username

module Domain =
open Chessie.ErrorHandling

type Username = private Username of string with


static member TryCreate (username : string) =
match username with
| null | "" -> fail "Username should not be empty"
| x when x.Length > 12 ->
fail "Username should not be more than 12 characters"
| x -> Username x |> ok

As we saw in the previous function, the TryCreate function has the following function
signature

string -> Result<Username, string list>

The Result , a type from the Chessie library, represents the result of our validation.
It will have either the Username (if the input is valid) or a string list (for invalid
input)

39
Chapter 5 - Validating New User Signup Form

The presence string list instead of just string is to support an use case
where we are interested in capturing all the errors. As we are going to capture
only the first error, we can treat this as a list with only one string .

The ok and fail are helper functions from Chessie to wrap our custom values
with the Success and Failure part of the Result type respectively.

As we will need the string representation of the Username to persist it in the data
store, let's add a property Value which returns the underlying actual string value.

module Domain =
// ...
type Username = private Username of string with
// ...
member this.Value =
let (Username username) = this
username

Let's do the same thing with the other two input that we are capturing during the user
signup

module Domain =
// ...
type EmailAddress = private EmailAddress of string with
member this.Value =
let (EmailAddress emailAddress) = this
emailAddress
static member TryCreate (emailAddress : string) =
try
new System.Net.Mail.MailAddress(emailAddress) |> ignore
EmailAddress emailAddress |> ok
with
| _ -> fail "Invalid Email Address"

40
Chapter 5 - Validating New User Signup Form

module Domain =
// ...
type Password = private Password of string with
member this.Value =
let (Password password) = this
password
static member TryCreate (password : string) =
match password with
| null | "" -> fail "Password should not be empty"
| x when x.Length < 4 || x.Length > 8 ->
fail "Password should contain only 4-8 characters"
| x -> Password x |> ok

Now we have all individual validation and transformation in place. The next step is
composing them together and create a new type UserSignupRequest that represents
the valid domain model version of the UserSignupViewModel

module Domain =
// ...
type UserSignupRequest = {
Username : Username
Password : Password
EmailAddress : EmailAddress
}

How do we create UserSignupRequest from UserSignupViewModel ?

With the help of trial, a computation expression(CE) builder from Chessie and the
TryCreate functions that we created earlier we can achieve it with ease.

41
Chapter 5 - Validating New User Signup Form

module Domain =
// ...
type UserSignupRequest = {
// ...
}
with static member TryCreate (username, password, email) =
trial {
let! username = Username.TryCreate username
let! password = Password.TryCreate password
let! emailAddress = EmailAddress.TryCreate email
return {
Username = username
Password = password
EmailAddress = emailAddress
}
}

The TryCreate function in the UserSignupRequest takes a tuple with three elements
and returns a Result<UserSignupRequest, string list>

The trail CE takes care of short circuiting if it encounters a validation error.

We might require some of the types that we have defined in the Domain

module while implementing the upcoming features. We will be moving the


common types to a shared Domain module as and when needed.

Showing Validation Error


We are done with the domain side of the UserSignup and one pending step is
communicating the validation error with the user.

We already have an Error property in UserSignupViewModel for this purpose. So, we


just need to get the error from the Result type and populate it.

The Chessie library has a function called either .

either fSuccess fFailure trialResult

It takes three parameters, two functions fSuccess and fFailure and a Result type.

42
Chapter 5 - Validating New User Signup Form

It maps the Result type with fSuccess if it is a Success otherwise it maps it with
fFailure .

module Suave =
// ...
open Domain
open Chessie.ErrorHandling
// ...
let handleUserSignup ctx = async {
match bindEmptyForm ctx.request with
| Choice1Of2 (vm : UserSignupViewModel) ->
let result =
UserSignupRequest.TryCreate (vm.Username, vm.Password, vm.Email)
let onSuccess (userSignupRequest, _) =
printfn "%A" userSignupRequest
Redirection.FOUND "/signup" ctx
let onFailure msgs =
let viewModel = {vm with Error = Some (List.head msgs)}
page "user/signup.liquid" viewModel ctx
return! either onSuccess onFailure result
// ...
}
// ...

In our case, in case of success, as a dummy implementation, we just print the


UserSignupRequest and redirect to the signup page again.

During failure, we populate the Error property of the view model with the first item in
the error messages list and re-render the signup page again.

As we are referring the liquid template path of the signup page in three places now,
let's create a label for this value and use the label in all the places.

43
Chapter 5 - Validating New User Signup Form

module Suave =
// ..
let signupTemplatePath = "user/signup.liquid"

let handleUserSignup ctx = async {


match bindEmptyForm ctx.request with
| Choice1Of2 (vm : UserSignupViewModel) ->
// ...
let onFailure msgs =
// ...
page signupTemplatePath viewModel ctx
// ...
| Choice2Of2 err ->
// ...
return! page signupTemplatePath viewModel ctx
}

let webPart () =
path "/signup"
>=> choose [
GET >=> page signupTemplatePath emptyUserSignupViewModel
// ...
]

Now if we build and run the application, we will be getting following console output
for valid signup details.

{Username = Username "demystifyfp";


Password = Password "secret";
EmailAddress = EmailAddress "demystifyfp@gmail.com";}

Summary
In this chapter, we learned how to do validation and transform view model to a
domain model using the Railway Programming technique with the help of the
Chessie library.

The source code for this chapter is available on GitHub

44
Chapter 5 - Validating New User Signup Form

45
Chapter 6 - Setting Up Database Migration

In the last chapter, we validated the signup details submitted by the user and
transformed it into a domain model.

The next step is persisting it in a database. We are going to use PostgreSQL to


achieve it.

In this sixth chapter, we are going to learn how to setup PostgreSQL database
migrations in fsharp using Fluent Migrator.

In the following chapter, we will be orchastrating the user signup.

Creating a Database Migrations Project


Fluent Migrator is one of the widely used Migration frameworks in .NET outside EF
code first migrations.

As we are not going to use EF in favor of SQLProvider, we are picking the fluent
migrator to help us in managing the database schema.

Let's get started by creating a new class library project, FsTweet.Db.Migrations, in


the src directory, using forge.

> forge new project -n FsTweet.Db.Migrations \


--folder src -t classlib --no-fake

This command creates this new project with .NET Framework 4.6.2 as Target. As we
are using a lower version, downgrade it to 4.6.1 by manually editing the
FsTweet.Db.Migrations.fsproj file.

- <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>

The next step is adding the FluentMigrator NuGet package and referring it in the
newly created FsTweet.Db.Migrations project.

> forge paket add FluentMigrator \


-p src/FsTweet.Db.Migrations/FsTweet.Db.Migrations.fsproj

46
Chapter 6 - Setting Up Database Migration

To create a migration in Fluent Migrator, we need to create a new class inheriting


Fluent Migrator's Migration abstract class.

This class also has to have an attribute Migration to specify the order of the
migration and also it should override the Up and Down methods.

Fsharp provides nicer support to write OO code. So writing the migration is straight
forward and we don't need to go back to C#!

As a first step, clean up the default code in the FsTweet.Db.Migrations.fs file and
update it as below.

namespace FsTweet.Db.Migrations

open FluentMigrator

[<Migration(201709250622L, "Creating User Table")>]


type CreateUserTable()=
inherit Migration()

override this.Up() = ()
override this.Down() = ()

As suggested by Sean Chambers, one of core contributor of fluent migrator, we are


using a time stamp in YYYYMMDDHHMM format in UTC to specify the migration order.

The next step is using the fluent methods offered by the fluent migrator we need to
define the Users table and its columns.

// ...
type CreateUserTable()=
// ...
override this.Up() =
base.Create.Table("Users")
.WithColumn("Id").AsInt32().PrimaryKey().Identity()
.WithColumn("Username").AsString(12).Unique().NotNullable()
.WithColumn("Email").AsString(254).Unique().NotNullable()
.WithColumn("PasswordHash").AsString().NotNullable()
.WithColumn("EmailVerificationCode").AsString().NotNullable()
.WithColumn("IsEmailVerified").AsBoolean()
|> ignore
// ...

47
Chapter 6 - Setting Up Database Migration

The last step is overriding the Down method. In the Down method, we just need to
delete the Users table.

type CreateUserTable()=
// ...
override this.Down() =
base.Delete.Table("Users") |> ignore
// ...

Building the Migrations Project


Now we have the migrations project in place, and it is all set to build and run.

Let's add a new FAKE Target BuildMigrations in the build script to build the
migrations.

// build.fsx
// ...
Target "BuildMigrations" (fun _ ->
!! "src/FsTweet.Db.Migrations/*.fsproj"
|> MSBuildDebug buildDir "Build"
|> Log "MigrationBuild-Output: "
)
// ...

Then we need to change the existing Build target to build only the FsTweet.Web

project instead of all the .fsproj projects in the application.

// build.fsx
// ...
Target "Build" (fun _ ->
!! "src/FsTweet.Web/*.fsproj"
|> MSBuildDebug buildDir "Build"
|> Log "AppBuild-Output: "
)
// ...

48
Chapter 6 - Setting Up Database Migration

To run the migration against Postgres, we need to install the Npgsql package from
NuGet.

> forge paket add Npgsql --version 3.1.10

At the time of this writing there is an issue with the latest version of Npgsql.
So, we are using the version 3.1.10 here.

FAKE has inbuilt support for running fluent migration from the build script.

To do it add the references of the FluentMigrator and Npgsql DLLs in the build
script.

// build.fsx
// ...
#r "./packages/FAKE/tools/Fake.FluentMigrator.dll"
#r "./packages/Npgsql/lib/net451/Npgsql.dll"
// ...
open Fake.FluentMigratorHelper
// ...

Then define RunMigrations Target with a connString and a dbConnection pointing to a


local database.

// build.fsx
// ...
let connString =
@"Server=127.0.0.1;Port=5432;Database=FsTweet;User Id=postgres;Password=test;"
let dbConnection = ConnectionString (connString, DatabaseProvider.PostgreSQL)

let migrationsAssembly =
combinePaths buildDir "FsTweet.Db.Migrations.dll"

Target "RunMigrations" (fun _ ->


MigrateToLatest dbConnection [migrationsAssembly] DefaultMigrationOptions
)
// ...

49
Chapter 6 - Setting Up Database Migration

This migration script doesn't create the database. It assumes that you are having a
PostgreSQL server with a database name FsTweet that can be connected using the
given connection string. You may need to change the connection string according to
your PostgreSQL server instance.

The last step in running the migration script is adding it to the build script build order.

We need to run the migrations before the Build target, as we need to have the
database schema in place to use SQLProvider to interact with the PostgreSQL.

// build.fsx
// ...
"Clean"
==> "BuildMigrations"
==> "RunMigrations"
==> "Build"
// ...

Then run the build.

> forge build

This command is an inbuilt alias in forge representing the forge fake Build

command.

While the build script is running, we can see the console log of the RunMigrations

target like the one below

...
Starting Target: RunMigrations (==> BuildMigrations)
...
----------------------------------------------------
201709250622: CreateUserTable migrating
----------------------------------------------------
[+] Beginning Transaction
[+] CreateTable Users
[+] Committing Transaction
[+] 201709250622: CreateUserTable migrated
[+] Task completed.
Finished Target: RunMigrations
...

50
Chapter 6 - Setting Up Database Migration

Upon successful execution of the build script, we can verify the schema using psql

> psql -d FsTweet


psql (9.6.2, server 9.5.1)
Type "help" for help.

FsTweet=# \d "Users"
Table "public.Users"

Column | Type | Modifiers


-----------------------+------------------------+-------------------------------------
-----------------
Id | integer | not null default nextval('"Users_Id_
seq"'::regclass)
Username | character varying(12) | not null
Email | character varying(254) | not null
PasswordHash | text | not null
EmailVerificationCode | text | not null
IsEmailVerified | boolean | not null
Indexes:
"PK_Users" PRIMARY KEY, btree ("Id")
"IX_Users_Email" UNIQUE, btree ("Email")
"IX_Users_Username" UNIQUE, btree ("Username")

Cool! The migrations went well :)

Extending the Connection String


In the script that we ran, the connection string is hard coded. To make it reusable
across different build environments, we need to get it from the environment variable.

FAKE has function environVarOrDefault , which takes the value from the given
environment name and if the environment variable is not available, it returns the
provided default value.

Let's use this function in our build script to make it reusable

// build.fsx
// ...
let connString =
environVarOrDefault
"FSTWEET_DB_CONN_STRING"
@"Server=127.0.0.1;Port=5432;Database=FsTweet;User Id=postgres;Password=test;"
// ...

51
Chapter 6 - Setting Up Database Migration

That's it!

Summary
In this chapter, we learned how to set up database migration using Fluent Migrator in
fsharp and leverage FAKE to run the migrations while running the build script.

The source code for this chapter is available on GitHub

52
Chapter 7 - Orchestrating User Signup

Before diving into the implementation of the user signup use case, let's spend some
time to jot down its requirements.

1. If the user submitted invalid details, we should let him/her know about the error
(which we already implemented in the fifth chapter part)

2. We also need to check the whether the username or the email provided by the
user has been already used by someone else and report it if we found it is not
available.

3. If all the details are well, then we need to persist the user details with his
password hashed and also a randomly generated verification code.

4. Then we need to send an email to the provided email address with the
verification code.

5. Upon receiving an URL with the verification code, the user will be navigating to
the provided URL to complete his signup process.

In this chapter, we are going to implement the service layer part of the user signup
which coordinates the steps two, three and four.

Generating Password Hash


As a first step, let's create the hash for the password provided by the user.

To generate the hash, we are going to use the Bcrypt algorithm. In .NET we can use
the Bcrypt.Net library to create the password hash using the Bcrypt algorithm.

Let's add its NuGet package to our Web project

> forge paket add BCrypt.Net-Next --version 2.1.1 \


-p src/FsTweet.Web/FsTweet.Web.fsproj

Then in the Domain module add a new type, PasswordHash

53
Chapter 7 - Orchestrating User Signup

// UserSignup.fs
module Domain =
// ...
open BCrypt.Net
// ...
type PasswordHash = private PasswordHash of string with
member this.Value =
let (PasswordHash passwordHash) = this
passwordHash

static member Create (password : Password) =


BCrypt.HashPassword(password.Value)
|> PasswordHash

As we did for the other Domain types, PasswordHash has a private constructor
function to prevent it from creating from outside.

The static function Create takes care of creating the password hash from the
provided password using the Bcrypt library.

The Value property provides the underlying string representation of the


PasswordHash type. We will be using while persisting the user details.

We are placing all the Domain types in UserSignup namespace now. Some of
the types that we declared here may be needed for the other use cases. We
will be doing the module reorganization when we require it.

Generating Random Verification Code


Like PasswordHash , let's create a domain type for the verification code with a Value

property and a static function to create it.

// UserSignup.fs
module Domain =
// ...
open System.Security.Cryptography
// ...
let base64URLEncoding bytes =
let base64String =
System.Convert.ToBase64String bytes
base64String.TrimEnd([|'='|])
.Replace('+', '-').Replace('/', '_')

54
Chapter 7 - Orchestrating User Signup

type VerificationCode = private VerificationCode of string with

member this.Value =
let (VerificationCode verificationCode) = this
verificationCode

static member Create () =


let verificationCodeLength = 15
let b : byte [] =
Array.zeroCreate verificationCodeLength

use rngCsp = new RNGCryptoServiceProvider()


rngCsp.GetBytes(b)

base64URLEncoding b
|> VerificationCode

We are making use of RNGCryptoServiceProvider from the .NET standard library to


generate the random bytes and convert them to a string using Base64Encoding and
making it safer to use in URL as mentioned in this StackOverflow answer.

Canonicalizing Username And Email


Address
To enable the uniqueness check on the Username and the EmailAddress fields, we
need to canonicalize both of them.

In our case, trimming the white-space characters and converting to the string to
lower case should suffice.

To do it, we can use the existing TryCreate function in the Username and
EmailAddress type.

type Username = private Username of string with


static member TryCreate (username : string) =
match username with
// ...
| x ->
x.Trim().ToLowerInvariant()
|> Username |> ok
// ...

// ...

55
Chapter 7 - Orchestrating User Signup

type EmailAddress = private EmailAddress of string with


// ...
static member TryCreate (emailAddress : string) =
try
// ...
emailAddress.Trim().ToLowerInvariant()
|> EmailAddress |> ok
// ...

A Type For The Create User Function


We now have both the PasswordHash and the random VerifcationCode in place to
persist them along with the canonicalized Username and EmailAddress .

As a first step towards persisting new user details, let's define a type signature for
the Create User function that we will be implementing in an upcoming chapter.

First, we need a type to represent the create user request

// UserSignup.fs
module Domain =
// ...
type CreateUserRequest = {
Username : Username
PasswordHash : PasswordHash
Email : EmailAddress
VerificationCode : VerificationCode
}
// ...

Then we need to have a type for the response. We will be returning the primary key
that has been generated from the PostgreSQL database.

// UserSignup.fs
module Domain =
// ...
type UserId = UserId of int
// ...

As creating a new user is a database operation, things might go wrong. We also


need to account the uniqueness check of the Username and the Email properties.

56
Chapter 7 - Orchestrating User Signup

Let's define types for accommodating these scenarios as well.

// UserSignup.fs
module Domain =
// ...
type CreateUserError =
| EmailAlreadyExists
| UsernameAlreadyExists
| Error of System.Exception
// ...

With the help of the types that we declared so far, we can now declare the type for
the create user function

type CreateUser =
CreateUserRequest -> AsyncResult<UserId, CreateUserError>

The AsyncResult type is from the Chessie library. It represents the Result of an
asynchronous computation.

A Type For The Send Signup Email


Function
Upon creating a new user, we need to send a new signup email to the user. Let's
create type for this as we did for CreateUser .

The inputs for this function are Username , EmailAddress , and the VerificationCode .

// UserSignup.fs
module Domain =
// ...
type SignupEmailRequest = {
Username : Username
EmailAddress : EmailAddress
VerificationCode : VerificationCode
}
// ...

As sending an email may fail, we need to have a type for representing it as well

57
Chapter 7 - Orchestrating User Signup

module Domain =
// ...
type SendEmailError = SendEmailError of System.Exception
// ...

If the email sent successfully, we would be returning unit .

With the help of these two types, we can declare the SendSignupEmail type as

module Domain =
// ...
type SendSignupEmail =
SignupEmailRequest -> AsyncResult<unit, SendEmailError>
// ...

Defining The SignupUser Function


Signature
The SignupUser function makes use of CreateUser and SendSignupEmail functions to
complete the user sign up process.

In addition to these two functions, the SignupUser function takes a record of type
UserSignupRequest as its input but what about the output?

type SignupUser =
CreateUser -> SendSignupEmail -> UserSignupRequest
-> ???

There are possible outcomes

1. CreateUser may fail


2. SendSignupEmail may fail
3. User successfully signed up.

We can group the two failure conditions into a single type

58
Chapter 7 - Orchestrating User Signup

module Domain =
// ...
type UserSignupError =
| CreateUserError of CreateUserError
| SendEmailError of SendEmailError
// ...

For successful signup, we will be returning a value of type UserId , which we


declared earlier.

type SignupUser =
CreateUser -> SendSignupEmail -> UserSignupRequest
-> AsyncResult<UserId, UserSignupError>

We are not going to use this SignupUser type anywhere else, and it is just for
illustration. In the later chapters, we'll see how to make use of this kind of type
aliases.

Implementing The SignupUser Function


Now we know the inputs and the outputs of the SignupUser function, and it is time to
get our hands dirty!

module Domain =
// ...
let signupUser (createUser : CreateUser)
(sendEmail : SendSignupEmail)
(req : UserSignupRequest) = asyncTrial {
// TODO
}

Like the trial computation that we used to do the user signup form validation, the
asyncTrail computation expression is going to help us here to do the error handling
in asynchronous operations.

The first step is creating a value of type CreateUserRequest from UserSignupRequest

59
Chapter 7 - Orchestrating User Signup

let signupUser ... (req : UserSignupRequest) = asyncTrail {


let createUserReq = {
PasswordHash = PasswordHash.Create req.Password
Username = req.Username
Email = req.EmailAddress
VerificationCode = VerificationCode.Create()
}
// TODO
}

The ... notation is just a convention that we are using here to avoid
repeating the parameters, and it is not part of the fsharp language syntax

The next step is calling the createUser function with the createUserReq

let signupUser (createUser : CreateUser) ... = asyncTrail {


let createUserReq = // ...
let! userId = createUser createUserReq
// TODO
}

Great! We need to send an email now. Let's do it!

Steps involved are creating a value of type SignupEmailRequest and calling the
sendEmail function with this value.

As the sendEmail function returning unit on success, we can use the do! notation
instead of let!

let signupUser ... (sendEmail : SendSignupEmail) ... = asyncTrail {


let createUserReq = // ...
let! userId = // ...
let sendEmailReq = {
Username = req.Username
VerificationCode = createUserReq.VerificationCode
EmailAddress = createUserReq.Email
}
do! sendEmail sendEmailReq
// TODO
}

Now you would be getting a compiler error

60
Chapter 7 - Orchestrating User Signup

Would you be able to find why are we getting this compiler error?

To figure out the solution, let's go back to the TryCreate function in UserSignupRequest

type.

// ...
with static member TryCreate (username, password, email) =
trial {
let! username = Username.TryCreate username
let! password = Password.TryCreate password
let! emailAddress = EmailAddress.TryCreate email
return {
Username = username
Password = password
EmailAddress = emailAddress
}
}

The signature of the TryCreate function is

(string, string, string) -> Result<UserSignupRequest, string>

The signature of the TryCreate function of the Domain types are

string -> Result<Username, string>


string -> Result<Password, string>
string -> Result<EmailAddress, string>

Let's focus our attention to the type that represents the result of a failed computation

... -> Result<..., string>


... -> Result<..., string>
... -> Result<..., string>

61
Chapter 7 - Orchestrating User Signup

All are of string type!

Coming back to the signupUser function what we are implementing, here is a type
signature of the functions

... -> AsyncResult<..., CreateUserError>


... -> AsyncResult<..., SendEmailError>

In this case, the types that are representing the failure are of different type. That's
thing that we need to fix!

If we transform (or map) the failure type to UserSignupError , then things would be
fine!

62
Chapter 7 - Orchestrating User Signup

Mapping AsyncResult Failure Type


You may find this section hard or confusing to get it in the first shot. A
recommended approach would be working out the following implementation
on your own and use the implementation provided here as a reference. And
also if you are thinking of taking a break, this is a right time!

We already have a union cases which maps CreateUserError and SendEmailError

types to UserSignupError

type UserSignupError =
| CreateUserError of CreateUserError
| SendEmailError of SendEmailError

These union case identifiers are functions which have the following signature

CreateUserError -> UserSignupError


SendEmailError -> UserSignupError

63
Chapter 7 - Orchestrating User Signup

But we can't use it directly, as the CreateUserError and the SendEmailError are part of
the AsyncResult type!

What we want to achieve is mapping

AsyncResult<UserId, CreateUserError>

to

AsyncResult<UserId, UserSignupError>

and

AsyncResult<unit, SendEmailError>

to

AsyncResult<unit, UserSignupError>

Accomplishing this mapping is little tricky.

Let's start our mapping by defining a new function called mapAsyncFailure

// UserSignup.fs
module Domain =
// ...
let mapAsyncFailure f aResult =
// TODO

The mapAsyncFailure function is a generic function with the following type signature.

'a -> 'b -> AsyncResult<'c, 'a> -> AsyncResult<'c, 'b>

It takes a function f which maps a type a to b and an AsyncResult as its input.


Its output is an AsyncResult with its failure type mapped using the given function f .

64
Chapter 7 - Orchestrating User Signup

The first step to do this mapping is to transform

AsyncResult<'c, 'a>

to

Async<Result<'c, 'a>>

The AsyncResult type is defined in Chessie as a single case discriminated union


case AR

type AsyncResult<'a, 'b> =


| AR of Async<Result<'a, 'b>>

The Chessie library already has a function, ofAsyncResult , to carry out this
transformation (or unboxing!)

let mapAsyncFailure f aResult =


aResult
|> Async.ofAsyncResult

The next step is mapping the value that is part of the Async type.

Async<'a> -> Async<'b>

We can again make use of the Chessie library again by using its map function. This
map function like other map functions in the fsharp standard module takes a function
as its input to do the mapping.

'a -> 'b -> Async<'a> -> Async<'b>

The easier way to understand is to think Async as a box. All mapping function does
is takes the value out of the Async box, perform the mapping using the provided
function, then put it to back to a new Async box and return it.

65
Chapter 7 - Orchestrating User Signup

But what is the function that we need to give to the map function to do the mapping

let mapAsyncFailure f aResult =


aResult
|> Async.ofAsyncResult
|> Async.map ???

We can't give the CreateUserError union case function directly as the f parameter
here; it only maps CreateUserError to UserSignupError .

The reason is, the value inside the Async is not CreateUserError , it's Result<UserId,

CreateUserError> .

We need to have an another map function which maps the failure part of the Result

type

66
Chapter 7 - Orchestrating User Signup

Let's assume that we have mapFailure function which takes a function f to do this
failure type mapping on the Result type.

We can continue with the definition of the mapAsyncFailure function using this
mapFailure function.

let mapAsyncFailure f aResult =


aResult
|> Async.ofAsyncResult
|> Async.map (mapFailure f)

The final step is putting the Async of Result type back to AsyncResult type. As
AsyncResult is defined as single case discriminated union, we can use the AR union
case to complete the mapping.

let mapAsyncFailure f aResult =


aResult
|> Async.ofAsyncResult
|> Async.map (mapFailure f)
|> AR

The mapFailure is not part of the codebase yet. So, Let's add it before going back to
the signupUser function.

The Chessie library already has a mapFailure function. But the mapping function
parameter maps a list of errors instead of a single error

67
Chapter 7 - Orchestrating User Signup

'a list -> 'b list -> Result<'c, 'a list> -> Result<'c, 'b list>

The reason for this signature is because the library treats failures as a list in the
Result type.

As we are treating the failure in the Result type as a single item, we can't directly
make use of it.

However, we can leverage it to fit our requirement by having an implementation,


which takes the first item from the failure list, call the mapping function and then
create a list from the output of the map function.

// UserSignup.fs
module Domain =
// ...
let mapFailure f aResult =
let mapFirstItem xs =
List.head xs |> f |> List.singleton
mapFailure mapFirstItem aResult
// ...

This mapFailure function has the signature

'a -> 'b -> Result<'c, 'a> -> Result<'c, 'b>

With this, we are done with the mapping of AsyncResult failure type.

Going Back To The signupUser Function


In the previous section, we implemented a solution to fix the compiler error that we
encountered while defining the signupUser function.

With the mapAsyncFailure function, we can rewrite the signupUser function to


transform the error type and return the UserId if everything goes well.

68
Chapter 7 - Orchestrating User Signup

module Domain =
// ...
let signupUser ... = asyncTrial {

let createUserReq = // ...


let! userId =
createUser createUserReq
|> mapAsyncFailure CreateUserError
let sendEmailReq = // ...
do! sendEmail sendEmailReq
|> mapAsyncFailure SendEmailError

return userId
}

That's it!!

Summary
One of the key take away of this chapter is how we can solve a complex problem in
fsharp by just focusing on the function signature.

We also learned how to compose functions together, transforming the values using
the map function to come up with a robust implementation.

The source code is available on the GitHub Repository

69
Chapter 8 - Transforming Async Result to Webpart

Hi there!

In the last chapter, using the Chessie library, we orchestrated the user signup
process.

There are three more things that we need to do wrap up the user signup workflow.

1. Providing implementation for creating a new user in PostgreSQL

2. Integrating with an email provider to send the signup email

3. Adding the presentation layer to inform the user about his/her progress in the
signup process.

In this chapter, we are going to pick the third item. We will be faking the
implementation of user creation and sending an email.

Domain To Presentation Layer


We have seen the expressive power of functions which transform a value from one
type to an another in the previous post.

We can apply the same thing while creating a presentation layer for a domain
construct.

Let's take our scenario.

The domain layer returns AsyncResult<UserId, UserSignupError> and the presentation


layer needs WebPart as we are using Suave.

So, all we need is a function with the following signature.

UserSignupViewModel ->
AsyncResult<UserId, UserSignupError> -> Async<WebPart>

The UserSignupViewModel is required communicate the error details with the user
along with the information that he/she submitted.

Let's start our implementation by creating a new function handleUserSignupAsyncResult

in the Suave module.

70
Chapter 8 - Transforming Async Result to Webpart

// UserSignup.fs
...
module Suave =
// ...
let handleUserSignupAsyncResult viewModel aResult =
// TODO

let handleUserSignup ... = // ...

We are using the prefix handle instead of map here as we are going to do a
side effect (printing in console in case of error) in addition to the transforming
the type.

The first step is transforming

AsyncResult<UserId, UserSignupError>

to

Async<Result<UserId, UserSignupError>>

As we seen in the previous post, we can make use of the ofAsyncResult function
from Chessie, to do it

let handleUserSignupAsyncResult viewModel aResult =


aResult
|> Async.ofAsyncResult
// TODO

The next step is transforming

Async<Result<UserId, UserSignupError>>

to

Async<WebPart>

71
Chapter 8 - Transforming Async Result to Webpart

As we did for mapping Async Failure in the previous post, We make use of the map

function on the Async module to carry out this transformation.

Let's assume that we have a method handleUserSignupResult which maps a Result

type to WebPart

UserSignupViewModel -> Result<UserId, UserSignupError> -> WebPart

We can complete the handleUserSignupAsyncResult function as

let handleUserSignupAsyncResult viewModel aResult =


aResult
|> Async.ofAsyncResult
|> Async.map (handleUserSignupResult viewModel)

The map function in the Async module is an extension provided by the


Chessie library, and it is not part of the standard Async module

Now we have a scaffolding for transforming the domain type to the presentation type.

Transforming UserSignupResult to
WebPart
In this section, we are going to define the handleUserSignupResult function that we left
as a placeholder in the previous section.

We are going to define it by having separate functions for handling success and
failures and then use them in the actual definition of handleUserSignupResult

If the result is a success, we need to redirect the user to a signup success page.

// UserSignup.fs
...
module Suave =
// ...
let handleUserSignupSuccess (viewModel : UserSignupViewModel) _ =
sprintf "/signup/success/%s" viewModel.Username
|> Redirection.FOUND
// let handleUserSignupAsyncResult viewModel aResult = ...

72
Chapter 8 - Transforming Async Result to Webpart

We are leaving the second parameter as _ , as we are not interested in the result of
the successful user signup ( UserId ) here.

We will be handing the path /signup/success/{username} later in this chapter.

In case of failure, we need to account for two kinds of error

1. Create User Error

2. Send Email Error

let's define separate functions for handing each kind of error.

module Suave =
// ...
let handleCreateUserError viewModel = function
| EmailAlreadyExists ->
let viewModel =
{viewModel with Error = Some ("email already exists")}
page signupTemplatePath viewModel
| UsernameAlreadyExists ->
let viewModel =
{viewModel with Error = Some ("username already exists")}
page signupTemplatePath viewModel
| Error ex ->
printfn "Server Error : %A" ex
let viewModel =
{viewModel with Error = Some ("something went wrong")}
page signupTemplatePath viewModel
// ...

We are updating the Error property with the appropriate error messages and re-
render the signup page in case of unique constraint violation errors.

For exceptions, which we modeled as Error here, we re-render the signup page
with an error message as something went wrong and printed the actual error in the
console.

Ideally, we need to have a logger to capture these errors. We will be implementing


them in an another chapter.

We need to do the similar thing for handling error while sending emails.

73
Chapter 8 - Transforming Async Result to Webpart

module Suave =
// ...
let handleSendEmailError viewModel err =
printfn "error while sending email : %A" err
let msg =
"Something went wrong. Try after some time"
let viewModel =
{viewModel with Error = Some msg}
page signupTemplatePath viewModel
// ...

To avoid the complexity, we are just printing the error.

Then define the handleUserSignupError function which handles the UserSignupError

using the two functions that we just defined.

module Suave =
// ...
let handleUserSignupError viewModel errs =
match List.head errs with
| CreateUserError cuErr ->
handleCreateUserError viewModel cuErr
| SendEmailError err ->
handleSendEmailError viewModel err
// ...

The errs parameter is a list of UserSignupError as the Result type models failures
as lists.

In our application, we are treating it as a list with one error.

Now we have functions to transform both the Sucess and the Failure part of the
UserSignupResult .

With the help of these functions, we can define the handleUserSignupResult using the
either function from Chessie

74
Chapter 8 - Transforming Async Result to Webpart

// UserSignup.fs
...
module Suave =
// ...
let handleUserSignupResult viewModel result =
either
(handleUserSignupSuccess viewModel)
(handleUserSignupError viewModel) result
// ...

With this, we are done with the following transformation.

AsyncResult<UserId, UserSignupError> -> Async<WebPart>

Wiring Up WebPart
In the previous section, we defined functions to transform the result of a domain
functionality to its corresponding presentation component.

The next work is wiring up this presentation component with the function which
handles the user signup POST request.

As a recap, here is a skeleton of the request handler function that we already defined
in the fifth chapter.

let handleUserSignup ctx = async {


match bindEmptyForm ctx.request with
| Choice1Of2 (vm : UserSignupViewModel) ->
let result = // ...
let onSuccess (userSignupRequest, _) =
printfn "%A" userSignupRequest
Redirection.FOUND "/signup" ctx
let onFailure msgs =
let viewModel =
{vm with Error = Some (List.head msgs)}
page "user/signup.liquid" viewModel ctx
return! either onSuccess onFailure result
| Choice2Of2 err ->
// ..
// ...
}

75
Chapter 8 - Transforming Async Result to Webpart

As a first step towards wiring up the user signup result, we need to use the pattern
matching on the validation result instead of using the either function.

let handleUserSignup ctx = async {


// ...
| Choice1Of2 (vm : UserSignupViewModel) ->
let result = // ...
match result with
| Ok (userSignupReq, _) ->
printfn "%A" userSignupReq
return! Redirection.FOUND "/signup" ctx
| Bad msgs ->
let viewModel =
{vm with Error = Some (List.head msgs)}
return! page "user/signup.liquid" viewModel ctx
| Choice2Of2 err -> // ...
// ...
}

The reason for this split is we will be doing an asynchronous operation if the request
is valid. For the invalid request, there is no asynchronous operation involved.

The next step is changing the signature of the handleUserSignup to take signupUser

function as its parameter

let handleUserSignup signupUser ctx = async {


// ...
}

This signupUser is a function with the signature

UserSignupRequest -> AsyncResult<UserId, UserSignupError>

It is equivalent to the SignupUser type, without the dependencies

type SignupUser =
CreateUser -> SendSignupEmail ->
UserSignupRequest -> AsyncResult<UserId, UserSignupError>

Then in the pattern matching part of the valid request, replace the placeholders
(printing and redirecting) with the actual functionality

76
Chapter 8 - Transforming Async Result to Webpart

let handleUserSignup signupUser ctx = async {


// ...
| Choice1Of2 (vm : UserSignupViewModel) ->
match result with
| Ok (userSignupReq, _) ->
let userSignupAsyncResult = signupUser userSignupReq
let! webpart =
handleUserSignupAsyncResult vm userSignupAsyncResult
return! webpart ctx
// ...
}

For valid signup request, we call the signupUser function and then pass the return
value of this function to the handleUserSignupAsyncResult function which returns an
Async<WebPart>

Through let! binding we retrieve the WebPart from Async<WebPart> and then using it
to send the response back to the user.

WebPart is a type alias of a function with the signature

HttpContext -> Async<HttpContext option>

Adding Fake Implementations for


Persistence and Email
As mentioned earlier, we are going to implement the actual functionality of
CreateUser and SendSignupEmail in the later chapters.

But that doesn't mean we need to wait until the end to see the final output in the
browser.

These two types are just functions! So, We can provide a fake implementation of
them and exercise the functionality that we wrote!

Let's add two more modules above the Suave module with these fake
implementations.

77
Chapter 8 - Transforming Async Result to Webpart

// UserSignup.fs
// ...
module Persistence =
open Domain
open Chessie.ErrorHandling

let createUser createUserReq = asyncTrial {


printfn "%A created" createUserReq
return UserId 1
}

module Email =
open Domain
open Chessie.ErrorHandling

let sendSignupEmail signupEmailReq = asyncTrial {


printfn "Email %A sent" signupEmailReq
return ()
}
// ...

The next step is using the fake implementation to complete the functionality

// ...
module Suave =
// ...
let webPart () =
let createUser = Persistence.createUser
let sendSignupEmail = Email.sendSignupEmail
let signupUser =
Domain.signupUser createUser sendSignupEmail
path "/signup"
>=> choose [
// ...
POST >=> handleUserSignup signupUser
]

There are two patterns that we have employed here.

Dependency Injection using Partial Application

We partially applied the first two parameters of the signupUser function to


inject the dependencies that are responsible for creating the user and
sending the signup email. Scott Wlaschin has written an excellent article
on this subject.

78
Chapter 8 - Transforming Async Result to Webpart

Composition Root

Now we can run the application.

If we try to signup with a valid user signup request, we will get the following output in
the console

{Username = Username "demystifyfp";


PasswordHash =
PasswordHash "$2a$10$UZczy11hA0e/2v0VlrmecehGlWv/OlxBPyFEdL4vObxAL7wQw0g/W";
Email = EmailAddress "demystifyfp@gmail.com";
VerificationCode = VerificationCode "oCzBXDY5wIyGlNFuG76a";} created
Email {Username = Username "demystifyfp";
EmailAddress = EmailAddress "demystifyfp@gmail.com";
VerificationCode = VerificationCode "oCzBXDY5wIyGlNFuG76a";} sent

and in the browser, we will get an empty page

79
Chapter 8 - Transforming Async Result to Webpart

Adding Signup Success Page


The final piece of work is adding a signup success page

Create a new liquid template in the views/user directory

<!-- views/user/signup_success.liquid -->


{% extends "master_page.liquid" %}

{% block head %}
<title> Signup Success </title>
{% endblock %}

{% block content %}
<div class="container">
<p class="well">
Hi {{ model }}, Your account has been created.
Check your email to activate the account.
</p>
</div>
{% endblock %}

This liquid template makes use of view model of type string to display the user name

The next step is adding a route for rendering this template with the actual user name
in the webpart function.

As we are now exposing more than one paths in user signup (one for the request
and another for the successful signup), we need to use the choose function to define
a list of WebPart s.

// UserSignup.fs
// ...
module Suave =
let webPart () =
// ...
choose [
path "/signup"
// ...
pathScan
"/signup/success/%s"
(page "user/signup_success.liquid")
]

80
Chapter 8 - Transforming Async Result to Webpart

The pathScan from Suave enable us to do strongly typed pattern matching on the
route. It takes a string (route) with PrintfFormat string and a function with parameters
matching the values in the route.

Here the user name is being matched on the route. Then we partially apply page
function with one parameter representing the path of the liquid template.

Now if we run the application, we will get the following page upon receiving a valid
user signup request.

That's it :)

Summary
In this chapter, we learned how to transform the result representation of a domain
functionality to its corresponding view layer representation.

The separation of concerns enables us to add a new Web RPC API or even
replacing Suave with any other library/framework without touching the existing
functionality.

The source code of this chapter is available on GitHub

81
Chapter 9 - Persisting New User

We are on track to complete the user signup feature. In this chapter, we are going to
implement the persistence layer for creating a user which we faked in the last
chapter.

Initializing SQLProvider
We are going to use SQLProvider, a SQL database type provider, to takes care of
PostgreSQL interactions,

As usual, let's add its NuGet package to our Web project using paket

> forge paket add SQLProvider --version 1.1.7 \


-p src/FsTweet.Web/FsTweet.Web.fsproj

Then we need to initialize SQLProvider by providing the required parameters.

To do it, let's add a separate fsharp file Db.fs in the Web Project

> forge newFs web -n src/FsTweet.Web/Db

Then move this file above UserSignup.fs

> forge moveUp web -n src/FsTweet.Web/Db.fs


> forge moveUp web -n src/FsTweet.Web/Db.fs

We are making use of the Forge alias that we set in the fourth chapter

The next step is initializing the SQLProvider with all the required parameters

82
Chapter 9 - Persisting New User

// src/FsTweet.Web/Db.fs
module Database

open FSharp.Data.Sql

[<Literal>]
let private connString =
"Server=127.0.0.1;Port=5432;Database=FsTweet;" +
"User Id=postgres;Password=test;"

[<Literal>]
let private npgsqlLibPath =
@"./../../packages/Npgsql/lib/net451"

[<Literal>]
let private dbVendor =
Common.DatabaseProviderTypes.POSTGRESQL

type Db = SqlDataProvider<
ConnectionString=connString,
DatabaseVendor=dbVendor,
ResolutionPath=npgsqlLibPath,
UseOptionTypes=true>

The type Db represents the PostgreSQL database provided in the connString

parameter. The connString that we are using here is the same one that we used
while running the migration script. You may have to change it to connect your
database.

Like DbContext in Entity Framework, the SQLProvider offers a dataContext type to


deal with the database interactions.

The dataContext is specific to the database that we provided in the connection string,
and this is available as a property of the Db type.

As we will be passing this dataContext object around, in all our data access
functions, we can define a specific type for it to save some key strokes!

module Database

// ...
type DataContext = Db.dataContext

83
Chapter 9 - Persisting New User

Runtime Configuration of SQLProvider


In the previous section, we configured SQLProvider to enable typed access to the
database. Upon initialization, it queries the meta tables of PostgreSQL database and
creates types. These types can be accessed via DataContext

It's okay for developing an application and compiling it.

But when the application goes live, we will be certainly pointing to a separate
database! To use a different PostgreSQL database at run time, we need a separate
DataContext pointing to that database.

As suggested by the Twelve-Factor app, let's use an environment variable to provide


the connection string.

We are already using one in our build script, which contains the connection string for
the migration script.

// build.fsx
//...
let connString =
environVarOrDefault
"FSTWEET_DB_CONN_STRING"
@"Server=127.0.0.1;Port=5432;..."
let dbConnection =
ConnectionString (connString, DatabaseProvider.PostgreSQL)
//...

The connString label here takes the value from the environment variable
FSTWEET_DB_CONN_STRING if it exists otherwise it picks a default one

If we set the value of this connString again to FSTWEET_DB_CONN_STRING environment


variable, we are ready to go.

Fake has an environment helper function setEnvironVar for this

// build.fsx
// ...

84
Chapter 9 - Persisting New User

setEnvironVar "FSTWEET_DB_CONN_STRING" connString


// ...

Now if we run the application using the fake build script, the environment variable
FSTWEET_DB_CONN_STRING always has value!

The next step is using this environment variable to get a new data context.

DataContext One Per Request


That data context that is being exposed by the SQLProvider uses the unit of work
pattern underneath.

So, while using SQLProvider in an application that can be used by multiple users
concurrently, we need to create a new data context for every request from the user
that involves database operation.

Let's assume that we have a function getDataContext , that takes a connection string
and returns its associated SQLProvider's data context. There are two ways that we
can use this function to create a new data context per request.

1. For every database layer function, we can pass the connection string and inside
that function that we can call the getDataContext using the connection string.

2. An another option would be modifying the getDataContext function to return an


another function that takes a parameter of type unit and returns the data
context of the provided connection string.

We are going to use the second option as its hides the details of getting an
underlying data context.

Let's see it in action to understand it better

As a first step, define a type that represents the factory function to create a data
context.

// src/FsTweet.Web/Db.fs
// ...
type GetDataContext = unit -> DataContext

85
Chapter 9 - Persisting New User

Then define the actual function

// src/FsTweet.Web/Db.fs
// ...
let dataContext (connString : string) : GetDataContext =
fun _ -> Db.GetDataContext connString

Then in the application bootstrap get the connection string value from the
environment variable and call this function to get the factory function to create data
context for every request

// src/FsTweet.Web/FsTweet.Web.fs
// ...
open System
open Database
// ...
let main argv =
let fsTweetConnString =
Environment.GetEnvironmentVariable "FSTWEET_DB_CONN_STRING"
let getDataCtx = dataContext fsTweetConnString

// ...

The next step is passing the GetDataContext function to the request handlers which
we will address later in this chapter.

Async Transaction in Mono


At the time of this writing, SQLProvider doesn't support transactions in Mono as the
TransactionScopeAsyncFlowOption is not implemented in Mono.

So, if we use the datacontext from the above factory function in mono, we may get
some errors associated with transaction when we asynchronously write any data to
the database

To circumvent this error, we can disable transactions in mono alone.

86
Chapter 9 - Persisting New User

let dataContext (connString : string) : GetDataContext =


let isMono =
System.Type.GetType ("Mono.Runtime") <> null
match isMono with
| true ->
let opts : Transactions.TransactionOptions = {
IsolationLevel =
Transactions.IsolationLevel.DontCreateTransaction
Timeout = System.TimeSpan.MaxValue
}
fun _ -> Db.GetDataContext(connString, opts)
| _ ->
fun _ -> Db.GetDataContext connString

Note: This is NOT RECOMMENDED in production.

With this, we are done with the runtime configuration of SQLProvider

Implementing Create User Function


In the existing fake implementation of the createUser add getDataCtx as its first
parameter and get the data context inside the function.

// src/FsTweet.Web/UserSignup.fs
// ...
module Persistence =
// ...
open Database

let createUser (getDataCtx : GetDataContext)


(createUserReq : CreateUserRequest) = asyncTrial {
let ctx = getDataCtx ()
// TODO
}

87
Chapter 9 - Persisting New User

We need to explicitly specify the type of the parameter GetDataContext to use the
types provided by the SQLProvider.

The next step is creating a new user from the createUserReq

let createUser ... = asyncTrail {


let ctx = getDataCtx ()

let users = ctx.Public.Users


let newUser = users.Create()

newUser.Email <- createUserReq.Email.Value


newUser.EmailVerificationCode <-
createUserReq.VerificationCode.Value
newUser.Username <- createUserReq.Username.Value
newUser.IsEmailVerified <- false
newUser.PasswordHash <- createUserReq.PasswordHash.Value
// TODO
}

Then we need to call the SubmitUpdatesAsync method on the ctx and return the Id

of the newly created user.

let createUser ... = asyncTrail {


// ...
do! ctx.SubmitUpdatesAsync()
return UserId newUser.Id
}

Though it appears like that we have completed the functionality, one important task
is pending in this function.

Yes, It's Error Handling!

Let's examine the return type of SubmitUpdatesAsync method, Async<unit> . In case of


an error, while submitting the changes to the database, this method will throw an
exception. It also applies to unique violation errors in the Username and Email

columns in the Users table. That's not what we want!

We want a value of type CreateUserError to represent the errors!

As we did for transforming the UserSignupResult to WebPart in the last chapter, we


need to transform AsyncResult<UserId, 'a> to AsyncResult<UserId, CreateUserError>

88
Chapter 9 - Persisting New User

Async Exception to Async Result


As a first step, the first transformation that we need to work on is returning an
AsyncResult<unit,Exception> instead of Async<unit> and an exception when we call
SubmitUpdatesAsync on the DataContext object.

To do, let's create a function submitUpdates in Database module that takes a


DataContext as its parameter

// src/FsTweet.Web/Db.fs
module Database
// ...
let submitUpdates (ctx : DataContext) =
// TODO

Then call the SubmitUpdatesAsync method and use Async.Catch function from the
Async standard module which catches the exception thrown during the
asynchronous operation and return the result of the operation as a Choice type.

let submitUpdates (ctx : DataContext) =


ctx.SubmitUpdatesAsync() // Async<unit>
|> Async.Catch // Async<Choice<unit, System.Exception>>
// TODO

The return type of each function has been added as comments for clarity!

The next step is mapping the Async<Choice<'a, 'b>> to Async<Result<'a, 'b>>

The Chessie library has a function ofChoice which transforms a Choice type to a
Result type. With the help of this function and the Async.map function from Chessie
library we can do the following

module Database
// ...
open Chessie.ErrorHandling
// ...

89
Chapter 9 - Persisting New User

let submitUpdates (ctx : DataContext) =


ctx.SubmitUpdatesAsync() // Async<unit>
|> Async.Catch // Async<Choice<unit, System.Exception>>
|> Async.map ofChoice // Async<Result<unit, System.Exception>>
// TODO

The final step is transforming it to AsyncResult by using the AR union case as we did
while mapping the Failure type of AsyncResult.

// DataContext -> AsyncResult<unit, System.Exception>


let submitUpdates (ctx : DataContext) =
ctx.SubmitUpdatesAsync() // Async<unit>
|> Async.Catch // Async<Choice<unit, System.Exception>>
|> Async.map ofChoice // Async<Result<unit, System.Exception>>
|> AR

Now we have a functional version of the submitUpdatesAsync method which returns an


AsyncResult .

Mapping AsyncResult Failure Type


If you got it right, you could have noticed that we are yet to do a step to complete the
error handling.

We need to transform the failure type of the Async Result from

AsyncResult<unit, System.Exception>

to

AsyncResult<unit, CreateUserError>

As this very similar to what we did while mapping the Failure type of AsyncResult in
the previous parts, let's jump in directly.

90
Chapter 9 - Persisting New User

// src/FsTweet.Web/FsTweet.Web.fs
//...
module Persistence =
// ...
// System.Exception -> CreateUserError
let private mapException (ex : System.Exception) =
Error ex

let createUser ... = asyncTrail {


// ...
do! submitUpdates ctx
|> mapAsyncFailure mapException
return UserId newUser.Id
}

We will be handling the unique constraint violation errors later in this chapter.

Great! With this, we can wrap up the implementation of the createUser function.

Passing The Dependency


The new createUser function takes a first parameter getDataCtx of type
GetDataContext .

To make it available, first, we need to change the webPart function to receive this as
a parameter and use it for partially applying it to the createUser function

// src/FsTweet.Web/UserSignup.fs
// ...
module Suave =
// ...
open Database

let webPart getDataCtx =

91
Chapter 9 - Persisting New User

let createUser =
Persistence.createUser getDataCtx
// ...

Then in the main function call the webPart function with the getDataCtx which we
created in the beginning of this chapter.

// src/FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let app =
choose [
// ...
UserSignup.Suave.webPart getDataCtx
]

Handling Unique Constraint Violation


Errors
To handle the unique constraint violation errors gracefully, we need to understand
some internals of the database abstraction provided by the SQLProvider.

The SQLProvider internally uses the Npgsql library to interact with PostgreSQL. As a
matter of fact, through the ResolutionPath parameter, we provided a path in which
the Npgsql DLL resides.

The Npgsql library throws PostgresException when the PostgreSQL backend


reports errors (e.g., query SQL issues, constraint violations).

To infer whether the PostgresException has occurred due to the violation of the
unique constraint, we need to check the ConstraintName and the SqlState property of
this exception.

For unique constraint violation, the ConstraintName property represents the name of
the constraint that has been violated and the SqlState property, which represents
PostgreSQL error code, will have the value "23505" .

92
Chapter 9 - Persisting New User

We can find out the unique constraints name associated with the Username and the
Email by running the \d "Users" command in psql. The constraint names are
IX_Users_Username and IX_Users_Email respectively.

The SQLProvider exposes this PostgresException as an AggregateException .

Now we have enough knowledge on how to capture the unique violation exceptions
and represent it as a Domain type. So, Let's start our implementation.

The first step is adding NuGet package reference of Npgsql

> forge paket add Npgsql \


--version 3.1.10 \
-p src/FsTweet.Web/FsTweet.Web.fsproj

At the time of this writing, there is an issue with the latest version of Npgsql.
So, we are using the version 3.1.10 here.

Then we need to add a reference to System.Data , as PostgresException , inherits


DbException from this namespace.

> forge add reference -n System.Data \


-p src/FsTweet.Web/FsTweet.Web.fsproj

The next step is extending the mapException function that we defined in the previous
section to map these PostgresException s to its corresponding error types.

93
Chapter 9 - Persisting New User

// src/FsTweet.Web/UserSignup.fs
// ...
module Persistence =
// ...
open Npgsql
open System
// ...

let private mapException (ex : System.Exception) =


match ex with
| :? AggregateException as agEx ->
match agEx.Flatten().InnerException with
| :? PostgresException as pgEx ->
match pgEx.ConstraintName, pgEx.SqlState with
| "IX_Users_Email", "23505" -> EmailAlreadyExists
| "IX_Users_Username", "23505" -> UsernameAlreadyExists
| _ ->
Error pgEx
| _ -> Error agEx
| _ -> Error ex

We are doing pattern matching over the exception types here. First, we check
whether the exception is of type AggregateException . If it is, then we flatten it to get
the inner exception and check whether it is PostgresException .

In case of PostgresException , we do the equality checks on the ConstraintName and


the SqlState properties with the appropriate values and return the corresponding
error types.

For all the type mismatch on the exceptions, we return it as an Error case with the
actual exception.

Refactoring mapException Using Partial


Active Patterns
Though we achieved what we want in the mapException function, it is a bit verbose.
The crux is the equality check on the two properties, and the rest of the code just
type casting from one type to other.

Can we write it better to reflect what we intended to do over here?

Yes, We Can!

94
Chapter 9 - Persisting New User

The answer is Partial Active Patterns.

Let's add a partial active pattern, UniqueViolation , in the Database module which
does the pattern matching over the exception types and parameterizes the check on
the constraint name.

// src/FsTweet.Web/Db.fs
module Database
// ...
open Npgsql
open System

let (|UniqueViolation|_|) constraintName (ex : Exception) =


match ex with
| :? AggregateException as agEx ->
match agEx.Flatten().InnerException with
| :? PostgresException as pgEx ->
if pgEx.ConstraintName = constraintName &&
pgEx.SqlState = "23505" then
Some ()
else None
| _ -> None
| _ -> None

Then with the help of this partial active pattern, we can rewrite the mapException as

let private mapException (ex : System.Exception) =


match ex with
| UniqueViolation "IX_Users_Email" _ ->
EmailAlreadyExists
| UniqueViolation "IX_Users_Username" _ ->
UsernameAlreadyExists
| _ -> Error ex

More readable isn't it?

Summary
Excellent, We learned a lot of things in this chapter!

95
Chapter 9 - Persisting New User

We started with initializing SQLProvider, then configured it to work with a different


database in runtime, and followed it up by creating a function to return a new Data
Context for every database operation.

Finally, we transformed the return type of SQLProvider to our custom Domain type!

The source code of this chapter is available on GitHub

96
Chapter 10 - Sending Verification Email

Hi there!

In this chapter, we are going to add support for sending an email to verify the email
address of a new signup, which we faked earlier.

Setting Up Postmark
To send email, we are going to use Postmark, a transactional email service provider
for web applications.

There are three prerequisites that we need to do before we use it in our application.

1. A user account in Postmark

2. A new server, kind of namespace to manage different applications in Postmark.

3. A sender signature, to use as a FROM address in the email that we will be


sending from FsTweet.

You make use of this Getting started guide from postmark to get these three
prerequisites done.

Configuring Signup Email Template


The next step is creating an email template in Postmark for the signup email.

Here is the HTML template that we will be using

Hi {{ username }},

Welcome to FsTweet!

Confirm your email by clicking the below link

http://localhost:8080/signup/verify/{{ verification_code }}

Cheers,
www.demystifyfp.com

HTML tags are not shown for brevity.

97
Chapter 10 - Sending Verification Email

The username and the verification_code are placeholders in the template, that will
be populated with the actual value while sending the email.

Upon saving the template, you will get a unique identifier, like 3160924 . Keep a note
of it as we will be using it shortly.

With these, we completed the setup on the Postmark side.

Abstractions For Sending Emails


Postmark has a dotnet client library to make our job easier.

As a first step, we have to add its NuGet package in our web project.

> forge paket add Postmark --version 2.2.1 \


-p src/FsTweet.Web/FsTweet.Web.fsproj

Then, create a new file Email.fs in the web project and move it above UserSignup.fs

file

> forge newFs web -n src/FsTweet.Web/Email


> forge moveUp web -n src/FsTweet.Web/Email.fs
> forge moveUp web -n src/FsTweet.Web/Email.fs

Let's add some basic types that we required for sending an email

// FsTweet.Web/Email.fs
module Email

open Chessie.ErrorHandling
open System

type Email = {
To : string
TemplateId : int64
PlaceHolders : Map<string,string>
}

type SendEmail = Email -> AsyncResult<unit, Exception>

98
Chapter 10 - Sending Verification Email

The Email record represents the required details for sending an email, and the
SendEmail represents the function signature of a send email function.

The next step is adding a function which sends an email using Postmark.

// ...
open PostmarkDotNet
// ...

let sendEmailViaPostmark senderEmailAddress (client : PostmarkClient) email =


// TODO

The sendEmailViaPostmark function takes the sender email address that we created as
part of the third prerequisite while setting up Postmark, a PostmarkClient and a value
of the Email type that we just created.

Then we need to create an object of type TemplatedPostmarkMessage and call the


SendMessageAsync method on the postmark client.

let sendEmailViaPostmark senderEmailAddress (client : PostmarkClient) email =


let msg =
new TemplatedPostmarkMessage(
From = senderEmailAddress,
To = email.To,
TemplateId = email.TemplateId,
TemplateModel = email.PlaceHolders
)
client.SendMessageAsync(msg)

The return type of SendMessageAsync method is Task<PostmarkResponse> . But what we


need is AsyncResult<unit, Exception> .

I guess you should know what we need to do now? Yes, transform!

let sendEmailViaPostmark ... =


// ...
client.SendMessageAsync(msg) // Task<PostmarkResponse>
|> Async.AwaitTask // Async<PostmarkResponse>
|> Async.Catch // Choice<PostmarkResponse, Exception>
// TODO

99
Chapter 10 - Sending Verification Email

By making use of the AwaitTask and the Catch function in the Async module, we
transformed Task<PostmarkResponse> to Choice<PostmarkResponse, Exception> .

To convert this choice type to AsyncResult<unit, Exception> , we need to know little


more details.

The PostmarkClient would populate the Status property of the PostmarkResponse with
the value Success if everything went well. We need to return a unit in this case.

If the Status property doesn't have the Success value, the Message property of the
PostmarkResponse communicates what went wrong.

With these details, we can now write a function that transforms


Choice<PostmarkResponse, Exception> to Result<unit, Exception>

// FsTweet.Web/Email.fs
// ...
open System
// ...
let mapPostmarkResponse response =
match response with
| Choice1Of2 ( postmarkRes : PostmarkResponse) ->
match postmarkRes.Status with
| PostmarkStatus.Success ->
ok ()
| _ ->
let ex = new Exception(postmarkRes.Message)
fail ex
| Choice2Of2 ex -> fail ex

Now we have a function that map Choice to Result .

Going back to the sendEmailViaPostmark function, we can leverage this


mapPostmarkResponse function to accomplish our initial objective.

let sendEmailViaPostmark ... =


// ...
client.SendMessageAsync(msg) // Task<PostmarkResponse>
|> Async.AwaitTask // Async<PostmarkResponse>
|> Async.Catch // Choice<PostmarkResponse, Exception>
|> Async.map mapPostmarkResponse // Async<Result<unit, Exception>>
|> AR // AsyncResult<unit, Exception>

Awesome! We transformed Task<PostmarkResponse> to AsyncResult<unit, Exception> .

100
Chapter 10 - Sending Verification Email

Injecting The Dependencies


There are two dependencies in the sendEmailViaPostmark function, senderEmailAddress ,
and client .

Let's write a function to inject these dependencies using partial application

// FsTweet.Web/Email.fs
// ...
let initSendEmail senderEmailAddress serverToken =
let client = new PostmarkClient(serverToken)
sendEmailViaPostmark senderEmailAddress client

The serverToken parameter represents the Server API token which will be used by
the PostmarkClient while communicating with the Postmark APIs to send an email.

The initSendEmail function partially applied the first two arguments of the
sendEmailViaPostmark function and returned a function having the signature Email ->

AsyncResult<unit, Exception> .

Then during the application bootstrap, get the sender email address and the
Postmark server token from environment variables and call the initSendEmail

function to get a function to send an email.

// FsTweet.Web/FsTweet.Web.fs
// ...
open Email
// ...
let main argv =
// ...
let serverToken =
Environment.GetEnvironmentVariable "FSTWEET_POSTMARK_SERVER_TOKEN"

let senderEmailAddress =
Environment.GetEnvironmentVariable "FSTWEET_SENDER_EMAIL_ADDRESS"

let sendEmail = initSendEmail senderEmailAddress serverToken

// ...

The next step is adding the sendEmail function as a parameter in the sendSignupEmail

function

101
Chapter 10 - Sending Verification Email

// FsTweet.Web/UserSignup.fs
// ...
module Email =
// ...
open Email

let sendSignupEmail sendEmail signupEmailReq = asyncTrial {


// ...
}

and pass the actual sendEmail function to it from the main function.

// FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let app =
choose [
// ...
UserSignup.Suave.webPart getDataCtx sendEmail
]
// ...

// FsTweet.Web/UserSignup.fs
// ...
module Suave =
// ...
let webPart getDataCtx sendEmail =
// ...
let sendSignupEmail = Email.sendSignupEmail sendEmail
// ...

Sending Signup Email


Everything has been setup to send an email to verify the email account of a new
Signup.

The final task is putting the pieces together in the sendSignupEmail function.

102
Chapter 10 - Sending Verification Email

// FsTweet.Web/UserSignup.fs
// ...
module Email =
// ...
let sendSignupEmail sendEmail signupEmailReq = asyncTrial {
let verificationCode =
signupEmailReq.VerificationCode.Value
let placeHolders =
Map.empty
.Add("verification_code", verificationCode)
.Add("username", signupEmailReq.Username.Value)
let email = {
To = signupEmailReq.EmailAddress.Value
TemplateId = int64(3160924)
PlaceHolders = placeHolders
}
do! sendEmail email
|> mapAsyncFailure Domain.SendEmailError
}

The implementation of the sendSignupEmail function is striaght forward. We need to


populate the individual properties of the Email record type with the appropriate
values and then call the sendEmail email.

Note that we are using do! as sendEmail asynchronously returing unit for
success.

As usual, we are mapping the failure type of the Async result from Exception to
SendEmailError

Configuring Send Email During


Development
In a typical application development process, we won't be sending actual email in the
development environment as sending an email may cost money.

103
Chapter 10 - Sending Verification Email

One of the standard ways is faking the implementation and using the console as we
did earlier.

To enable this in our application, let's add a new function consoleSendEmail function
which prints the email record type in the console

// FsTweet.Web/Email.fs
// ...
let consoleSendEmail email = asyncTrial {
printfn "%A" email
}

Then in the main function, get the name of the environment from an environment
variable and initialize the signupEmail function accordingly.

// FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let env =
Environment.GetEnvironmentVariable "FSTWEET_ENVIRONMENT"

let sendEmail =
match env with
| "dev" -> consoleSendEmail
| _ -> initSendEmail senderEmailAddress serverToken
// ...

Summary
With the help of the abstractions and design that we created in the earlier chapters,
we added the support for sending an email with ease.

The source code of this chapter is available on GitHub

104
Chapter 11 - Verifying User Email

Hi,

In the previous chapter, we added support for sending verification email using
Postmark.

In this chapter, we are going to wrap up the user signup workflow by implementing
the backend logic of the user verifcation link that we sent in the email.

A Type For The Verify User Function.


Let's get started by defining a type for the function which verifies the user.

// src/FsTweet.Web/UserSignup.fs
// ...
module Domain =
// ...
type VerifyUser = string -> AsyncResult<Username option, System.Exception>
// ...

It takes a verification code of type string and asynchronously returns either


Username option or an exception if there are any fatal errors while verifying the user.

The Username option type will have the value if the verification code matches
otherwise it would be None .

Implementing the Verify User Function


The implementation of the VerifyUser function will take two parameters

// src/FsTweet.Web/UserSignup.fs
// ...
module Persistence =
// ...
let verifyUser
(getDataCtx : GetDataContext)
(verificationCode : string) = asyncTrial {
// TODO
}

105
Chapter 11 - Verifying User Email

The first parameter getDataCtx represents the factory function to get the
SQLProvider's datacontext that we implemented while persisting a new user. When
we partially applying this argument alone, we will get a function of type VerifyUser

We first need to query the Users table to get the user associated with the verification
code provided.

let verifyUser
(getDataCtx : GetDataContext)
(verificationCode : string) = asyncTrial {

let ctx = getDataCtx ()


let userToVerify =
query {
for u in ctx.Public.Users do
where (u.EmailVerificationCode = verificationCode)
} //
// TODO
}

SQLProvider uses the F# Query Expressions to query a data source.

The query expression that we wrote here is returning a value of type


IQueryable<DataContext.public.UsersEntity> .

To get the first item from this IQueryable asynchronously, we need to call
Seq.tryHeadAsync function (an extension function provided by the SQLProvider)

let verifyUser ... = asyncTrial {


// ...
let userToVerify =
query {
// ...
} |> Seq.tryHeadAsync
// TODO
}

Now userToVerify will be of type Async<DataContext.public.UsersEntity option> .

Like SubmitUpdatesAsync function, the tryHeadAsync throws exceptions if there is an


error during the execution of the query. So, we need to catch the exception and
return it as an AsyncResult .

106
Chapter 11 - Verifying User Email

Let's add a new function in the Database module to do this

// src/FsTweet.Web/Db.fs
// ...
let toAsyncResult queryable =
queryable // Async<'a>
|> Async.Catch // Async<Choice<'a, Exception>>
|> Async.map ofChoice // Async<Result<'a, Exception>>
|> AR // AsyncResult<'a, Exception>

This implementation to very similar to what we did in the implementation of the


submitUpdates function.

Now, with the help of this toAsyncResult function, we can now do the exception
handling in the verifyUser function.

// src/FsTweet.Web/UserSignup.fs
// ...
module Persistence =
// ...
let verifyUser ... = asyncTrial {
let! userToVerify =
query {
// ...
} |> Seq.tryHeadAsync |> toAsyncResult
// TODO
}

Note that, We have changed let to let! to retrieve the UsersEntity option from
AsyncResult<DataContext.public.UsersEntity option> .

Great!

If the userToVerify didn't exist, we just need to return None

let verifyUser ... = asyncTrial {


let! userToVerify = // ...
match userToVerify with
| None -> return None
| Some user ->
// TODO
}

107
Chapter 11 - Verifying User Email

If the user exists, then we need to set the verification code to empty (to prevent from
using it multiple times) and mark the user as verified and persist the changes.

let verifyUser ... = asyncTrial {


// ...
| Some user ->
user.EmailVerificationCode <- ""
user.IsEmailVerified <- true
do! submitUpdates ctx
// TODO
}

The last step is returning the username of the User to let the caller of the verifyUser

function know that the user has been verified and greet the user with the username.

We already have a domain type Username to represent the username. But the type of
the username that we retrieved from the database is a string .

So, We need to convert it from string to Username . To do it we defined a static


function on the Username type, TryCreate , which takes a string and returns
Result<Username, string> .

We could use this function here but before committing, let's ponder over the
scenario.

While creating the user we used the TryCreate function to validate and create the
corresponding Username type. In case of any validation errors, we populated the
Failure part of the Result type with the appropriate error message of type string .

Now, when we read the user from the database, ideally there shouldn't be any
validation errors. But we can't guarantee this behavior as the underlying the
database table can be accessed and modified without using our validation pipeline.

In case, if the validation fails, it should be treated as a fatal error!

We may not need this level of robustness, but the objective here is to
demonstrate how to build a robust system using F#.

So, the function that we need has to have the following signature

string -> Result<Username, Exception>

108
Chapter 11 - Verifying User Email

As we will be using this function in the asyncTrial computation expression, it would


be helpful if we return it as an AsyncResult instead of Result

string -> AsyncResult<Username, Exception>

If we compare this function signature with that of the TryCreate function

string -> Result<Username, string>


string -> AsyncResult<Username, Exception>

we can get a clue that we just need to map the failure type to Exception from string

and lift Result to AsyncResult .

We already have a function called mapFailure to map the failure type, but it is
defined after the definition of Username . To use it, we first move it before the
Username type definition.

// src/FsTweet.Web/UserSignup.fs
// ...
module Domain =
// ...
let mapFailure f aResult =
let mapFirstItem xs =
List.head xs |> f |> List.singleton
mapFailure mapFirstItem aResult

type Username = // ...


// ...

and then define the function TryCreateAsync using it

type Username = // ...


// ...
static member TryCreateAsync username =
Username.TryCreate username // Result<Username, string>
|> mapFailure (System.Exception) // Result<Username, Exception>
|> Async.singleton // Async<Result<Username, Exception>>
|> AR // AsyncResult<Username, Exception>

Back to the verifyUser function, we can now return the Username if user verification
succeeds

109
Chapter 11 - Verifying User Email

// src/FsTweet.Web/UserSignup.fs
// ...
module Persistence =
// ...
let verifyUser ... = asyncTrial {
// ...
| Some user ->
// ...
let! username =
Username.TryCreateAsync user.Username
return Some username
}

The next step is wiring up this persistence logic with the presentation layer.

The Presentation Side of User Verification


We are returning Username option when the user verification completed without any
errors. If it has a value, We need to show a success page otherwise we can show a
not found page.

// src/FsTweet.Web/UserSignup.fs
// ...
module Suave =
// ...

// (Username option * 'a) -> WebPart


let onVerificationSuccess (username, _ )=
match username with
| Some (username : Username) ->
page "user/verification_success.liquid" username.Value
| _ ->
page "not_found.liquid" "invalid verification code"

We are using a tuple of type (Username option * 'a) as an input parameter


here as the Success side of the Result type is a tuple of two values, success
and warning. As we are not using warning here, we can ignore. We will be
refactoring it in an another chapter.

Let's add these two liquid template files.

FsTweet.Web/views/user/verification_success.liquid

110
Chapter 11 - Verifying User Email

{% extends "master_page.liquid" %}

{% block head %}
<title> Email Verified </title>
{% endblock %}

{% block content %}

Hi {{ model }}, Your email address has been verified.


Now you can <a href="/login">login</a>!

{% endblock %}

FsTweet.Web/views/not_found.liquid

{% extends "master_page.liquid" %}

{% block head %}
<title> Not Found :( </title>
{% endblock %}

{% block content %}
{{model}}
{% endblock %}

In case of errors during user verification, we need to log the error in the console and
render a generic error page to user

module Suave =
// ...

// System.Exception list -> WebPart


let onVerificationFailure errs =
let ex : System.Exception = List.head errs
printfn "%A" ex
page "server_error.liquid" "error while verifying email"

The input parameter errs is of type System.Exception list as the failure type
of Result is a list of error type, and we are using it as a list with the single
value.

Then add the liquid template for the showing the server error

111
Chapter 11 - Verifying User Email

FsTweet.Web/views/server_error.liquid

{% extends "master_page.liquid" %}

{% block head %}
<title> Internal Error :( </title>
{% endblock %}

{% block content %}
{{model}}
{% endblock %}

Now we have functions that map success and failure parts of the Result to its
corresponding WebPart .

The next step is using these two functions to map AsyncResult<Username option,

Exception> to Async<WebPart>

module Suave =
// ...
let handleVerifyUserAsyncResult aResult =
aResult // AsyncResult<Username option, Exception>
|> Async.ofAsyncResult // Async<Result<Username option, Exception>>
|> Async.map
(either onVerificationSuccess onVerificationFailure) // Async<WebPart>

Now the presentation side is ready; the next step is wiring the persistence and the
presentation layer.

Adding Verify Signup Endpoint


As a first step, let's add a route and a webpart function for handling the signup verify
request from the user.

112
Chapter 11 - Verifying User Email

module Suave =
// ...
let webPart getDataCtx sendEmail =
// ...
let verifyUser = Persistence.verifyUser getDataCtx
choose [
// ...
pathScan "/signup/verify/%s" (handleSignupVerify verifyUser)
]

The handleSignupVerify is not defined yet, so let's add it above the webPart function

module Suave =
// ...
let handleSignupVerify
(verifyUser : VerifyUser) verificationCode ctx = async {
// TODO
}

This function first verifies the user using the verificationCode

let handleSignupVerify ... = async {


let verifyUserAsyncResult = verifyUser verificationCode
// TODO
}

Then map the verifyUserAsyncResult to the webpart using the


handleVerifyUserAsyncResult function we just defined

let handleSignupVerify ... = async {


let verifyUserAsyncResult = verifyUser verificationCode
let! webpart = handleVerifyUserAsyncResult verifyUserAsyncResult
// TODO
}

And finally call the webpart function

113
Chapter 11 - Verifying User Email

let handleSignupVerify ... = async {


let verifyUserAsyncResult = verifyUser verificationCode
let! webpart = handleVerifyUserAsyncResult verifyUserAsyncResult
return! webpart ctx
}

Summary
With this chapter, we have completed the user signup workflow.

I hope you found it useful and learned how to put the pieces together to build fully
functional feature robustly.

The source code of this part can be found on GitHub

Exercise
How about sending a welcome email to the user upon successful verification of
his/her email?

114
Chapter 12 - Reorganising Code and Refactoring

Hi there!

We have come a long way so far, and we have lot more things to do!

Before we get going, Let's spend some time to reorganize some of the code that we
wrote and refactor specific functions to help ourselves to move faster.

The UserSignup.fs file has some helper functions for working with the Chessie
library. As a first step, we will move them to a separate file.

Let's create a new file Chessie.fs in the web project.

> forge newFs web -n src/FsTweet.Web/Chessie

Then move it above Db.fs . In other words, move it up four times.

You can do it from the command line as we did before by executing the forge moveUp

command four times manually.

> forge moveUp web -n src/FsTweet.Web/Chessie.fs


> forge moveUp web -n src/FsTweet.Web/Chessie.fs
> forge moveUp web -n src/FsTweet.Web/Chessie.fs
> forge moveUp web -n src/FsTweet.Web/Chessie.fs

But there is a better way to do this!

The Repeat Command


In Non-Windows machine, using omyzsh's repeat command, we can achieve it
single line as below

> repeat 4 forge moveUp web -n src/FsTweet.Web/Chessie.fs

In Windows, as the repeat command is not available in cmder out of the box, my
first preference is manually manipulating the file order in the fsproj file directly.

However, if you would like to do it via cmder, we can achieve it by following the
below steps

115
Chapter 12 - Reorganising Code and Refactoring

These steps assume that you are using cmder in bash mode as mentioned in the
first chapter.

Navigate to cmder's config file

> cd $CMDER_ROOT/config

Create a shell file with the name user-profile.sh

> touch user-profile.sh

Then copy & paste below function in this script file

#!/bin/bash
repeat() {
number=$1
shift
for i in `seq $number`; do
$@
done
}

Finally, restart the cmder and switch to bash mode

> bash

Now in cmder, you can execute the repeat command.

If you are getting command not found error, execute the below command

source $CMDER_ROOT/config/user-profile.sh

Moving mapFailure Function


In the previous chapter, while implementing the static member function
TryCreateAsync in the Username type, we moved the mapFailure from its earlier place
to above the Username type to use it in the TryCreateAsync function.

116
Chapter 12 - Reorganising Code and Refactoring

It is a cue for us to reconsider the placement of the mapFailure function. If we want


to use it in an another function, we may need to move it to somewhere else.

So, let's move this function to the Chessie.fs file that we just created.

// FsTweet.Web/Chessie.fs
module Chessie

open Chessie.ErrorHandling

let mapFailure f result =


let mapFirstItem xs =
List.head xs |> f |> List.singleton
mapFailure mapFirstItem result

After we move this function to here, we need to refer this Chessie module in the
Domain module.

// FsTweet.Web/UserSignup.fs
namespace UserSignup

module Domain =
// ...
open Chessie

Make sure we are opening this Chessie module after opening Chessie.ErrorHandling

as we are overriding some of the functions in the new module.

Overriding The either function


We are making use of the either function from the Chessie library to map the
Result to WebPart with some compromises on the design.

To fix this, let's have a look at the signature of the either function

('b -> 'c) -> ('d -> 'c) -> (Result<'b, 'd> -> 'c)

It takes a function to map the success part ('b -> 'c) and an another function to
map the failure part ('d -> 'c) and returns a function that takes a Result<'b, 'd>

type and returns 'c .

117
Chapter 12 - Reorganising Code and Refactoring

It is the same thing that we needed, but the problem is the actual type of 'b and
'd

The success part 'b has a type ('TSuccess, 'TMessage list) to represent both the
success and the warning part. As we are not making use of warnings in FsTweet,
instead of this tuple and we just need the success part 'TSuccess alone.

To achieve it let's add a onSuccess adapter function which maps only the success
type and drops the warnings list in the tuple.

// FsTweet.Web/Chessie.fs
module Chessie
// ...

// ('a -> 'b) -> ('a * 'c) -> 'b


let onSuccess f (x, _) = f x

Then move our attention to the failure part d which has a type 'TMessage list

representing the list of errors. In FsTweet, we are short-circuiting as soon as we


found the first error and we are not capturing all the errors. So, in our case, the type
'TMessage list will always have a list with only one item 'TMessage .

Like onSuccess , we can have a function onFailure to map the first item of the list
alone.

module Chessie
// ...

// ('a -> 'b) -> ('a list) -> 'b


let onFailure f xs =
xs |> List.head |> f

The onFailure function takes the first item from the list and uses it as the argument
while calling the map function f .

Now with the help of these two functions, onSuccess and onFailure , we can override
the either function.

module Chessie
// ...

// ('b -> 'c) -> ('d -> 'c) -> (Result<'b, 'd> -> 'c)

118
Chapter 12 - Reorganising Code and Refactoring

let either onSuccessF onFailureF =


either (onSuccess onSuccessF) (onFailure onFailureF)

The overrided version either has the same signature but treats the success part
without warnings and the failure part as a single item instead of a list.

Let's use this in the Suave module in the functions that transform Result<Username

option, Exception list> to WebPart .

// FsTweet.Web/UserSignup.fs
namespace UserSignup
// ...
module Suave =
// ...
open Chessie
// ...

- let onVerificationSuccess (username, _ ) =


+ let onVerificationSuccess username =
// ...

- let onVerificationFailure errs =


- let ex : System.Exception = List.head errs
+ let onVerificationFailure (ex : System.Exception) =
// ...

// ...

Thanks to the adapter functions, onSuccess and onFailure , now the function
signatures are expressing our intent without any compromises.

Let's do the same thing for the functions that map Result<UserId, UserSignupError> to
WebPart .

module Suave =
// ...
- let handleUserSignupError viewModel errs =

119
Chapter 12 - Reorganising Code and Refactoring

- match List.head errs with


+ let onUserSignupFailure viewModel err =
+ match err with
// ...

- let handleUserSignupSuccess viewModel _ =


+ let onUserSignupSuccess viewModel _ =
// ...

let handleUserSignupResult viewModel result =

- either
- (handleUserSignupSuccess viewModel)
- (handleUserSignupError viewModel) result
+ either
+ (onUserSignupSuccess viewModel)
+ (onUserSignupFailure viewModel) result
// ...

While changing the function signature, we have also modified the prefix
handle to on to keep it consistent with the nomenclature that we are using to
the functions that are mapping the success and failure parts of a Result type.

Pattern Matching On Result type


An another similar piece of code that requires refactoring is pattern matching on the
result type.

Let's have a look at the handleUserSignup function

// FsTweet.Web/UserSignup.fs
// ...
module Suave =
// ...
let handleUserSignup ... = async {
match result with
| Ok (userSignupReq, _) ->
// ...
| Bad msgs ->
let viewModel = {vm with Error = Some (List.head msgs)}
// ...
// ...
}
// ...

120
Chapter 12 - Reorganising Code and Refactoring

Like the adapter functions, onSuccess and onFailure , we need adapters while
pattern matching on the Result type.

Let's create an Active Pattern to carry out this for us.

// FsTweet.Web/Chessie.fs
module Chessie
// ...

let (|Success|Failure|) result =


match result with
| Ok (x,_) -> Success x
| Bad errs -> Failure (List.head errs)

With the help of this active pattern, we can now rewrite the handleUserSignup function
as

// FsTweet.Web/UserSignup.fs
// ...
module Suave =
// ...
let handleUserSignup ... = async {
match result with
- | Ok (userSignupReq, _) ->
+ | Success userSignupReq ->
// ...
- | Bad msgs ->
- let viewModel = {vm with Error = Some (List.head msgs)}
+ | Failure msg ->
+ let viewModel = {vm with Error = Some msg}
// ...
// ...
}
// ...

Elegant! Let's switch our attention to the AsyncResult .

Revisiting the mapAsyncFailure function


Let's begin this change by looking at the signature of the mapAsyncFailure function

('a -> 'b) -> AsyncResult<'c, 'a> -> AsyncResult<'c, 'b>

121
Chapter 12 - Reorganising Code and Refactoring

It takes a maping function ('a -> 'b) and an AsyncResult and maps the failure side
of the AsyncResult . But the name mapAsyncFailure not clearly communicates this.

The better name would be mapAsyncResultFailure .

An another option would be having the function mapFailure in the module


AsyncResult so that the caller will use it as AsyncResult.mapFailure

We can also use an abbreviation AR to represent AsyncResult , and we can call the
function as AR.mapFailure .

Let's choose AR.mapFailure as it is shorter than the other one.

To enable this, we need to create a new module AR and decorate it with the
RequireQualifiedAccess Attribute so that functions inside this module can't be called
without the module name.

// FsTweet.Web/Chessie.fs
module Chessie
// ...

[<RequireQualifiedAccess>]
module AR =
// TODO

Then move the mapAsyncFailure function to this module and rename it to mapFailure

module AR =
let mapFailure f aResult =
aResult
|> Async.ofAsyncResult
|> Async.map (mapFailure f) |> AR

And finally, we need to use this moved and renamed function in the Persistence and
Email modules in the UserSignup.fs file.

// FsTweet.Web/UserSignup.fs
// ...
module Persistence =
// ...
open Chessie
// ...

122
Chapter 12 - Reorganising Code and Refactoring

let createUser ... =


// ...
- |> mapAsyncFailure mapException
+ |> AR.mapFailure mapException

// ...

// FsTweet.Web/UserSignup.fs
// ...
module Email =
// ...
open Chessie
// ...

let sendSignupEmail ... =


// ...
- |> mapAsyncFailure Domain.SendEmailError
+ |> AR.mapFailure Domain.SendEmailError

Defining AR.catch function


Let's switch our focus to the Database module and give some attention the following
piece of code.

// FsTweet.Web/Db.fs
// ...
let submitUpdates ... =
// ...
|> Async.Catch
|> Async.map ofChoice
|> AR

let toAsyncResult ... =


// ...
|> Async.Catch
|> Async.map ofChoice
|> AR

The repeated three lines of code take an asynchronous computation Async<'a> and
execute it with exception handling using Async.Catch function and then map the
Async<Choice<'a, Exception>> to AsyncResult<'a, Exception> .

123
Chapter 12 - Reorganising Code and Refactoring

In other words, we can extract these three lines to a separate function which has the
signature

Async<'a> -> AsyncResult<'a, Exception>

Let's create this function in the AR module

// FsTweet.Web/Chessie.fs
// ...
module AR =
// ...

let catch aComputation =


aComputation // Async<'a>
|> Async.Catch // Async<Choice<'a, Exception>>
|> Async.map ofChoice // Async<Result<'a, Exception>>
|> AR // AsyncResult<'a, Exception>

Then use it to redefine the submitUpdates function and remove the toAsyncResult

function from the Database module

// FsTweet.Web/Db.fs
// ...
open Chessie
// ...
let submitUpdates (ctx : DataContext) =
ctx.SubmitUpdatesAsync() // Async<unit>
|> AR.catch
// ...

Finally, change the verifyUser function to use this function instead of the removed
function toAsyncResult

// FsTweet.Web/UserSignup.fs
// ...
module Persistence =
// ...
open Chessie
// ...

let verifyUser ... =


// ...
- } |> Seq.tryHeadAsync |> toAsyncResult

124
Chapter 12 - Reorganising Code and Refactoring

+ } |> Seq.tryHeadAsync |> AR.catch


// ...

// ...

With these, we are done with the refactoring and reorganizing of the functions
associated with the Chessie library.

The User Module


The Domain module in the UserSignup.fs file has the following types that represent
the individual properties of a user in FsTweet

Username
UserId
EmailAddress
Password
PasswordHash

So, let's put these types in a separate namespace User and use it in the Domain

module of UserSignup .

Create a new file, User.fs, in the web project and move it above UserSignup.fs file

> forge newFs web -n src/FsTweet.Web/User


> repeat 2 forge moveUp web -n src/FsTweet.Web/User.fs

Then move the types that we just listed

// FsTweet.Web/User.fs
module User
open Chessie.ErrorHandling
open Chessie
type Username = ...
type UserId = ...
type EmailAddress = ...
type Password = ...
type PasswordHash = ...

Finally, use this module in the UserSignup.fs file

125
Chapter 12 - Reorganising Code and Refactoring

// FsTweet.Web/UserSignup.fs
namespace UserSignup

module Domain =
// ...
open User
// ...
module Persistence =
// ...
open User
// ...
// ...
module Suave =
// ...
open User
// ...

Summary
In this chapter, we learned how to create adapter functions and override a
functionality provided by a library to fit our requirements. The key to this refactoring
is the understanding of the function signatures.

The source code associated with this chapter is available on GitHub

126
Chapter 13 - Adding Login Page

In this chapter, we are going to start the implementation of a new feature, enabling
users to log in to FsTweet.

Let's get started by creating a new file Auth.fs in the web project and move it above
FsTweet.Web.fs

> forge newFs web -n src/FsTweet.Web/Auth


> forge moveUp web -n src/FsTweet.Web/Auth.fs

Serving The Login Page


The first step is rendering the login page in response to the /login HTTP GET
request. As we did for the user signup, we are going to have multiple modules in the
Auth.fs representing different layers of the application.

To start with, let's create a module Suave with a view model for the login page.

// FsTweet.Web/Auth.fs
namespace Auth
module Suave =
type LoginViewModel = {
Username : string
Password : string
Error : string option
}

As we seen in the UserSignupViewModel , we need an empty view model while


rendering the login page for the first time.

module Suave =
// ...
let emptyLoginViewModel = {
Username = ""
Password = ""
Error = None
}

Then we need to create a liquid template for the login page.

Let's create a new file user/login.liquid in the views directory

127
Chapter 13 - Adding Login Page

FsTweet.Web/views/user/login.liquid

{% extends "master_page.liquid" %}

{% block head %}
<title> Login </title>
{% endblock %}

{% block content %}
<div>
{% if model.Error %}
<p class="alert alert-danger">
{{ model.Error.Value }}
</p>
{% endif %}
<form method="POST" action="/login">
<input
type="text" id="Username" name="Username"
value="{{model.Username}}" required>
<input
type="password" id="Password" name="Password"
value="{{model.Password}}" required>
<button type="submit">Login</button>
</form>
</div>
{% endblock %}

For brevity, the styles and some HTML tags are ignored.

The next step is creating a new function to render this template with a view model.

// FsTweet.Web/Auth.fs
module Suave =
open Suave.DotLiquid
// ...
let loginTemplatePath = "user/login.liquid"

let renderLoginPage (viewModel : LoginViewModel) =


page loginTemplatePath viewModel

Then create a new function webpart to wire this function with the /login path

128
Chapter 13 - Adding Login Page

module Suave =
// ...
open Suave.Filters
open Suave.Operators
// ...

let webpart () =
path "/login"
>=> renderLoginPage emptyLoginViewModel

The last step is calling this webpart function from the main function and append this
webpart to the application's webpart list.

// FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let app =
choose [
// ...
Auth.Suave.webpart ()
]
// ...

That's it!

If we run the application now, you can see a beautiful login page

129
Chapter 13 - Adding Login Page

Handling the Login Request


To handle the HTTP POST request on /login , let's create a new function
handleUserLogin (aka WebPart ) and wire it up in the webpart function

// FsTweet.Web/Auth.fs
module Suave =
// ...
let handleUserLogin ctx = async {
// TODO
}
// ...

module Suave =
+ open Suave
// ...
let webpart () =
- path "/login"
- >=> renderLoginPage emptyLoginViewModel
+ path "/login" >=> choose [
+ GET >=> renderLoginPage emptyLoginViewModel
+ POST >=> handleUserLogin
+ ]

To handle the request for login, we first need to bind the submitted form values to a
value of LoginViewModel

// ...
open Suave.Form
// ...
let handleUserLogin ctx = async {
match bindEmptyForm ctx.request with
| Choice1Of2 (vm : LoginViewModel) ->
// TODO
| Choice2Of2 err ->
// TODO
}

If there is an error while doing model binding, we can populate the Error field of an
empty LoginViewModel and rerender the login page.

130
Chapter 13 - Adding Login Page

let handleUserLogin ctx = async {


match bindEmptyForm ctx.request with
| Choice1Of2 (vm : LoginViewModel) ->
// TODO
| Choice2Of2 err ->
+ let viewModel =
+ {emptyLoginViewModel with Error = Some err}
+ return! renderLoginPage viewModel ctx
}

If the model binding is successful, we need to validate the incoming LoginViewModel .

Validating the Login Request


The username and the password fields of the LoginViewModel are of types string . But
what we want to carry out the login operation is their corresponding domain models
Username and Password .

Let's define a new module Domain in Auth.fs above Suave and define a domain type
for the login request.

// FsTweet.Web/Auth.fs
namespace Auth

module Domain =
open User
type LoginRequest = {
Username : Username
Password : Password
}

module Suave =
// ...

Then define a static member function TryCreate which creates LoginRequest using
the trial computation expression and the TryCreate functions of Username and
Password type.

131
Chapter 13 - Adding Login Page

module Domain =
open Chessie.ErrorHandling
// ...
type LoginRequest = // ...

// (string * string) -> Result<LoginRequest,string>


with static member TryCreate (username, password) =
trial {
let! username = Username.TryCreate username
let! password = Password.TryCreate password
return {
Username = username
Password = password
}
}

Then in the handleUserLogin function, we can make use of this function to validate
the LoginViewModel .

module Suave =
open Domain
open Chessie
// ...
let handleUserLogin ctx = async {
// ...
| Choice1Of2 (vm : LoginViewModel) ->
let result =
LoginRequest.TryCreate (vm.Username, vm.Password)
match result with
| Success req ->
return! Successful.OK "TODO" ctx
| Failure err ->
let viewModel = {vm with Error = Some err}
return! renderLoginPage viewModel ctx
// ...
}

The Success and Failure active pattern that we defined in the previous chapter made
our job easier here to pattern match on the Result<LoginRequest,string> type.

If there is any error, we populate the view model with the error message and
rerender the login page.

132
Chapter 13 - Adding Login Page

For a valid login request, we need to implement the actual behavior. Let's leave this
as a TODO and revisit it in the next chapter.

Summary
In this chapter, we added implementations for rending the login page. Then we
added functions to handle and validate the login request from the user.

The source code is available in the GitHub repository

133
Chapter 14 - Handling Login Request

In the previous chapter, we have validated the login request from the user and
mapped it to a domain type LoginRequest . The next step is authenticating the user to
login to the application.

It involves following steps.

1. Finding the user with the given username


2. If the user exists, matching the provided password with the user's corresponding
password hash.
3. If the password matches, creating a user session (cookie) and redirecting the
user to the homepage.
4. Handling the errors while performing the above three steps.

We are going to implement all the above steps except creating a user session in this
chapter.

Let's get started!

Finding The User By Username


To find the user by his/her username, we first need to have domain type
representing User .

So, as a first step, let's create a record type for representing the User .

// FsTweet.Web/User.fs
// ...
type User = {
UserId : UserId
Username : Username
PasswordHash : PasswordHash
}

The EmailAddress of the user will be either verified or not verified.

// FsTweet.Web/User.fs
// ...
type UserEmailAddress =
| Verified of EmailAddress
| NotVerified of EmailAddress

134
Chapter 14 - Handling Login Request

To retrieve the string representation of the EmailAddress in both the cases, let's add a
member property Value

type UserEmailAddress =
// ...
with member this.Value =
match this with
| Verified e | NotVerified e -> e.Value

Then add EmailAddress field in the User record of this type

type User = {
// ...
EmailAddress : UserEmailAddress
}

Now we have a domain type to represent the user. The next step is defining a type
for the function which retireves User by Username

// FsTweet.Web/User.fs
// ...
type FindUser =
Username -> AsyncResult<User option, System.Exception>

As the user may not exist for a given Username , we are using User option .

Great! Let's define the persistence layer which implements this.

Create a new module Persistence in the User.fs and add a findUser function

// FsTweet.Web/User.fs
// ...
module Persistence =
open Database

let findUser (getDataCtx : GetDataContext) (username : Username) = asyncTrial {


// TODO
}

135
Chapter 14 - Handling Login Request

Finding the user by Username is very similar to what we did in the verifyUser

function. There we found the user by verification code, and here we need to find by
Username .

module Persistence =
// ...
open FSharp.Data.Sql
open Chessie

let findUser ... = asyncTrial {


let ctx = getDataCtx()
let! userToFind =
query {
for u in ctx.Public.Users do
where (u.Username = username.Value)
} |> Seq.tryHeadAsync |> AR.catch
// TODO
}

If the user didn't exist, we need to return None

let findUser ... = asyncTrial {


// ...
match userToFind with
| None -> return None
| Some user ->
// TODO
}

If the user exists, we need to transform that user that we retrieved to its
corresponding User domain model. To do it, we need a function that has the
signature

DataContext.``public.UsersEntity`` -> AsyncResult<User, System.Exception>

Let's create this function

136
Chapter 14 - Handling Login Request

// FsTweet.Web/User.fs
// ...
module Persistence =
// ...
let mapUser (user : DataContext.``public.UsersEntity``) =
// TODO
// ...

We already have TryCreate functions in Username and EmailAddress to create


themselves from the string type.

But we didn't have one for the PasswordHash . As we need it in this mapUser function,
let's define it.

// FsTweet.Web/User.fs
module User
// ...
type PasswordHash = ...
// ...

// string -> Result<PasswordHash, string>


static member TryCreate passwordHash =
try
BCrypt.InterrogateHash passwordHash |> ignore
PasswordHash passwordHash |> ok
with
| _ -> fail "Invalid Password Hash"

The InterrogateHash function from the BCrypt library takes a hash and outputs its
components if it is valid. In case of invalid hash, it throws an exception.

Now, coming back to the mapUser that we just started, let's map the username, the
password hash, and the email address of the user

137
Chapter 14 - Handling Login Request

// FsTweet.Web/User.fs
// ...
module Persistence =
let mapUser (user : DataContext.``public.UsersEntity``) =
let userResult = trial {
let! username = Username.TryCreate user.Username
let! passwordHash = PasswordHash.TryCreate user.PasswordHash
let! email = EmailAddress.TryCreate user.Email
// TODO
}
// TODO
// ...

Then we need to check whether the user email address is verified or not and create
the corresponding UserEmailAddress type.

let mapUser ... =


let userResult = trial {
// ...
let userEmail =
match user.IsEmailVerified with
| true -> Verified email
| _ -> NotVerified email
// TODO
}
// TODO

Now we have all the individual fields of the User record; we can return it from trial

computation expression

let mapUser ... =


let userResult = trial {
// ...
return {
UserId = UserId user.Id
Username = username
PasswordHash = passwordHash
EmailAddress = userEmail
}
}
// TODO

138
Chapter 14 - Handling Login Request

The userResult is of type Result<User, string> with the failure (of string type) side
representing the validation error that may occur while mapping the user
representation from the database to the domain model. It also means that data that
we retrieved is not consistent, and hence we need to treat this failure as Exception.

// DataContext.``public.UsersEntity`` -> AsyncResult<User, System.Exception>


let mapUser ... =
let userResult = trial { ... }
userResult // Result<User, string>
|> mapFailure System.Exception // Result<User, Exception>
|> Async.singleton // Async<Result<User, Exception>>
|> AR // AsyncResult<User, Exception>

We mapped the failure side of userResult to System.Exception and transformed


Result to AsyncResult .

With the help of this mapUser function, we can now return the User domain type
from the findUser function if the user exists for the given username

// FsTweet.Web/User.fs
// ...
module Persistence =
// ...
let mapUser ... = ...

let findUser ... = asyncTrial {


match userToFind with
// ...
| Some user ->
let! user = mapUser user
return Some user
}

Implementing The Login Function


The next step after finding the user is, verifying his/her password hash with the
password provided.

To do it, we need to have a function in the PasswordHash type.

139
Chapter 14 - Handling Login Request

// FsTweet.Web/User.fs
// ...
type PasswordHash = ...
// ...

// Password -> PasswordHash -> bool


static member VerifyPassword
(password : Password) (passwordHash : PasswordHash) =
BCrypt.Verify(password.Value, passwordHash.Value)

// ...

The Verify function from the BCrypt library takes care of verifying the password
with the hash and returns true if there is a match and false otherwise.

Now we have the required functions for implementing the login function.

Let's start our implementation of the login function by defining a type for it.

// FsTweet.Web/Auth.fs
module Domain =
// ...
type Login =
FindUser -> LoginRequest -> AsyncResult<User, LoginError>

The LoginError type is not defined yet. So, let's define it

module Domain =
// ...

type LoginError =
| UsernameNotFound
| EmailNotVerified
| PasswordMisMatch
| Error of System.Exception

type Login = ...

The LoginError discriminated union elegantly represents all the possible errors that
may happen while performing the login operation.

The implementation of the login function starts with finding the user and mapping
its failure to the Error union case if there is any error.

140
Chapter 14 - Handling Login Request

module Domain =
// ...
open Chessie

let login (findUser : FindUser) (req : LoginRequest) = asyncTrial {


let! userToFind =
findUser req.Username |> AR.mapFailure Error
// TODO
}

If the user to find didn't exist, we need to return the UsernameNotFound error.

let login ... = asyncTrial {


// ...
match userToFind with
| None ->
return UsernameNotFound
// TODO
}

Though it appears correct, there is an error in above implementation.

The function signature of the login function currently is

FindUser -> LoginRequest -> AsyncResult<LoginError, LoginError>

Let's focus our attention to the return type AsyncResult<LoginError, LoginError> .

The F# Compiler infers the failure part of the AsyncResult as LoginError from the
below expression

asyncTrial {
let! userToFind =
findUser req.Username // AsyncResult<User, Exception>
|> AR.mapFailure Error // AsyncResult<User, LoginError>
}

when we return the UsernameNotFound union case, F# Compiler infers it as the


success side of the AsyncResult .

141
Chapter 14 - Handling Login Request

asyncTrial {
return UsernameNotFound // LoginError
}

It is because the return keyword behind the scenes calls the Return function of the
AsyncTrialBuilder type and this Return function populates the success side of the
AsyncResult .

Here is the code snippet of the Return function copied from the Chessie
library for your reference

type AsyncTrialBuilder() =
member __.Return value : AsyncResult<'a, 'b> =
value
|> ok
|> Async.singleton
|> AR

To fix this type mismatch we need to do what the Return function does but for the
failure side.

let login ... = asyncTrial {


// ...
match userToFind with
| None ->
let! result =
UsernameNotFound // LoginError
|> fail // Result<'a, LoginError>
|> Async.singleton // Async<Result<'a, LoginError>>
|> AR // AsyncResult<'a, LoginError>
return result
// TODO
}

The let! expression followed by return can be replaced with return! which does
the both.

142
Chapter 14 - Handling Login Request

let login ... = asyncTrial {


// ...
match userToFind with
| None ->
return! UsernameNotFound
|> fail
|> Async.singleton
|> AR
// TODO
}

The next thing that we have to do in the login function, checking whether the user's
email is verified or not. If it is not verified, we return the EmailNotVerified error.

let login ... = asyncTrial {


// ...
match userToFind with
// ...
| Some user ->
match user.EmailAddress with
| NotVerified _ ->
return!
EmailNotVerified
|> fail
|> Async.singleton
|> AR
// TODO
}

If the user's email address is verified, then we need to verify his/her password and
return PasswordMisMatch error if there is a mismatch.

143
Chapter 14 - Handling Login Request

let login ... = asyncTrial {


// ...
match userToFind with
// ...
| Some user ->
match user.EmailAddress with
// ...
| Verified _ ->
let isMatchingPassword =
PasswordHash.VerifyPassword req.Password user.PasswordHash
match isMatchingPassword with
| false ->
return!
PasswordMisMatch
|> fail
|> Async.singleton
|> AR
// TODO
}

I am sure you would be thinking about refactoring the following piece of code which
is getting repeated in all the three places when we return a failure from the
asyncTrial computation expression.

|> fail
|> Async.singleton
|> AR

To refactor it, let's have a look at the signature of the fail function from the
Chessie library.

'b -> Result<'a, 'b>

The three lines of code that was getting repeated do the same transformation but on
the AsyncResult instead of Result

144
Chapter 14 - Handling Login Request

'b -> AsyncResult<'a, 'b>

So, let's create fail function in the AR module which implements this logic

// FsTweet.Web/Chessie.fs
// ...
module AR =
// ...
let fail x =
x // 'b
|> fail // Result<'a, 'b>
|> Async.singleton // Async<Result<'a, 'b>>
|> AR // AsyncResult<'a, 'b>

With the help of this new function, we can simplify the login function as below

+ open Chessie
...
- return!
- UsernameNotFound
- |> fail
- |> Async.singleton
- |> AR
+ return! AR.fail UsernameNotFound
...
- return!
- EmailNotVerified
- |> fail
- |> Async.singleton
- |> AR
+ return! AR.fail EmailNotVerified
...
- return!
- PasswordMisMatch
- |> fail
- |> Async.singleton
- |> AR
+ return! AR.fail PasswordMisMatch

Coming back to the login function, if the password does match, we just need to
return the User .

145
Chapter 14 - Handling Login Request

let login ... = asyncTrial {


// ...
match userToFind with
// ...
| Some user ->
match user.EmailAddress with
// ...
| Verified _ ->
let isMatchingPassword = ...
match isMatchingPassword with
// ...
| true -> return user
}

The presentation layer can take this value of User type and send it to the end user
either as an HTTP Cookie or a JWT.

The Presentation Layer For Transforming


Login Response
If there is any error while doing login, we need to populate the login view model with
the corresponding error message and rerender the login page.

146
Chapter 14 - Handling Login Request

// FsTweet.Web/Auth.fs
// ...
module Suave =
// ...

// LoginViewModel -> LoginError -> WebPart


let onLoginFailure viewModel loginError =
match loginError with
| PasswordMisMatch ->
let vm =
{viewModel with Error = Some "password didn't match"}
renderLoginPage vm
| EmailNotVerified ->
let vm =
{viewModel with Error = Some "email not verified"}
renderLoginPage vm
| UsernameNotFound ->
let vm =
{viewModel with Error = Some "invalid username"}
renderLoginPage vm
| Error ex ->
printfn "%A" ex
let vm =
{viewModel with Error = Some "something went wrong"}
renderLoginPage vm
// ...

In case of login success, we return the username as a response. In the next chapter,
we will be revisiting this piece of code.

// FsTweet.Web/Auth.fs
// ...
module Suave =
// ...
open User
// ...
// User -> WebPart
let onLoginSuccess (user : User) =
Successful.OK user.Username.Value
// ...

147
Chapter 14 - Handling Login Request

With the help of these two function, we can transform the Result<User,LoginError> to
WebPart using the either function

module Suave =
// ...

// LoginViewModel -> Result<User,LoginError> -> WebPart


let handleLoginResult viewModel loginResult =
either onLoginSuccess (onLoginFailure viewModel) loginResult

// ...

The next piece of work is transforming the async version of login result

module Suave =
// ...
open Chessie.ErrorHandling // Make sure this open statement is above "open Chessie"

// LoginViewModel -> AsyncResult<User,LoginError> -> Async<WebPart>


let handleLoginAsyncResult viewModel aLoginResult =
aLoginResult
|> Async.ofAsyncResult
|> Async.map (handleLoginResult viewModel)

The final step is wiring the domain, persistence and the presentation layers
associated with the login.

First, pass the getDataCtx function from the main function to the webpart function

// FsTweet.Web/FsTweet.Web.fs
- Auth.Suave.webpart ()
+ Auth.Suave.webpart getDataCtx

Then in the webpart function in the add getDataCtx as its parameter and use it to
partially apply in the findUser function

- let webpart () =
+ let webpart getDataCtx =
+ let findUser = Persistence.findUser getDataCtx

148
Chapter 14 - Handling Login Request

Followed up with passing the partially applied findUser function to the


handlerUserLogin function and remove the TODO placeholder in the handlerUserLogin

function.

- let handleUserLogin ctx = async {


+ let handleUserLogin findUser ctx = async {
...
- return! Successful.OK "TODO" ctx
...
- POST >=> handleUserLogin
+ POST >=> handleUserLogin findUser

Finally in the handleUserLogin function, if the login request is valid, call the login

function with the provided findUser function and the validated login request and
transform the result of the login function with to WebPart using the
handleLoginAsyncResult defined earlier.

let handleUserLogin findUser ctx = async {


// ...
let result = ...
match result with
| Success req ->
let aLoginResult = login findUser req
let! webpart =
handleLoginAsyncResult vm aLoginResult
return! webpart ctx
// ...
}

That's it!

Summary
We covered a lot of ground in this chapter. We started with finding the user by
username and then we moved to implement the login function. And finally, we
transformed the result of the login function to the corresponding webparts.

The source code of this chapter is available here.

149
Chapter 14 - Handling Login Request

150
Chapter 15 - Creating User Session and Authenticating User

In the last chapter, we have implemented the backend logic to verify the login
credentials of a user. Upon successful verification of the provided credentials, we
just responded with a username.

In this chapter, we are going to replace this placeholder with the actual
implementation.

Creating Session Cookie


As HTTP is a stateless protocol, we need to create a unique session id for every
successful login verification and an HTTP cookie to holds this session id.

This session cookie will be present in all the subsequent requests from the user and
we can use it to authenticate the user instead of prompting the username and the
password for each request.

To create this session id and the cookie, we are going to leverage the authenticated
function from Suave.

It takes two parameters and return a Webpart

CookieLife -> bool -> Webpart

The CookieLife defines the lifespan of a cookie in the user's browser, and the bool

parameter is to specify the presence of the cookie in HTTPS alone.

module Suave =
+ open Suave.Authentication
+ open Suave.Cookie
// ...

let onLoginSuccess (user : User) =


- Successful.OK user.Username.Value
+ authenticated CookieLife.Session false

// ...

The CookieLife.Session defines that the cookie will be present till the user he quits
the browser. There is another option MaxAge of TimeSpan to specify the lifespan of the
cookie using TimeSpan. And as we are not going to use HTTPS, we are setting the

151
Chapter 15 - Creating User Session and Authenticating User

second parameter as false .

The session id in the cookie doesn't personally identify the user. So, we need to
store the associated user information in some other place.

+------------------+--------------------+
| SessionId | User |
+---------------------------------------+
| | |
+------------------+--------------------+

There are multiple ways we can achieve it.

1. Adding a new table in the database and persist this relationship.

2. We can even use a NoSQL datastore to store this key-value data

3. We can store it an in-memory cache in the server.

4. We can make use of another HTTP cookie.

Every approach has its pros and cons, and we need to pick the opt one.

The Suave library has an abstraction to deal with this state management.

https://github.com/SuaveIO/suave/blob/master/src/Suave/State.fs

type StateStore =
/// Get an item from the state store
abstract get<'T> : string -> 'T option
/// Set an item in the state store
abstract set<'T> : string -> 'T -> WebPart

It also provides two out of the box implementations of this abstraction,


MemoryCacheStateStore , and CookieStateStore that corresponds to the third and fourth
ways defined above respectively.

In our case, We are going to use CookieStateStore .

152
Chapter 15 - Creating User Session and Authenticating User

// FsTweet.Web/Auth.fs
// ...
module Suave =
// ...
open Suave.State.CookieStateStore

// ...
// string -> 'a -> HttpContext -> WebPart
let setState key value ctx =
match HttpContext.state ctx with
| Some state ->
state.set key value
| _ -> never

let userSessionKey = "fsTweetUser"

// User -> WebPart


let createUserSession (user : User) =
statefulForSession
>=> context (setState userSessionKey user)

// ...

The statefulForSession function, a WebPart from Suave, initializes the state in the
HttpContext with CookieStateStore .

The setState function takes a key and value, along with a HttpContext . If there is a
state store present in the HttpContext , it stores the key and the value pair. In the
absence of a state store, it does nothing, and we are making the never WebPart
from Suave to denote it.

An important thing that we need to notice here is setState function doesn't know
what is the underlying StateStore that we are using.

In the createUserSession function, we are initializing the StateStore to use


CookieStateStore by calling the statefulForSession function and then calling the
setState to store the user information in the state cookie.

We are making use of the context function (aka combinator) while calling the
setState . The context function from the Suave library is having the following
signature.

(HttpContext -> WebPart) -> WebPart

153
Chapter 15 - Creating User Session and Authenticating User

The final step in calling this createUserSession function from the onLoginSuccess

function and redirects the user to his/her wall page.

let onLoginSuccess (user : User) =


authenticated CookieLife.Session false
>=> createUserSession user
>=> Redirection.FOUND "/wall"

Rending The Wall Page With A Placeholder


In the previous section, upon successful login, we are redirecting to the wall page
( /wall ) which is currently not exists. So, let's add it with a placeholder and we will
revisit in another chapter.

Let's get started by creating a new fsharp file Wall.fs and move it above
FsTweet.Web.fs

> forge newFs web -n src/FsTweet.Web/Wall


> forge moveUp web -n src/FsTweet.Web/Wall.fs

Then in the Wall.fs file add this initial implementation of User's wall.

// FsTweet.Web/Wall.fs
namespace Wall

module Suave =
open Suave
open Suave.Filters
open Suave.Operators

let renderWall ctx = async {


return! Successful.OK "TODO" ctx
}

let webpart () =
path "/wall" >=> renderWall

And finally, call this webpart function from the main function

154
Chapter 15 - Creating User Session and Authenticating User

// FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let app =
choose [
// ...
Wall.Suave.webpart ()
]
// ...

Now if we run the application and log in using a registered account, we will be
redirected to the wall page, and we can find the cookies for auth and state in the
browser.

The values of these cookies are encrypted using a randomly generated key on the
server side by Suave. We can either provide this key or let the suave to create one.

The downside of letting suave to generate the key is, it will create a new key
whenever the server restarts. And also if we run multiple instances of FsTweet.Web

behind a load balancer, each instance will have a different server key.

So, the ideal thing would be explicitly providing the server key.

As mentioned in the Server Keys section of the official documentation, To generate a


key let's create a script file script.fsx in the root directory and add the provided code
snippet to create the key.

155
Chapter 15 - Creating User Session and Authenticating User

// FsTweet/script.fsx
#r "./packages/Suave/lib/net40/Suave.dll"

open Suave.Utils
open System

Crypto.generateKey Crypto.KeyLength
|> Convert.ToBase64String
|> printfn "%s"

When we run this script, it will be print a key.

The next step is passing this a key as an environment variable to the application and
configuring the suave web server to use this key.

// FsTweet.Web/FsTweet.Web.fs

let main argv =


// ...
+ let serverKey =
+ Environment.GetEnvironmentVariable "FSTWEET_SERVER_KEY"
+ |> ServerKey.fromBase64
+ let serverConfig =
+ {defaultConfig with serverKey = serverKey}

+ startWebServer serverConfig app


- startWebServer defaultConfig app

Protecting WebParts
Currently, the Wall page can be accessed even without login as we are not
protecting it.

To protect it, we need to do the following things.

1. Validate the Auth Token present in the cookie


2. Deserialize the User type from the user state cookie.
3. Call a WebPart with the deserialized user type only if step one and two are
successful
4. Redirect user to the login page if either step one or two failed.

156
Chapter 15 - Creating User Session and Authenticating User

For validating the auth token present in the cookie, Suave.Authentication module has
a function called authenticate .

The authenticate function has five parameters.

1. relativeExpiry ( CookieLife ) - How long does the authentication cookie last?

2. secure ( bool ) - HttpsOnly?

3. missingCookie ( unit -> Choice<byte[],WebPart> ) - What to do if authentication


cookie is missing?

4. decryptionFailure ( SecretBoxDecryptionError -> Choice<byte[],WebPart> ) - What to


do if there is any error while decrypting the value present in the cookie?

5. fSuccess ( WebPart ) - What to do upon successful verification of authentication


cookie?

Let's put this authenticate function in action

// FsTweet.Web/Auth.fs
module Suave =
// ...
let redirectToLoginPage =
Redirection.FOUND "/login"

let requiresAuth fSuccess =


authenticate CookieLife.Session false
(fun _ -> Choice2Of2 redirectToLoginPage)
(fun _ -> Choice2Of2 redirectToLoginPage)
??? // TODO
// ...

For both missingCookie and decryptionFailure , we are redirecting the user to the
login page, and for a valid auth session cookie, we need to give some thoughts.

We first have to retrieve the User value from the state cookie, and then we have to
call the provided fSuccess . If there is an error while retrieving the user from the
cookie, we need to redirect to the login page.

157
Chapter 15 - Creating User Session and Authenticating User

module Suave =
// ...

// HttpContext -> User option


let retrieveUser ctx : User option =
match HttpContext.state ctx with
| Some state ->
state.get userSessionKey
| _ -> None

// WebPart -> (User -> WebPart) -> HttpContext -> WebPart


let initUserSession fFailure fSuccess ctx =
match retrieveUser ctx with
| Some user -> fSuccess user
| _ -> fFailure

// WebPart -> (User -> WebPart) -> WebPart


let userSession fFailure fSuccess =
statefulForSession
>=> context (initUserSession fFailure fSuccess)

// ...

// (User -> WebPart) -> WebPart


let requiresAuth fSuccess =
authenticate ...
...
(userSession redirectToLoginPage fSuccess)

// ...

In the userSession function, we are initializing the user state from the
CookieStateStore by calling the statefulForSession function, and then we retrieve the
logged in user from the state cookie.

With the help of the requiresAuth function, now we can define a WebPart that can be
accessed only by the authenticated user.

Going back to renderWall function in the Wall.fs, we can now make it accessible only
to the authenticated user by doing the following changes.

158
Chapter 15 - Creating User Session and Authenticating User

// FsTweet.Web/Wall.fs
module Suave =
// ...
+ open User
+ open Auth.Suave

- let renderWall ctx = async {


+ let renderWall (user : User) ctx = async {
- return! Successful.OK "TODO" ctx
+ return! Successful.OK user.Username.Value ctx
+ }

let webpart () =
- path "/wall" >=> renderWall
+ path "/wall" >=> requiresAuth renderWall

Instead of displaying a plain text, TODO , we have replaced it with the username of
the logged in user. We will be revisiting this renderWall function in the later chapters.

Handling Optional Authentication


Say if the user is already logged in and if he/she visits /login page, right now we
are rendering the login page and prompting the user to log in again.

But better user experience would be redirecting the user to the wall page.

To achieve it, let's create new function mayRequiresAuth .

// FsTweet.Web/Auth.fs

159
Chapter 15 - Creating User Session and Authenticating User

module Suave =
// ...

// (User option -> WebPart) -> WebPart


let optionalUserSession fSuccess =
statefulForSession
>=> context (fun ctx -> fSuccess (retrieveUser ctx))

// (User option -> WebPart) -> WebPart


let mayRequiresAuth fSuccess =
authenticate CookieLife.Session false
(fun _ -> Choice2Of2 (fSuccess None))
(fun _ -> Choice2Of2 (fSuccess None))
(optionalUserSession fSuccess)

// ...

The mayRequiresAuth function is similar to requiresAuth except that it calls the


fSuccess function with a User option type instead of redirecting to login page if the
user didn't log in.

The next step is changing the renderLoginPage function to accommodate this new
requirement.

// FsTweet.Web/Auth.fs

module Suave =
// ...
- let renderLoginPage (viewModel : LoginViewModel) =
- page loginTemplatePath viewModel
+ let renderLoginPage (viewModel : LoginViewModel) hasUserLoggedIn =
+ match hasUserLoggedIn with
+ | Some _ -> Redirection.FOUND "/wall"
+ | _ -> page loginTemplatePath viewModel

// ...

let webpart getDataCtx =


let findUser = Persistence.findUser getDataCtx
path "/login" >=> choose [
- GET >=> renderLoginPage emptyLoginViewModel
+ GET >=> mayRequiresAuth (renderLoginPage emptyLoginViewModel)
POST >=> handleUserLogin findUser
]

160
Chapter 15 - Creating User Session and Authenticating User

As we have changed the renderLoginPage function to take an extra parameter


hasUserLoggedIn , we need to add a None as the last argument wherever we are
calling the renderLoginPage function.

...
- renderLoginPage vm
+ renderLoginPage vm None
...

- return! renderLoginPage viewModel ctx


+ return! renderLoginPage viewModel None ctx

Summary
In this chapter, we learned how to do authentication in Suave and manage state
using cookies. The source code associated with this part is available on GitHub

Exercises
1. Instead of storing the user information in a cookie, store and retrieve it from a
new table in the PostgreSQL database. You can get the session id from the auth
cookie by using the HttpContext.sessionId function in the Suave.Authentication

module.

2. Suave supports cookies with sliding expiry. Replace the CookieLife.Session with
CookieLife.MaxAge and implement sliding expiry.

161
Chapter 16 - Posting New Tweet

Hi there!

In this chapter, we are going to implement the core feature of Twitter, posting a
tweet.

Let's dive in!

Rendering The Wall Page


In the previous chapter, we have left the user's wall page with a placeholder. So, As
a first step, let's replace this with an actual page to enable the user to post tweets.

This initial version of user's wall page will display a textarea to capture the tweet.

It will also greet the user with a message Hi {username} along with links to go his/her
profile page and log out. We will be adding implementations for profile and log out in
the later chapters.

In the Wall.fs, define a new type WallViewModel

namespace Wall

module Suave =
// ...
open Suave.DotLiquid

type WallViewModel = {
Username : string
}
// ...

and render the user/wall.liquid template with this view model

let renderWall (user : User) ctx = async {


- return! Successful.OK user.Username.Value ctx
+ let vm = {Username = user.Username.Value }
+ return! page "user/wall.liquid" vm ctx
}

Create a new dotliqud template wall.liquid in the views/user directory and update it
as below

162
Chapter 16 - Posting New Tweet

{% extends "master_page.liquid" %}

{% block head %}
<title> {{model.Username}} </title>
{% endblock %}

{% block content %}
<div>
<div>
<p class="username">Hi {{model.Username}}</p>
<a href="/{{model.Username}}">My Profile</a>
<a href="/logout">Logout</a>
</div>
<div>
<div>
<form id="tweetForm">
<textarea id="tweet"></textarea>
<button> Tweet </button>
</form>
</div>
</div>
</div>
{% endblock %}

Styles are ignored for brevity.

Now, if you run the application, you will be able to see the updated wall page after
login.

Full Page Refresh


In both signup and login pages, we are doing full page refresh when the user
submitted the form. But on the wall page, doing a complete page refresh while
posting a new tweet is not a good user experience.

The better option would be the javascript code on the wall page doing an AJAX
POST request with a JSON payload when the user clicks the Tweet button.

163
Chapter 16 - Posting New Tweet

That means we need to have a corresponding endpoint on the server responding to


this request!

Revisiting The requiresAuth function


Before creating an HTTP endpoint to handle the new tweet HTTP POST request,
let's revisit our authentication implementation to add support for JSON HTTP
endpoints.

let requiresAuth fSuccess =


authenticate CookieLife.Session false
(fun _ -> Choice2Of2 redirectToLoginPage)
(fun _ -> Choice2Of2 redirectToLoginPage)
(userSession redirectToLoginPage fSuccess)

Currently, we are redirecting the user to login page, if the user didn't have access.
But this approach will not work out for AJAX requests, as it doesn't do full page
refresh.

What we want is an HTTP response from the server with a status code 401

Unauthorized and a JSON body.

To enable this, let's refactor the requiresAuth as below

// FsTweet.Web/Auth.fs
// ...
module Suave =
// ...
// WebPart -> WebPart -> WebPart
let onAuthenticate fSuccess fFailure =
authenticate CookieLife.Session false
(fun _ -> Choice2Of2 fFailure)
(fun _ -> Choice2Of2 fFailure)
(userSession fFailure fSuccess)

let requiresAuth fSuccess =


onAuthenticate fSuccess redirectToLoginPage
// ...

164
Chapter 16 - Posting New Tweet

We have extracted the requiresAuth function into a new function onAuthenticate and
added a new parameter fFailure to parameterize what to do when authentication
fails.

Then in the requiresAuth function, we are calling the onAuthenticate function with the
redirectToLoginPage webpart for authentication failures.

Now with the help of the new function onAuthenticate , we can send an unauthorized
response in case of an authentication failure using a new function requiresAuth2

let requiresAuth2 fSuccess =


onAuthenticate fSuccess (RequestErrors.UNAUTHORIZED "???")

There are only two hard things in Computer Science: cache invalidation and
naming things. -- Phil Karlton

To be honest, I'm not in favor of naming this function as requiresAuth2 . But I


couldn't be able to come up a with a better name.

The RequestErrors.UNAUTHORIZED function from Suave, takes a string to populate the


request body and return a WebPart . To send JSON string as a response body, we
need to do few more work!

Sending JSON Response


For sending a JSON response, there is no out of the box support in Suave as the
library doesn't want to have a dependency on any other external libraries other than
FSharp.Core.

However, we can do it with ease with the fundamental HTTP abstractions provided
by Suave.

We just need to serialize the return value to the JSON string representation and
send the response with the header Content-Type populated with application/json

value.

To do the JSON serialization and deserialization (which we will be doing later in this
chapter), let's add a Chiron Nuget Package to the FsTweet.Web project.

> forge paket add Chiron --version 6.2.1 \


-p src/FsTweet.Web/FsTweet.Web.fsproj

165
Chapter 16 - Posting New Tweet

Chiron is a JSON library for F#. It can handle all of the usual things you’d want
to do with JSON, (parsing and formatting, serialization and deserialization).

Chiron works rather differently to most .NET JSON libraries with which you
might be familiar, using neither reflection nor annotation, but instead uses a
simple functional style to be very explicit about the relationship of types to
JSON. This gives a lot of power and flexibility - Chrion Documentation

Then create a new fsharp file Json.fs to put all the JSON related functionalities.

> forge newFs web -n src/FsTweet.Web/Json

And move this file above User.fs

> repeat 7 forge moveUp web -n src/FsTweet.Web/Json.fs

To send an error message to the front-end, we are going to use the following JSON
structure

{
"msg" : "..."
}

Let's add a function, unauthorized , in the Json.fs file that returns a WebPart having a
401 Unauthorized response with a JSON body.

166
Chapter 16 - Posting New Tweet

// FsTweet.Web/Json.fs

[<RequireQualifiedAccess>]
module JSON

open Suave
open Suave.Operators
open Chiron

// WebPart
let unauthorized =
["msg", String "login required"] // (string * Json) list
|> Map.ofList // Map<string,Json>
|> Object // Json
|> Json.format // string
|> RequestErrors.UNAUTHORIZED // Webpart
>=> Writers.addHeader
"Content-type" "application/json; charset=utf-8"

The String and Object are the union cases of the Json discriminated type in the
Chiron library.

The Json.format function creates the string representation of the underlying Json

type, and then we pass it to the RequestErrors.UNAUTHORIZED function to populate the


response body with this JSON formatted string and finally, we set the Content-Type

header.

Now we can rewrite the requiresAuth2 function as below

let requiresAuth2 fSuccess =


- onAuthenticate fSuccess (RequestErrors.UNAUTHORIZED "???")
+ onAuthenticate fSuccess JSON.unauthorized

With this we are done with the authentication side of HTTP endpoints serving JSON
response.

167
Chapter 16 - Posting New Tweet

Handling New Tweet POST Request


Let's add a scaffolding for handling the new Tweet HTTP POST request.

// FsTweet.Web/Wall.fs
module Suave =
// ...
let handleNewTweet (user : User) ctx = async {
// TODO
}
// ...

add then wire this up with a new HTTP endpoint.

// FsTweet.Web/Wall.fs
module Suave =
// ...
- let webpart () =
- path "/wall" >=> requiresAuth renderWall
+ let webpart () =
+ choose [
+ path "/wall" >=> requiresAuth renderWall
+ POST >=> path "/tweets"
+ >=> requiresAuth2 handleNewTweet
+ ]

The first step in handleNewTweet is parsing the incoming JSON body, and the next
step is deserializing it to a fsharp type. Chiron library has two functions
Json.tryParse and Json.tryDeserialize to do these two steps respectively.

Let's add a new function parse in Json.fs to parse the JSON request body in the
HttpRequest to Chiron's Json type.

168
Chapter 16 - Posting New Tweet

// FsTweet.Web/Json.fs
// ...
open System.Text
open Chessie.ErrorHandling

// HttpRequest -> Result<Json,string>


let parse req =
req.rawForm // byte []
|> Encoding.UTF8.GetString // string
|> Json.tryParse // Choice<Json, string>
|> ofChoice // Result<Json, string>
// ...

Then in the handleNewTweet function, we can call this function to parse the incoming
the HTTP request.

// ...
open Chessie
// ...
let handleNewTweet (user : User) ctx = async {
match JSON.parse ctx.request with
| Success json ->
// TODO
| Failure err ->
// TODO
}

If there is any parser error, we need to return bad request with a JSON body. To do
it, let's leverage the same JSON structure that we have used for sending JSON
response for unauthorized requests.

// FsTweet.Web/Json.fs
// ...

// string -> WebPart


let badRequest err =
["msg", String err ] // (string * Json) list
|> Map.ofList // Map<string,Json>
|> Object // Json
|> Json.format // string
|> RequestErrors.BAD_REQUEST // Webpart
>=> Writers.addHeader
"Content-type" "application/json; charset=utf-8"

169
Chapter 16 - Posting New Tweet

The badRequest function and the unauthorized binding both have some common
code. So, let's extract the common part out.

// FsTweet.Web/Json.fs
// ...

let contentType = "application/json; charset=utf-8"

// (string -> WebPart) -> Json -> WebPart


let json fWebpart json =
json // Json
|> Json.format // string
|> fWebpart // WebPart
>=> Writers.addHeader "Content-type" contentType // WebPart

// (string -> WebPart) -> string -> WebPart


let error fWebpart msg =
["msg", String msg] // (string * Json) list
|> Map.ofList // Map<string,Json>
|> Object // Json
|> json fWebpart // WebPart

Then change the unauthorized and badRequest functions to use this new function

let badRequest msg =


error RequestErrors.BAD_REQUEST msg
let unauthorized =
error RequestErrors.UNAUTHORIZED "login required"

Going back to the handleNewTweet function, if there is an error while parsing the
request JSON, we can return a bad request as a response.

// FsTweet.Web/Wall.fs
// ...
module Suave =
// ...
let handleNewTweet (user : User) ctx = async {
match JSON.parse ctx.request with
| Success json ->
// TODO
| Failure err ->
- // TODO
+ return! JSON.badRequest err ctx
}
// ...

170
Chapter 16 - Posting New Tweet

Let's switch our focus to handle a valid JSON request from the user.

The JSON structure of the new tweet POST request will be

{
"post" : "Hello, World!"
}

To represent this JSON on the server side (like View Model), Let's create a new type
PostRequest .

// FsTweet.Web/Wall.fs
module Suave =
// ...
type PostRequest = PostRequest of string
// ...

To deserialize the Json type (that we get after parsing) to PostRequest , Chiron
library requires PostRequest type to have a static member function FromJson with the
signature PostRequest -> Json<PostRequest>

module Suave =
// ...
open Chiron

// ...
type PostRequest = PostRequest of string with
// PostRequest -> Json<PostRequest>
static member FromJson (_ : PostRequest) = json {
let! post = Json.read "post"
return PostRequest post
}
// ...

We are making use of the json computation expression from Chrion library to
create PostRequest from Json .

Then in the handleNewTweet function, we can deserialize the Json to PostRequest

using the Json.tryDeserialize function from Chiron.

171
Chapter 16 - Posting New Tweet

let handleNewTweet (user : User) ctx = async {


match JSON.parse ctx.request with
| Success json ->
match Json.tryDeserialize json with
| Choice1Of2 (PostRequest post) ->
// TODO
| Choice2Of2 err ->
return! JSON.badRequest err ctx
// ...

The Json.tryDeserialize function takes Json as its input and return Choice<'a,

string> where the actual type of 'a is inferred from the usage of Choice and also
the actual type of 'a should have a static member function FromJson .

In case of any deserialization error, we are returning it as a bad request using the
JSON.badRequest function that we created earlier.

Now we have the server side representation of a tweet post in form of PostRequest .
The next step is validating this new tweet post.

Create a new file Tweet.fs in FsTweet.Web project and move it above


FsTweet.Web.fs

> forge newFs web -n src/FsTweet.Web/Tweet


> repeat 2 forge moveUp web -n src/FsTweet.Web/Tweet.fs

As we did for making illegal states unrepresentable in user signup, let's create a new
type Post , a domain model of the tweet post.

172
Chapter 16 - Posting New Tweet

// FsTweet.Web/Tweet.fs
namespace Tweet
open Chessie.ErrorHandling

type Post = private Post of string with


// string -> Result<Post, string>
static member TryCreate (post : string) =
match post with
| null | "" ->
fail "Tweet should not be empty"
| x when x.Length > 140 ->
fail "Tweet should not be more than 140 characters"
| x ->
Post x |> ok
member this.Value =
let (Post post) = this
post

We can now use this Post.TryCreate static member function to validate the
PostRequest in the handleNewTweet function.

// FsTweet.Web/Wall.fs
module Suave =
open Tweet
// ...
let handleNewTweet (user : User) ctx = async {
match JSON.parse ctx.request with
| Success json ->
match Json.tryDeserialize json with
| Choice1Of2 (PostRequest post) ->
- // TODO
+ match Post.TryCreate post with
+ | Success post ->
+ return! Successful.OK "TODO" ctx
+ | Failure err ->
+ return! JSON.badRequest err ctx
// ...
// ...

The next step after validation is persisting the new tweet!

173
Chapter 16 - Posting New Tweet

Persisting New Tweet


To persist a new tweet, we need a new table in our PostgreSQL database. So, let's
add this in our migration file.

// FsTweet.Db.Migrations/FsTweet.Db.Migrations.fs

// ...

[<Migration(201710071212L, "Creating Tweet Table")>]


type CreateTweetTable()=
inherit Migration()

override this.Up() =
base.Create.Table("Tweets")
.WithColumn("Id").AsGuid().PrimaryKey()
.WithColumn("Post").AsString(144).NotNullable()
.WithColumn("UserId").AsInt32().ForeignKey("Users", "Id")
.WithColumn("TweetedAt").AsDateTimeOffset().NotNullable()
|> ignore

override this.Down() =
base.Delete.Table("Tweets") |> ignore

Then run the migration using forge fake RunMigrations command to create the
Tweets table.

Upon successful execution, we will be having a Tweets table in our database.

> psql -d FsTweet

FsTweet=# \d "Tweets";;

Table "public.Tweets"
Column | Type | Modifiers
-----------+--------------------------+-----------
Id | uuid | not null
Post | character varying(144) | not null
UserId | integer | not null
TweetedAt | timestamp with time zone | not null
Indexes:
"PK_Tweets" PRIMARY KEY, btree ("Id")
Foreign-key constraints:
"FK_Tweets_UserId_Users_Id"
FOREIGN KEY ("UserId") REFERENCES "Users"("Id")

174
Chapter 16 - Posting New Tweet

Then define a type for representing the function for persisting a new tweet.

// FsTweet.Web/Tweet.fs

// ...
open User
open System
// ...
type TweetId = TweetId of Guid
type CreateTweet =
UserId -> Post -> AsyncResult<TweetId, Exception>

Then create a new module Persistence in Tweet.fs and define the createTweet

function which provides the implementation of the persisting a new tweet in


PostgreSQL using SQLProvider.

// FsTweet.Web/Tweet.fs
// ...
module Persistence =

open User
open Database
open System

let createTweet (getDataCtx : GetDataContext)


(UserId userId) (post : Post) = asyncTrial {

let ctx = getDataCtx()


let newTweet = ctx.Public.Tweets.Create()
let newTweetId = Guid.NewGuid()

newTweet.UserId <- userId


newTweet.Id <- newTweetId
newTweet.Post <- post.Value
newTweet.TweetedAt <- DateTime.UtcNow

do! submitUpdates ctx


return TweetId newTweetId
}

To use this persistence logic with the handleNewTweet function, we need to transform
the AsyncResult<TweetId, Exception> to WebPart .

175
Chapter 16 - Posting New Tweet

Before we go ahead and implement it, let's add few helper functions in Json.fs to
send Ok and InternalServerError responses with JSON body

// FsTweet.Web/Json.fs
// ...

// WebPart
let internalError =
error ServerErrors.INTERNAL_ERROR "something went wrong"

// Json -> WebPart


let ok =
json (Successful.OK)

Then define what we need to for both Success and Failure case.

// FsTweet.Web/Wall.fs
// ...
module Suave =
// ...
open Chessie.ErrorHandling
open Chessie
// ...

// TweetId -> WebPart


let onCreateTweetSuccess (TweetId id) =
["id", String (id.ToString())] // (string * Json) list
|> Map.ofList // Map<string, Json>
|> Object // Json
|> JSON.ok // WebPart

// Exception -> WebPart


let onCreateTweetFailure (ex : System.Exception) =
printfn "%A" ex
JSON.internalError

// Result<TweetId, Exception> -> WebPart


let handleCreateTweetResult result =
either onCreateTweetSuccess onCreateTweetFailure result

// AsyncResult<TweetId, Exception> -> Async<WebPart>


let handleAsyncCreateTweetResult aResult =
aResult // AsyncResult<TweetId, Exception>
|> Async.ofAsyncResult // Async<Result<TweetId, Exception>>
|> Async.map handleCreateTweetResult // Async<WebPart>
// ...

176
Chapter 16 - Posting New Tweet

The final piece is passing the dependency getDataCtx for the createTweet function
from the application's main function.

// FsTweet.Web/FsTweet.Web.fs
// ...
- Wall.Suave.webpart ()
+ Wall.Suave.webpart getDataCtx
]

// FsTweet.Web/Wall.fs
// ...
- let handleNewTweet (user : User) ctx = async {
+ let handleNewTweet createTweet (user : User) ctx = async {
// ...

- let webpart () =
+ let webpart getDataCtx =
+ let createTweet = Persistence.createTweet getDataCtx
choose [
path "/wall" >=> requiresAuth renderWall
POST >=> path "/tweets"
- >=> requiresAuth2 handleNewTweet
+ >=> requiresAuth2 (handleNewTweet createTweet)
]

And then invoke the createTweet function in the handleNewTweet function and
transform the result to WebPart using the handleAsyncCreateTweetResult function.

let handleNewTweet createTweet (user : User) ctx = async {


// ...
match Post.TryCreate post with
| Success post ->
- return! Successful.OK "TODO" ctx
+ let aCreateTweetResult =
+ createTweet user.UserId post
+ let! webpart =
+ handleAsyncCreateTweetResult aCreateTweetResult
+ return! webpart ctx
// ...
}

With this, we have successfully added support for creating a new tweet.

177
Chapter 16 - Posting New Tweet

To invoke this HTTP API from the front end, let's create a new javascript file
FsTweet.Web/assets/js/wall.js and update it as below

$(function(){
$("#tweetForm").submit(function(event){
event.preventDefault();

$.ajax({
url : "/tweets",
type: "post",
data: JSON.stringify({post : $("#tweet").val()}),
contentType: "application/json"
}).done(function(){
alert("successfully posted")
}).fail(function(jqXHR, textStatus, errorThrown) {
console.log({
jqXHR : jqXHR,
textStatus : textStatus,
errorThrown: errorThrown})
alert("something went wrong!")
});

});
});

Then in the wall.liquid template include this script file.

<!-- FsTweet.Web/views/user/wall.liquid -->


// ...
{% block scripts %}
<script src="/assets/js/wall.js"></script>
{% endblock %}

We are making use of the scripts block defined the master_page.liquid here.

<div id="scripts">
<!-- ... -->
{% block scripts %}
{% endblock %}
</div>

Let's run the application and do a test drive to verify this new feature.

178
Chapter 16 - Posting New Tweet

We can also verify it in the database

Awesome! We made it!!

Revisiting AsyncResult to WebPart


Transformation
In all the places to transform AsyncResult to WebPart , we were using the following
functions

// FsTweet.Web/Wall.fs

// Result<TweetId, Exception> -> WebPart


let handleCreateTweetResult result = ...

// AsyncResult<TweetId, Exception> -> Async<WebPart>


let handleAsyncCreateTweetResult aResult = ...

// FsTweet.Web/Auth.fs

// LoginViewModel -> Result<User,LoginError> -> WebPart


let handleLoginResult viewModel loginResult =

// LoginViewModel -> AsyncResult<User,LoginError> -> Async<WebPart>


let handleLoginAsyncResult viewModel aLoginResult =

// FsTweet.Web/UserSignup.fs
// ...

We can generalize this transformation as

179
Chapter 16 - Posting New Tweet

('a -> 'b) -> ('c -> 'b) -> AsyncResult<'a, 'c> -> Async<'b>
// onSuccess onFailure aResult aWebPart

It is similar to the signature of the either function in the Chessie library

('a -> 'b) -> ('c -> 'b) -> Result<'a, 'c> -> 'b

The only difference is, the function that we need should work with AsyncResult

instead of Result . In other words, we need the either function for AsyncResult .

Let's create this out

// FsTweet.Web/Chessie.fs
// ...

module AR =
// ...
let either onSuccess onFailure aResult =
aResult
|> Async.ofAsyncResult
|> Async.map (either onSuccess onFailure)

With this we can refactor the Wall.fs as below

180
Chapter 16 - Posting New Tweet

// FsTweet.Web/Wall.fs
// ...
- let handleCreateTweetResult result =
- either onCreateTweetSuccess onCreateTweetFailure result
-
- let handleAsyncCreateTweetResult aResult =
- aResult
- |> Async.ofAsyncResult
- |> Async.map handleCreateTweetResult

// ...
let handleNewTweet createTweet (user : User) ctx = async {
// ...
match Post.TryCreate post with
| Success post ->
- let aCreateTweetResult = createTweet user.UserId post
let! webpart =
- handleAsyncCreateTweetResult aCreateTweetResult
+ createTweet user.UserId post
+ |> AR.either onCreateTweetSuccess onCreateTweetFailure
// ...

Now it looks cleaner, Isn't it?

Make this similar refactoring in UserSignup.fs and Auth.fs as well

Unifying JSON parse and deserialize


In the handleNewTweet function, we are doing two things to get the server-side
representation of the tweet being posted, parsing and deserializing.

If there is any error while doing any of these, we are returning bad request as a
response.

let handleNewTweet ... = async {


// ...
match JSON.parse ctx.request with
| Success json ->
match Json.tryDeserialize json with
| Choice1Of2 (PostRequest post) ->
// ...
| Choice2Of2 err ->
// ...
// ...

181
Chapter 16 - Posting New Tweet

We can unify these two functions together that has the following signature

HttpRequest -> Result<^a, string>

Note: We are using ^a instead of 'a . i.e., ^a is a Statically resolved type


parameter. We need this as the Json.tryDeserialize function requires the
FromJson static member function constraint on the type ^a .

Let' name this function deserialize and add the implementation in Json.fs

// FsTweet.Web/Json.fs
// ...
// HttpRequest -> Result<^a, string>
let inline deserialize< ^a when (^a or FromJsonDefaults)
: (static member FromJson: ^a -> ^a Json)>
req : Result< ^a, string> =

parse req // Result<Json, string>


|> bind (fun json ->
json
|> Json.tryDeserialize
|> ofChoice) // Result<^a, string>
// ...

Chiron library has FromJsonDefaults type to extend the fsharp primitive types to have
the FromJson static member function.

The bind function is from Chessie library, which maps the success part of the
Result with the provided function.

With this new function, we can rewrite the handleNewTweet function as below

let handleNewTweet ctx = async {


- match JSON.parse ctx.request with
- | Success json ->
- match Json.tryDeserialize json with
- | Choice1Of2 (PostRequest post) ->
+ match JSON.deserialize ctx.request with
+ | Success (PostRequest post) ->
// ...
- | Choice2Of2 err ->
- return! JSON.badRequest err ctx
// ...

182
Chapter 16 - Posting New Tweet

Summary
In this chapter, we saw how to expose JSON HTTP endpoints in Suave and also
learned how to use the Chiron library to deal with JSON.

The source code associated with this chapter is available on GitHub

183
Chapter 17 - Adding User Feed

In the last chapter, we saw to how to persist a new tweet from the user. But after
persisting the tweet, we haven't do anything. In real twitter, we have a user feed,
which shows a timeline with tweets from him/her and from others whom he/she
follows.

In this chapter, we are going to address the first part of user's timeline, viewing
his/her tweets on the Wall page.

Publishing a New Tweet


Earlier, we just created a new tweet in the database when the user submitted a
tweet. To add support for user feeds and timeline, we need to notify an external
system after persisting the new tweet.

As we did for orchestrating the user signup, we need to define a new function which
carries out both of the mentioned operations.

Let's get started by defining a new type to represent a Tweet!

// FsTweet.Web/Tweet.fs
// ...
type Tweet = {
UserId : UserId
Username : Username
Id : TweetId
Post : Post
}

// module Persistence = ...

Create a new module Domain in Wall.fs and define a type for notifying the arrival of a
new tweet. Make sure that this new module is above the Suave module

// FsTweet.Web/Wall.fs

module Domain =
open Tweet
open System
open Chessie.ErrorHandling

type NotifyTweet = Tweet -> AsyncResult<unit, Exception>

184
Chapter 17 - Adding User Feed

The NotifyTweet typifies a notify tweet function that takes Tweet and returns either
unit or Exception asynchronously.

Then create a new type PublishTweet to represent the signature of the orchestration
function with its dependencies partially applied.

module Domain =
// ...
open User

// ...

type PublishTweet =
User -> Post -> AsyncResult<TweetId, PublishTweetError>

The SignupUser type that we defined in the orchestrating user signup chapter
has the signature that contains both the dependencies and the actual
parameters. As mentioned there, it was for illustration, and we haven't used it
anywhere.

Here, we are getting rid of the dependencies and using only the parameters
required to specify what we want. Later in the presentation layer, we'll be
using it explicitly like

let handleNewTweet (publishTweet : PublishTweet) ... = async {


// ...
let! webpart =
publishTweet user.UserId post
// ...

In the presentation layer of user signup, the similar function has defined like

let handleUserSignup signupUser ctx = async {


// ...
}

as the SignupUser type has the dependencies, we can't use it explicitly.

We have used both the approaches to demonstrate the differences.

We don't have the PublishTweetError type defined yet. So, let's add it first.

185
Chapter 17 - Adding User Feed

type PublishTweetError =
| CreateTweetError of Exception
| NotifyTweetError of (TweetId * Exception)

// type PublishTweet = ...

Finally, implement the publishTweet function

// FsTweet.Web/Wall.fs

module Domain =
// ...
open Chessie

// ...

let publishTweet createTweet notifyTweet


(user : User) post = asyncTrial {

let! tweetId =
createTweet user.UserId post
|> AR.mapFailure CreateTweetError

let tweet = {
Id = tweetId
UserId = user.UserId
Username = user.Username
Post = post
}
do! notifyTweet tweet
|> AR.mapFailure (fun ex -> NotifyTweetError(tweetId, ex))

return tweetId
}

The publishTweet function is making use of the abstractions that we built earlier and
implements the publish tweet logic.

We are mapping the possible failure of each operation to its corresponding


union case of the PublishTweetError type using the AR.mapFailure function that
we defined earlier.

There is no function implementing the NotifyTweet type yet in our application, and
our next step is adding it.

186
Chapter 17 - Adding User Feed

GetStream.IO
To implement newsfeed and timeline, we are going to use GetStream.

The Stream Framework is an open source solution, which allows you to build
scalable news feed, activity streams, and notification systems.

GetStream.io is the SASS provider of the stream framework and we are going to use
its free plan.

GetStream.io has a simple and powerful in-browser getting started documentation to


get you started right. Follow this documentation to create an app in GetStream.io
and get a basic understanding of how it works.

After completing this documentation (roughly take 5-10 minutes), if you navigate to
the dashboard, you can find the following UI component

The GetStream.io creates an application for you to enable the in-browser interactive
guide. Keep an of note the App Id, Key, and Secret. We will be using it shortly while
integrating it.

It also creates some feed groups in the created applications, and we will be using the
user and timeline feed group

187
Chapter 17 - Adding User Feed

If you are not going through the in-browser tutorial in GetStream.io, you have
to create the application and these two feed groups manually.

Configuring GetStream.io
Let's create a new file Stream.fs in the web project

> forge newFs web -n src/FsTweet.Web/Stream

and move it above Json.fs

> repeat 7 forge moveUp web -n src/FsTweet.Web/Stream.fs

Then add the stream-net NuGet package. stream-net is a .NET library for building
newsfeed and activity stream applications with Getstream.io

> forge paket add stream-net --version 1.3.2 \


-p src/FsTweet.Web/FsTweet.Web.fsproj

To model the configuration parameters that are required to talk to GetStream.io,


Let's define a record type Config .

// FsTweet.Web/Stream.fs
[<RequireQualifiedAccess>]
module GetStream

type Config = {
ApiSecret : string
ApiKey : string
AppId : string
}

188
Chapter 17 - Adding User Feed

We also need a Client record type to hold the actual GetStream.io client and this
config.

// FsTweet.Web/Stream.fs
// ...
open Stream

type Client = {
Config : Config
StreamClient : StreamClient
}

To initialize this Client type let's add a constructor function.

let newClient config = {


StreamClient =
new StreamClient(config.ApiKey, config.ApiSecret)
Config = config
}

The final step is creating a new stream client during the application bootstrap.

// FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...

+ let streamConfig : GetStream.Config = {


+ ApiKey =
+ Environment.GetEnvironmentVariable "FSTWEET_STREAM_KEY"
+ ApiSecret =
+ Environment.GetEnvironmentVariable "FSTWEET_STREAM_SECRET"
+ AppId =
+ Environment.GetEnvironmentVariable "FSTWEET_STREAM_APP_ID"
+ }

+
+ let getStreamClient = GetStream.newClient streamConfig

We are getting the required configuration parameters from the respective


environment variables populated with the corresponding values in the dashboard
that we have seen earlier.

189
Chapter 17 - Adding User Feed

Notifying New Tweet


Notifying a new tweet using GetStrem.io involves two steps.

1. Retreiving the user feed.

2. Create a new activity of type tweet and add it to the user feed.

To retrieve the user feed of the user, let's add a function userFeed in the Stream.fs.
The hardcoded string "user" is the name of the feed group that we saw earlier

// FsTweet.Web/Stream.fs
// ...

// Client -> 'a -> StreamFeed


let userFeed getStreamClient userId =
getStreamClient.StreamClient.Feed("user", userId.ToString())

Then in the Wall.fs, create a new module GetStream above Suave module and add a
new function notifyTweet to add a new activity to the user feed.

190
Chapter 17 - Adding User Feed

// FsTweet.Web/Wall.fs
// ...

module GetStream =
open Tweet
open User
open Stream
open Chessie.ErrorHandling

// GetStream.Client -> Tweet -> AsyncResult<Activity, Exception>


let notifyTweet (getStreamClient: GetStream.Client) (tweet : Tweet) =

let (UserId userId) = tweet.UserId


let (TweetId tweetId) = tweet.Id
let userFeed =
GetStream.userFeed getStreamClient userId

let activity =
new Activity(userId.ToString(), "tweet", tweetId.ToString())

// Adding custom data to the activity


activity.SetData("tweet", tweet.Post.Value)
activity.SetData("username", tweet.Username.Value)

userFeed.AddActivity(activity) // Task<Activity>
|> Async.AwaitTask // Async<Activity>
|> Async.Catch // Async<Choice<Activity,Exception>>
|> Async.map ofChoice // Async<Result<Activity,Exception>>
|> AR // AsyncResult<Activity,Exception>

// ...

The AddActivity function adds an Activity to the user feed and returns
Task<Activity> , and we are transforming it to AsyncResult<Activity,Exception> .

The NotifyTweet type that we defined earlier has the function signature returning
AsyncResult<unit, Exception> but the implemenation function notifyTweet returns
AsyncResult<Activity, Exception> .

So, while transforming, we need to ignore the Activity and map it to unit instead.
To do it add a new function mapStreamResponse

191
Chapter 17 - Adding User Feed

// FsTweet.Web/Wall.fs
// ...
module GetStream =
// ...
open Chessie.ErrorHandling
// ...

let mapStreamResponse response =


match response with
| Choice1Of2 _ -> ok ()
| Choice2Of2 ex -> fail ex

let notifyTweet ... = ...

and use this function instead of ofChoice in the notifyTweet function.

let notifyTweet (getStreamClient: GetStream.Client) (tweet : Tweet) =

...

userFeed.AddActivity(activity) // Task<Activity>
|> Async.AwaitTask // Async<Activity>
|> Async.Catch // Async<Choice<Activity,Exception>>
- |> Async.map ofChoice // Async<Result<Activity,Exception>>
+ |> Async.map GetStream.mapStreamResponse // Async<Result<unit,Exception>>
|> AR // AsyncResult<unit,Exception>

Now we have an implementation for notifying when a user tweets.

Wiring Up The Presentation Layer


Currently, in the handleNewTweet function, we are justing creating a tweet using the
createTweet function. To publish the new tweet (which does both creating and
notifying), we need to change it to publishTweet and then transform its success and
failure return values to Webpart .

192
Chapter 17 - Adding User Feed

// FsTweet.Web/Wall.fs
module Suave =
+ open Domain
...

- let onCreateTweetSuccess (PostId id) =


+ let onPublishTweetSuccess (PostId id) =
...

- let onCreateTweetFailure (ex : System.Exception) =


- printfn "%A" ex
- JSON.internalError
+ let onPublishTweetFailure (err : PublishTweetError) =
+ match err with
+ | NotifyTweetError (postId, ex) ->
+ printfn "%A" ex
+ onPublishTweetSuccess postId
+ | CreateTweetError ex ->
+ printfn "%A" ex
+ JSON.internalError

- let handleNewTweet createTweet (user : User) ctx = async {


+ let handleNewTweet (publishTweet : PublishTweet) (user : User) ctx = async {
...
let! webpart =
- createTweet user.UserId post
+ publishTweet user post
- |> AR.either onCreateTweetSuccess onCreateTweetFailure
+ |> AR.either onPublishTweetSuccess onPublishTweetFailure

For NotifyTweetError , we are just printing the error for simplicity, and assumes
it as fire and forget.

The final piece is passing the publishTweet dependency to the handleNewTweet

193
Chapter 17 - Adding User Feed

// FsTweet.Web/Wall.fs
module Suave =
...
- let webpart getDataCtx =
+ let webpart getDataCtx getStreamClient =
// ...

+ let notifyTweet = GetStream.notifyTweet getStreamClient


+ let publishTweet = publishTweet createTweet notifyTweet

choose [
path "/wall" >=> requiresAuth renderWall
POST >=> path "/tweets"
- >=> requiresAuth2 (handleNewTweet createTweet)
+ >=> requiresAuth2 (handleNewTweet publishTweet)

and then pass the getStreamClient from the main function.

// FsTweet.Web/FsTweet.Web.fs
// ...
- Wall.Suave.webpart getDataCtx
+ Wall.Suave.webpart getDataCtx getStreamClient
]

Now if you run the app with the GetStream environment variables populated with
their correpsonding values and post a tweet, it will be added to the user feed.

Subscribing to the User Feed


In the previous section, we have added the server side implementation for adding a
tweet activity to the user feed and it's time to add it in the client-side.

Adding GetStream.io JS Library


GetStream.io provides a javascript client library to enable client-side integration in
the browser.

Download the minified javascript file and move it to the


src/FsTweet.Web/assets/js/lib directory.

194
Chapter 17 - Adding User Feed

Then in the wall.liquid template, add a reference to this getstream.fs file in the
scripts block.

FsTweet.Web/views/user/wall.liquid

{% block scripts %}
+ <script src="/assets/js/lib/getstream.js"> </script>
<script src="/assets/js/wall.js"></script>
{% endblock %}

Initializing GetStream.io JS Library


To initialize the GetStream.io javascript client, we need GetStream.io's API key and
App ID. We already have it on the server side, So, we just need to pass it.

There are two ways we can do it,

1. Exposing an API to retrieve this details.


2. Populate the values in a javascript object while rending the wall page using
Dotliquid.

We are going to use the second option as it is simpler. To enable it we first need to
pass the getStreamClient from the webpart function to the renderWall function.

// FsTweet.Web/Wall.fs
module Suave =

- let renderWall (user : User) ctx = async {


+ let renderWall
+ (getStreamClient : GetStream.Client)
+ (user : User) ctx = async {
...

let webpart getDataCtx getStreamClient =


...
- path "/wall" >=> requiresAuth renderWall
+ path "/wall" >=> requiresAuth (renderWall getStreamClient)

Then we need to extend the WallViewModel to have two more properties and populate
it with the getStreamClient 's config values.

195
Chapter 17 - Adding User Feed

type WallViewModel = {
// ...
ApiKey : string
AppId : string
}

// ...
let renderWall ... =
// ...
let vm = {
// ...
ApiKey = getStreamClient.Config.ApiKey
AppId = getStreamClient.Config.AppId}
// ...

The next step is a populating a javascript object with these values in the wall.liquid
template.

{% block scripts %}
<!-- ... -->
<script type="text/javascript">
window.fsTweet = {
stream : {
appId : "{{model.AppId}}",
apiKey : "{{model.ApiKey}}"
}
}
</script>
<!-- before <script src="/assets/js/wall.js"></script> -->
{% endblock %}

Finally, in the wall.js file, initialize the getstream client with these values.

// src/FsTweet.Web/assets/js/wall.js
$(function(){
// ...
let client =
stream.connect(fsTweet.stream.apiKey, null, fsTweet.stream.appId);
});

Adding User Feed Subscription


To initialize a user feed on the client side, GetStream.io requires the user id and the
user feed token. So, we first need to pass it from the server side.

196
Chapter 17 - Adding User Feed

As we did for the passing API key and App Id, we first need to extend the view model
with the required properties

// src/FsTweet.Web/Wall.fs

module Suave =
// ...
type WallViewModel = {
// ...
UserId : int
UserFeedToken : string
}
// ...

Then populate the view model with the corresponding values

let renderWall ... =


let (UserId userId) = user.UserId

let userFeed =
GetStream.userFeed getStreamClient userId

let vm = {
// ...
UserId = userId
UserFeedToken = userFeed.ReadOnlyToken
}
// ...

Note: We are passing the ReadOnlyToken as the client side just going to listen
to the new tweet.

Finally, pass the values via wall.liquid template.

197
Chapter 17 - Adding User Feed

{% block scripts %}
<!-- ... -->
<script type="text/javascript">
window.fsTweet = {
user : {
id : "{{model.UserId}}",
name : "{{model.Username}}",
feedToken : "{{model.UserFeedToken}}"
},
// before stream : {
}
</script>
<!-- ... -->
{% endblock %}

On the client side, use these values to initialize the user feed and subscribe to the
new tweet and print to the console.

// src/FsTweet.Web/assets/js/wall.js
$(function(){
// ...
let userFeed =
client.feed("user", fsTweet.user.id, fsTweet.user.feedToken);

userFeed.subscribe(function(data){
console.log(data.new[0])
});
});

Now if you post a tweet, you will get a console log of the new tweet.

Adding User Wall

198
Chapter 17 - Adding User Feed

The last thing that we need to add is rendering the user wall and put the tweets there
instead of the console log. To do it, first, we need to have a placeholder on the
wall.liquid page.

<!-- FsTweet.Web/views/user/wall.liquid -->


<!-- ... -->
<div id="wall" />
<!-- ... -->

Then add a new file tweet.js to render the new tweet in the wall.

// src/FsTweet.Web/assets/js/tweet.js
$(function(){

var timeAgo = function () {


return function(val, render) {
return moment(render(val) + "Z").fromNow()
};
}

var template = `
<div class="tweet_read_view bg-info">
<span class="text-muted">
@{{tweet.username}} - {{#timeAgo}}{{tweet.time}}{{/timeAgo}}
</span>
<p>{{tweet.tweet}}</p>
</div>
`

window.renderTweet = function($parent, tweet) {


var htmlOutput = Mustache.render(template, {
"tweet" : tweet,
"timeAgo" : timeAgo
});
$parent.prepend(htmlOutput);
};

});

The renderTweet function takes the parent DOM element and the tweet object as its
inputs.

199
Chapter 17 - Adding User Feed

It generates the HTML elements of the tweet view using Mustache and Moment.js
(for displaying the time). And then it prepends the created HTML elements to the
parents DOM using the jQuery's prepend method.

In the wall.liquid file refer this tweet.js file

<!-- FsTweet.Web/views/user/wall.liquid -->


<!-- ... -->
{% block scripts %}
<!-- ... -->
<script src="/assets/js/tweet.js"> </script>
<!-- before referring wall.js -->
{% endblock %}

And then refer the Mustache and Moment.js libraries in the master_page.liquid.

<!-- src/FsTweet.Web/views/master_page.liquid -->


<div id="scripts">
<!-- ... -->
<script src="{replace_this_moment_js_CDN_URL}"></script>
<script src="{replace_this_mustache_js_CDN_URL}"></script>
<!-- ... -->
</div>

Finally, replace the console log with the call to the renderTweet function.

// src/FsTweet.Web/assets/js/wall.js
...

userFeed.subscribe(function(data){
- console.log(data.new[0]);
+ renderTweet($("#wall"),data.new[0]);
});

})

Now if we tweet, we can see the wall is being populated with the new tweet.

200
Chapter 17 - Adding User Feed

We made it!!

Summary
In this chapter, we learned how to integrate GetStream.io in FsTweet to notify the
new tweets and also added the initial version of user wall.

The source code of this chapter is available on GitHub

201
Chapter 18 - Adding User Profile Page

We are on the verge of completing the initial version of FsTweet. To say FsTweet as
a Twitter clone, we should be able to follow other users and view their tweets in our
wall page. To do it, we first need to have a user profile page where we can go and
follow the user.

In this chapter, we are going to create the user profile page.

The User Profile Page


We are going to consider the username of the user as the twitter handle and the
handler for the URL /{username} renders the user's profile page.

The user profile page will have the following UI Components.

1. A Gravatar image of the user along with the username.

2. List of tweets tweeted by the given user.

3. List of users that he/she is following.

4. List of his/her followers.

The components three and four will be addressed in the later chapters.

In addition to it, we also have to address the following three scenarios on the profile
page.

1. Anyone should be able to view a profile of anybody else without logging in to the
application. The anonymous user can only view the page.

202
Chapter 18 - Adding User Profile Page

2. If a logged in user visits another user profile page, he/she should be able to
follow him/her

3. If a logged in user visits his/her profile page, there should not be any provision
to follow himself/herself.

203
Chapter 18 - Adding User Profile Page

Let's dive in and implement the user profile page.

To start with we are going to implement the first UI Component, the gravatar image
along with the username and we will also be addressing the above three scenarios.

User Profile Liquid Template


Let's get started by creating a new liquid template profile.liqud for the user profile
page.

> touch src/FsTweet.Web/views/user/profile.liquid

Then update it as below

204
Chapter 18 - Adding User Profile Page

{% extends "master_page.liquid" %}
{% block head %}
<title> {{model.Username}} - FsTweet </title>
{% endblock %}
{% block content %}
<div>
<img src="{{model.GravatarUrl}}" alt="" class="gravatar" />
<p class="gravatar_name">@{{model.Username}}</p>
{% if model.IsLoggedIn %}
{% unless model.IsSelf %}
<a href="#" id="follow">Follow</a>
{% endunless %}
<a href="/logout">Logout</a>
{% endif %}
</div>
<p id="followingCount"/><div id="following"/>
<p id="followersCount"/><div id="followers"/>
{% endblock %}

Styles are ignored for brevity.

We are using two boolean properties IsLoggedIn and IsSelf to show/hide the UI
elements that we saw above.

The next step is adding the server side logic to render this template.

Rendering User Profile Template


Create a new fsharp file UserProfile.fs and move it above FsTweet.Web.fs

> forge newFs web -n src/FsTweet.Web/UserProfile

> forge moveUp web -n src/FsTweet.Web/UserProfile.fs

As a first step, let's define a domain model for user profile

205
Chapter 18 - Adding User Profile Page

// src/FsTweet.Web/UserProfile.fs
namespace UserProfile

module Domain =
open User

type UserProfile = {
User : User
GravatarUrl : string
IsSelf : bool
}

Then add the gravatarUrl function that creates the gravatar URL from the user's
email address.

module Domain =
// ...
open System.Security.Cryptography

// ...

let gravatarUrl (emailAddress : UserEmailAddress) =


use md5 = MD5.Create()
emailAddress.Value
|> System.Text.Encoding.Default.GetBytes
|> md5.ComputeHash
|> Array.map (fun b -> b.ToString("x2"))
|> String.concat ""
|> sprintf "http://www.gravatar.com/avatar/%s?s=200"

The gravatarUrl function uses this logic to generate the URL.

To simplify the creating a value of UserProfile , let's add a function newUserProfile to


create UserProfile from User .

// User -> UserProfile


let newProfile user = {
User = user
GravatarUrl = gravatarUrl user.EmailAddress
IsSelf = false
}

The next step is adding the findUserProfile function, which finds the user profile by
username.

206
Chapter 18 - Adding User Profile Page

If the Username of the logged in user matches with the Username that we are looking
to find, we don't need to call the findUserProfile . Instead, we can use the User

value that we get from the session cookie and then call newProfile function with the
logged in user to get the profile and modify its IsSelf property to true .

open Chessie.ErrorHandling
// ...
type FindUserProfile =
Username -> User option
-> AsyncResult<UserProfile option, System.Exception>

// FindUser -> Username -> User option


// -> AsyncResult<UserProfile option, Exception>
let findUserProfile
(findUser : FindUser) (username : Username) loggedInUser = asyncTrial {

match loggedInUser with


| None ->
let! userMayBe = findUser username
return Option.map newProfile userMayBe
| Some (user : User) ->
if user.Username = username then
let userProfile =
{newProfile user with IsSelf = true}
return Some userProfile
else
let! userMayBe = findUser username
return Option.map newProfile userMayBe
}

We are making use of the findUser function that we created while handling user
login request.

The FindUserProfile type represents the function signature of the findUserProfile

function with its dependencies partially applied.

Now we have the domain logic for finding user profile in place and let's turn our
attention to the presentation logic!

As we did for other pages, create a new module Suave and define a view model for
the profile page.

207
Chapter 18 - Adding User Profile Page

// src/FsTweet.Web/UserProfile.fs
namespace UserProfile
//...

module Suave =
type UserProfileViewModel = {
Username : string
GravatarUrl : string
IsLoggedIn : bool
IsSelf : bool
}

Then add a function newUserProfileViewModel which creates UserProfileViewModel from


UserProfile .

module Suave =
open Domain
// ...

// UserProfile -> UserProfileViewModel


let newUserProfileViewModel (userProfile : UserProfile) = {
Username = userProfile.User.Username.Value
GravatarUrl = userProfile.GravatarUrl
IsLoggedIn = false
IsSelf = userProfile.IsSelf
}

The next step is transforming the return type ( AsyncResult<UserProfile option,

Exception> ) of the findUserProfile function to Async<WebPart> . To do it we first need


to define what we will be doing on success and on failure.

// src/FsTweet.Web/UserProfile.fs
// ...
module Suave =
// ...
open Suave.DotLiquid
open Chessie
open System
open User
// ...

let renderUserProfilePage (vm : UserProfileViewModel) =


page "user/profile.liquid" vm
let renderProfileNotFound =
page "not_found.liquid" "user not found"

208
Chapter 18 - Adding User Profile Page

// bool -> UserProfile option -> WebPart


let onFindUserProfileSuccess isLoggedIn userProfileMayBe =
match userProfileMayBe with
| Some (userProfile : UserProfile) ->
let vm = { newUserProfileViewModel userProfile with
IsLoggedIn = isLoggedIn }
renderUserProfilePage vm
| None -> renderProfileNotFound

// System.Exception -> WebPart


let onFindUserProfileFailure (ex : Exception) =
printfn "%A" ex
page "server_error.liquid" "something went wrong"

Then wire these functions up with the actual request handler.

// FindUserProfile -> string -> User option -> WebPart


let renderUserProfile (findUserProfile : FindUserProfile)
username loggedInUser ctx = async {

match Username.TryCreate username with


| Success validatedUsername ->
let isLoggedIn =
Option.isSome loggedInUser
let onSuccess =
onFindUserProfileSuccess isLoggedIn
let! webpart =
findUserProfile validatedUsername loggedInUser
|> AR.either onSuccess onFindUserProfileFailure
return! webpart ctx
| Failure _ ->
return! renderProfileNotFound ctx

The final step is exposing this function and adding an HTTP route.

209
Chapter 18 - Adding User Profile Page

// src/FsTweet.Web/UserProfile.fs
// ...
module Suave =
// ...
open Database
open Suave.Filters
open Auth.Suave
// ...

let webpart (getDataCtx : GetDataContext) =


let findUser = Persistence.findUser getDataCtx
let findUserProfile = findUserProfile findUser
let renderUserProfile = renderUserProfile findUserProfile
pathScan "/%s" (fun username -> mayRequiresAuth (renderUserProfile username))

// FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let app =
choose [
// ...
+ UserProfile.Suave.webpart getDataCtx
]
// ...

We need to make sure that this webpart should be the last item in the choose

list as the path /%s matches every path that has this pattern.

To test drive this new feature, run the application and view the user profile as an
anonymous user. Then signup some new users (make sure you verify their email id)
and then log in and see other users profile.

We haven't added the log out yet. So, to log in as a new user either clear the
cookies in the browser or restart your browser.

Adding User Feed


The next UI Component that we need to implement is the tweet feed of the user.
Unlike the user feed that we added in the previous chapter, here we are just going to
fetch his/her tweets and going to show as a history.

210
Chapter 18 - Adding User Profile Page

To enable it we have to pass the GetStream.io's configuration and user details to the
client side. Let's add them as properties in the UserProfileViewModel .

// src/FsTweet.Web/UserProfile.fs
// ...
module Suave =
// ...
type UserProfileViewModel = {
// ...
UserId : int
UserFeedToken : string
ApiKey : string
AppId : string
}
// ...

Then add the getStreamClient parameter to the newUserProfileViewModel function and


populate the newly added properties.

- let newUserProfileViewModel (userProfile : UserProfile) = {


- Username = userProfile.User.Username.Value
- GravatarUrl = userProfile.GravatarUrl
- IsLoggedIn = false
- IsSelf = userProfile.IsSelf
- }

+ let newUserProfileViewModel
+ (getStreamClient : GetStream.Client) (userProfile : UserProfile) =
+
+ let (UserId userId) = userProfile.User.UserId
+ let userFeed = GetStream.userFeed getStreamClient userId
+ {
+ Username = userProfile.User.Username.Value
+ GravatarUrl = userProfile.GravatarUrl
+ IsLoggedIn = false
+ IsSelf = userProfile.IsSelf
+ UserId = userId
+ UserFeedToken = userFeed.ReadOnlyToken
+ ApiKey = getStreamClient.Config.ApiKey
+ AppId = getStreamClient.Config.AppId
+ }

Now you will be getting compiler errors as the onHandleUserProfileSuccess function


was directly calling the newUserProfileViewModel function and it doesn't have
getStreamClient to pass the argument.

211
Chapter 18 - Adding User Profile Page

Instead of passing the value of GetStream.Client around, we can partially apply it in


the onHandleUserProfileSuccess function and pass as an argument to the
renderUserProfile function and eventually to the onHandleUserProfileSuccess function.

- let webpart (getDataCtx : GetDataContext) =


+ let webpart (getDataCtx : GetDataContext) getStreamClient =
...
- let renderUserProfile = renderUserProfile findUserProfile
+ let newUserProfileViewModel = newUserProfileViewModel getStreamClient
+ let renderUserProfile = renderUserProfile newUserProfileViewModel findUserProfile
...

- let renderUserProfile findUserProfile username loggedInUser ctx = async {


+ let renderUserProfile
+ newUserProfileViewModel findUserProfile username loggedInUser ctx = async {

match Username.TryCreate username with


| Success validatedUsername ->
let isLoggedIn = Option.isSome loggedInUser
let onSuccess =
- onFindUserProfileSuccess isLoggedIn
+ onFindUserProfileSuccess newUserProfileViewModel isLoggedIn

- let onFindUserProfileSuccess isLoggedIn userProfileMayBe =


+ let onFindUserProfileSuccess newUserProfileViewModel isLoggedIn userProfileMayBe =

The final step is passing the getStreamClient from the application's main function.

// FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let app =
choose [
// ...
- UserProfile.Suave.webPart getDataCtx
+ UserProfile.Suave.webPart getDataCtx getStreamClient
]
// ...

With this, we are done with the server side changes for showing a user feed in the
user profile page.

212
Chapter 18 - Adding User Profile Page

The next change that we need to do is on the liquid template profile.liquid

First, add a placeholder for showing the user feed

<!-- user/profile.liquid -->


<!-- ... -->
{% block content %}
<div>
<!-- ... -->
<div id="tweets" />
</div>
{% endblock %}

Then as we did in the last chapter, define a scripts block and pass the
GetStream.io's initialization values to the client side.

<!-- user/profile.liquid -->


<!-- ... -->

{% block scripts %}
<script src="/assets/js/lib/getstream.js"> </script>

<script type="text/javascript">
window.fsTweet = {
user : {
id : "{{model.UserId}}",
name : "{{model.Username}}",
feedToken : "{{model.UserFeedToken}}"
},
stream : {
appId : "{{model.AppId}}",
apiKey : "{{model.ApiKey}}"
}
}
</script>

<script src="/assets/js/tweet.js"></script>
<script src="/assets/js/profile.js"></script>
{% endblock %}

The profile.js that we are referring here is not added yet. So, let's add it

213
Chapter 18 - Adding User Profile Page

// assets/js/profile.js
$(function(){
let client =
stream.connect(fsTweet.stream.apiKey, null, fsTweet.stream.appId);
let userFeed =
client.feed("user", fsTweet.user.id, fsTweet.user.feedToken);

userFeed.get({
limit: 25
}).then(function(body) {
$(body.results.reverse()).each(function(index, tweet){
renderTweet($("#tweets"), tweet);
});
})
});

The code is straight-forward, we are initializing the GetStream.io's client and the user
feed. And then we are retrieving the last 25 tweets of the user.

Awesome!.

Now if we run the app and visits a user profile, we can see his/her tweets!

Summary
In this chapter, we implemented the user profile page with the help of the
abstractions that we built earlier. Then we added the logout functionality.

The source code associated with this chapter is available on GitHub

214
Chapter 19 - Following a User

In this nineteenth chapter, we are going to implement an another core feature of


Twitter, Following other users and viewing their tweets on our wall page.

Adding Log out


To test drive the implementation of following a user in FsTweet, we may need to log
out and log in as a different user. But we haven't added the logout functionality yet.

So, as part of this feature implementation, let's get started with implementing log out.

The log out functionality is more straightforward to implement. Thanks to the


deauthenticate WebPart from the Suave.Authentication module which clears both the
authentication and the state cookie. After removing the cookies, we just need to
redirect the user to the login page.

Let's add a new path /logout in Auth.fs and handle the logout request as
mentioned.

// src/FsTweet.Web/Auth.fs
...
module Suave =
...

let webpart getDataCtx =


let findUser = Persistence.findUser getDataCtx
- path "/login" >=> choose [
- GET >=> mayRequiresAuth (renderLoginPage emptyLoginViewModel)
- POST >=> handleUserLogin findUser
+ choose [
+ path "/login" >=> choose [
+ GET >=> mayRequiresAuth (renderLoginPage emptyLoginViewModel)
+ POST >=> handleUserLogin findUser
+ ]
+ path "/logout" >=> deauthenticate >=> redirectToLoginPage
]

Following A User
Let's get started by creating a new file Social.fs in the FsTweet.Web project and
move it above UserProfile.fs

215
Chapter 19 - Following a User

> forge newFs web -n src/FsTweet.Web/Social

> repeat 2 forge moveUp web -n src/FsTweet.Web/Social.fs

The backend implementation of following a user involves two things.

1. Persisting the social connection (following & follower) in the database.

2. Subscribing to the other user's twitter feed.

As we did for the other features, let's add a Domain module and orchestrate this
functionality.

// FsTweet.Web/Social.fs
namespace Social

module Domain =
open System
open Chessie.ErrorHandling
open User

type CreateFollowing = User -> UserId -> AsyncResult<unit, Exception>


type Subscribe = User -> UserId -> AsyncResult<unit, Exception>
type FollowUser = User -> UserId -> AsyncResult<unit, Exception>

// Subscribe -> CreateFollowing ->


// User -> UserId -> AsyncResult<unit, Exception>
let followUser
(subscribe : Subscribe) (createFollowing : CreateFollowing)
user userId = asyncTrial {

do! subscribe user userId


do! createFollowing user userId
}

The CreateFollowing and the Subscribe types represent the function signatures of
the two tasks that we need to do while following a user.

The next step is defining functions which implement these two functionalities.

Persisting the social connection


To persist the social connection, we need to have a new table. So, As a first step,
let's add a migration (script) to create this new table.

216
Chapter 19 - Following a User

// src/FsTweet.Db.Migrations/FsTweet.Db.Migrations.fs
// ...

[<Migration(201710280554L, "Creating Social Table")>]


type CreateSocialTable()=
inherit Migration()

override this.Up() =
base.Create.Table("Social")
.WithColumn("Id").AsGuid().PrimaryKey().Identity()
.WithColumn("FollowerUserId").AsInt32().ForeignKey("Users", "Id").NotNullable()
.WithColumn("FollowingUserId").AsInt32().ForeignKey("Users", "Id").NotNullable()
|> ignore

base.Create.UniqueConstraint("SocialRelationship")
.OnTable("Social")
.Columns("FollowerUserId", "FollowingUserId") |> ignore

override this.Down() =
base.Delete.Table("Tweets") |> ignore

Then run the build script command forge fake RunMigrations command to creates this
database table

Make sure to verify the underlying schema after running the build script.

The next step is defining the function which persists the social connection in this
table.

Create a new module Persistence in the Social.fs file and define the createFollowing

function as below

217
Chapter 19 - Following a User

// FsTweet.Web/Social.fs
// ...
module Persistence =
open Database
open User
// GetDataContext -> User -> UserId -> AsyncResult<unit, Exception>
let createFollowing (getDataCtx : GetDataContext) (user : User) (UserId userId) =
let ctx = getDataCtx ()
let social = ctx.Public.Social.Create()
let (UserId followerUserId) = user.UserId
social.FollowerUserId <- followerUserId
social.FollowingUserId <- userId
submitUpdates ctx

We are using the term follower to represent the current logged in user and the
following user to represent the user that the logged in user about to follow.

Subscribing to the User Feed


The second task is subscribing to the user feed so that the follower will be getting the
tweets from the users he/she is following.

To subscribe to a user feed in GetStream.IO, we first have to get the TimelineFeed of


the logged in user.

Let's add a new function in Stream.fs to get an user's timeline feed.

// src/FsTweet.Web/Stream.fs
let timeLineFeed getStreamClient (userId : int) =
getStreamClient.StreamClient.Feed("timeline", userId.ToString())

The hard coded string "timeline" is the name of the feed group that we
created (or GetStream.io created for us) in the seventeenth chapter.

As we did for notifying a new tweet, let's create a new module GetStream and add
the subscribe function.

218
Chapter 19 - Following a User

// FsTweet.Web/Social.fs
// ...
module GetStream =
open User
open Chessie

// GetStream.Client -> User -> UserId -> AsyncResult<unit, Exception>


let subscribe (getStreamClient : GetStream.Client) (user : User) (UserId userId) =
let (UserId followerUserId) = user.UserId

let timelineFeed =
GetStream.timeLineFeed getStreamClient followerUserId
let userFeed =
GetStream.userFeed getStreamClient userId

timelineFeed.FollowFeed(userFeed) // Task
|> Async.AwaitTask // Async<uint>
|> AR.catch // AsyncResult<unit, Exception>

In GetStream.io's vocabulary, following a user means, getting the timeline feed of


the follower and follow the other user using this timeline feed.

The Presentation Layer on Server Side


In the last three sections, we built the internal pieces that are required to follow a
user. The final step is wiring the parts together with the presentation layer and
expose an HTTP endpoint to carry out the functionality.

Let's start with defining the sample JSON that the follow user endpoint should
support.

{
"userId" : 123
}

Then add a server-side type to represent this JSON request body.

219
Chapter 19 - Following a User

// FsTweet.Web/Social.fs
// ...
module Suave =
open Chiron

type FollowUserRequest = FollowUserRequest of int with


static member FromJson (_ : FollowUserRequest) = json {
let! userId = Json.read "userId"
return FollowUserRequest userId
}

If following a user operation is successful, we need to return 204 No Content, and if it


is a failure, we have to print the actual exception details to the console and return
500 Internal Server Error.

// FsTweet.Web/Social.fs
// ...
module Suave =
// ...
open Suave
// ...

let onFollowUserSuccess () =
Successful.NO_CONTENT

let onFollowUserFailure (ex : System.Exception) =


printfn "%A" ex
JSON.internalError

Then we have to define the request handler which handles the request to follow the
user.

220
Chapter 19 - Following a User

module Suave =
// ...
open Domain
open User
open Chessie
// ...

// FollowUser -> User -> WebPart


let handleFollowUser (followUser : FollowUser) (user : User) ctx = async {
match JSON.deserialize ctx.request with
| Success (FollowUserRequest userId) ->
let! webpart =
followUser user (UserId userId)
|> AR.either onFollowUserSuccess onFollowUserFailure
return! webpart ctx
| Failure _ ->
return! JSON.badRequest "invalid user follow request" ctx
}

The handleFollowUser function deserializes the request to FollowUserRequest using the


deserialize function that we defined earlier in the Json.fs file. If deserialization fails,
we are returning bad request. For a valid request, we are calling the followUser

function and maps its success and failure results to WebPart .

The last piece is wiring this handler with the /follow endpoint.

// FsTweet.Web/Social.fs
// ...
module Suave =
// ...
open Suave.Filters
open Persistence
open Domain
open Suave.Operators
open Auth.Suave

// ...

let webpart getDataCtx getStreamClient =

let createFollowing = createFollowing getDataCtx


let subscribe = GetStream.subscribe getStreamClient
let followUser = followUser subscribe createFollowing

let handleFollowUser = handleFollowUser followUser


POST >=> path "/follow" >=> requiresAuth2 handleFollowUser

221
Chapter 19 - Following a User

// FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let app =
choose [
// ...
+ Social.Suave.webpart getDataCtx getStreamClient
UserProfile.Suave.webPart getDataCtx getStreamClient
]
// ...

The Presentation Layer on Client Side


Now the backend is capable of handling the request to follow a user, and we have to
update our front-end code to release this new feature.

To follow a user, we need his/her user id. To retrieve it on the client-side, let's add a
data attribute to the follow button in the profile.liquid template.

// views/user/profile.liquid

- <a id="follow">Follow</a>
+ <a id="follow" data-user-id="{{model.UserId}}">Follow</a>

We are already having the user id of the profile being viewed as a global
variable fsTweet.user.id in the JS side. This approach is to demonstrate
another method to share data between client and server.

Then add a new javascript file social.js which handles the client side activities for
following a user.

222
Chapter 19 - Following a User

// assets/js/social.js
$(function(){
$("#follow").on('click', function(){
var $this = $(this);
var userId = $this.data('user-id');
$this.prop('disabled', true);
$.ajax({
url : "/follow",
type: "post",
data: JSON.stringify({userId : userId}),
contentType: "application/json"
}).done(function(){
alert("successfully followed");
$this.prop('disabled', false);
}).fail(function(jqXHR, textStatus, errorThrown) {
console.log({
jqXHR : jqXHR,
textStatus : textStatus,
errorThrown: errorThrown});
alert("something went wrong!")
});
});
});

This javascript snippet fires an AJAX POST request with the user id using jQuery
upon clicking the follow button and it shows an alert for both success and failure
cases of the response.

Finally, add a reference of this script file in profile.liquid

<script src="/assets/js/profile.js"></script>
+ <script src="/assets/js/social.js"></script>
{% endblock %}

That's it! We can follow a user, by clicking the follow button in his/her profile page.

Revisiting User Wall


In the current implementation, the user's wall page has subscribed only to the logged
in user's feed. This subscription will populate just if the user posts a tweet. So, the
Wall page will be empty most of the cases.

223
Chapter 19 - Following a User

Ideally, it should display the user's timeline where he/she can see the tweets from
his/her followers. And also, we need a real-time update when the timeline receives a
new tweet from the follower.

GetStream.io's javascript client library already supports these features. So, we just
have to enable it.

As a first step, in addition to passing the user feed token, we have to share the
timeline token. Update the view model of the Wall page with a new property
TimelineToken and update this property with the read-only token of the user's timeline
feed.

// src/FsTweet.Web/Wall.fs
...
type WallViewModel = {
...
+ TimelineToken : string
...}
...
+
+ let timeLineFeed =
+ GetStream.timeLineFeed getStreamClient userId

let vm = {
...
+ TimelineToken = timeLineFeed.ReadOnlyToken
...}

To pass this Timeline token with the javascript code, add a new property
timelineToken in the fsTweet.user object in the wall.liquid template.

<!-- views/user/wall.liquid -->

<script type="text/javascript">
window.fsTweet = {
user : {
...
- feedToken : "{{model.UserFeedToken}}"
+ feedToken : "{{model.UserFeedToken}}",
+ timelineToken : "{{model.TimelineToken}}"
},
stream : {...}
}

224
Chapter 19 - Following a User

The last step is initializing a timeline feed using this token and subscribe to it.

// assets/js/wall.js
$(function(){
// ...
let timelineFeed =
client.feed("timeline", fsTweet.user.id, fsTweet.user.timelineToken);

timelineFeed.subscribe(function(data){
renderTweet($("#wall"),data.new[0]);
});
});

This would update the wall page when the timeline feed receives a new tweet.

To have the wall page with a populate timeline, we need to fetch the tweets from the
timeline feed just like what we did for getting the user's tweet on the user profile
page.

// assets/js/wall.js
$(function(){
// ...
timelineFeed.get({
limit: 25
}).then(function(body) {
$(body.results.reverse()).each(function(index, tweet){
renderTweet($("#wall"), tweet);
});
});
});

In GetStream.io, the timeline feed of a user will not have the user's tweets. So, the
populated wall page here will not have user's tweet. To show both the user's tweets
and his/her timeline tweets, we can fetch the user's tweets as well and merge both
the feeds and then sort with time.

To do it, replace the above snippet with the below one

225
Chapter 19 - Following a User

// assets/js/wall.js
$(function(){
// ...
timelineFeed.get({
limit: 25
}).then(function(body) {
var timelineTweets = body.results
userFeed.get({
limit : 25
}).then(function(body){
var userTweets = body.results
var allTweets = $.merge(timelineTweets, userTweets)
allTweets.sort(function(t1, t2){
return new Date(t2.time) - new Date(t1.time);
})
$(allTweets.reverse()).each(function(index, tweet){
renderTweet($("#wall"), tweet);
});
})
})
});

Cool!

Now run the app, open two browser windows, log in as two different users and follow
the other user.

A gif image is showcasing following an another user.

After following the other user, you can get the live updates.

A gif image is showcasing the live update.

We made it!

Showing Following Users In User Profile


Currently, In the user profile page, we are always showing Follow button, even if the
logged in user already following the given user.

As we have added support for following a user, while rendering the user profile page,
we can now check whether the logged in user follows the given user or not and show
either the follow button or following button accordingly.

226
Chapter 19 - Following a User

To enable this, let's get add a new type UserProfileType to represent all the three
possible cases while serving the user profile page.

// src/FsTweet.Web/UserProfile.fs
// ...
type UserProfileType =
| Self
| OtherNotFollowing
| OtherFollowing
// ...

Then we need to use this type in the place of the IsSelf property.

type UserProfile = {
User : User
GravatarUrl : string
- IsSelf : bool
+ UserProfileType : UserProfileType
}

Now we are getting a set of compiler warnings, showing us the directions of the
places where we have to go and fix this property change.

The first place that we need to fix is the newProfile function. Let's change it to
accept a one more parameter userProfileType and use it to set UserProfileType of
the new user profile.

- let newProfile user = {


+ let newProfile userProfileType user = {
User = user
GravatarUrl = gravatarUrl user.EmailAddress
- IsSelf = false
+ UserProfileType = userProfileType
}

Then in the places where we are calling this newProfile function, pass the
appropriate user profile type.

227
Chapter 19 - Following a User

match loggedInUser with


| None ->
let! userMayBe = findUser username
- return Option.map newProfile userMayBe
+ return Option.map (newProfile OtherNotFollowing) userMayBe
| Some (user : User) ->
if user.Username = username then
let userProfile =
- {newProfile user with IsSelf = true}
+ newProfile Self user
return Some userProfile
else
let! userMayBe = findUser username
- return Option.map newProfile userMayBe
+ return Option.map (newProfile OtherNotFollowing) userMayBe

For an anonymous user, the user profile will always be other whom he/she is not
following. But for a logged in user who is viewing an another user's profile, we need
to check the Social table and set the type to either OtherNotFollowing or
OtherFollowing .

Let's keep it as OtherNotFollowing for the time being and we'll implement this check
shortly.

The next place that we need to fix is where we are populating the
UserProfileViewModel . To do it, we first have to add a new property IsFollowing in the
view model.

type UserProfileViewModel = {
// ...
IsFollowing : bool
}

And then in the newUserProfileViewModel function, populate this and the IsSelf

property from the UserProfileType .

let newUserProfileViewModel ... =


// ...
let isSelf, isFollowing =
match userProfile.UserProfileType with
| Self -> true, false
| OtherFollowing -> false, true
| OtherNotFollowing -> false, false

228
Chapter 19 - Following a User

{
// ...
IsSelf = isSelf
IsFollowing = isFollowing
}

Now we are right except the following check. The last piece that we need to change
before implementing this check is updating the profile.liquid show either follow or
following link based on the IsFollowing property.

<!-- views/user/profile.liquid -->


<!-- ... -->

{% unless model.IsSelf %}
- <a href="#" id="follow">Follow</a>
+ {% if model.IsFollowing %}
+ <a href="#" id="unfollow">Following</a>
+ {% else %}
+ <a href="#" id="follow" data-user-id="{{model.UserId}}">Follow</a>
+ {% endif %}
{% endunless %}

Great! Now it's time to implement the isFollowing check.

Implementing The IsFollowing Check


Let's get started by defining a type for this check in the Social.fs's Domain module.

// src/FsTweet.Web/Social.fs
module Domain =
// ...
type IsFollowing =
User -> UserId -> AsyncResult<bool, Exception>
// ...

229
Chapter 19 - Following a User

With this type in place, we can now change the findUserProfile to accept a new
parameter isFollowing of this type and use it to figure out the actual
UserProfileType .

module Domain =
// ...
open Social.Domain
// ...

let findUserProfile
... (isFollowing : IsFollowing) ... = asyncTrial {
match loggedInUser with
| None -> // ...
| Some (user : User) ->
// ...
else
// ...
// return Option.map (newProfile OtherNotFollowing) userMayBe
match userMayBe with
| Some otherUser ->
let! isFollowingOtherUser =
isFollowing user otherUser.UserId
let userProfileType =
if isFollowingOtherUser then
OtherFollowing
else OtherNotFollowing
let userProfile =
newProfile userProfileType otherUser
return Some userProfile
| None -> return None
}

Then add the implementation function isFollowing in the Persistence module

230
Chapter 19 - Following a User

// src/FsTweet.Web/Social.fs
// ...
module Persistence =
// ...
open Chessie.ErrorHandling
open FSharp.Data.Sql
open Chessie
// ...

let isFollowing (getDataCtx : GetDataContext)


(user : User) (UserId userId) = asyncTrial {

let ctx = getDataCtx ()


let (UserId followerUserId) = user.UserId

let! connection =
query {
for s in ctx.Public.Social do
where (s.FollowerUserId = followerUserId &&
s.FollowingUserId = userId)
} |> Seq.tryHeadAsync |> AR.catch

return connection.IsSome
}
// ...

The logic is straight-forward, we retrieve the social connection by providing both the
follower user id and following user's user id. If the relationship exists we return true ,
else we return false .

Then we need to pass this function after partially applied the first parameter
( getDataCtx ) to the findUserProfile function.

+ open Social
// ...
let webpart (getDataCtx : GetDataContext) getStreamClient =
let findUser = Persistence.findUser getDataCtx
- let findUserProfile = findUserProfile findUser
+ let isFollowing = Persistence.isFollowing getDataCtx
+ let findUserProfile = findUserProfile findUser isFollowing
// ...

That's it. Now if we run the application and views a profile that we are following, we
will be seeing the following button instead of the follow button.

231
Chapter 19 - Following a User

Summary
We covered a lot of ground in this chapter. We started with adding log out and then
we moved to adding support for following the user. Then we updated the wall page
to show the timeline, and finally we revisited the user profile page to reflect the social
connection status.

The source code of this chapter is available on GitHub

Exercise
It'd be great if we can get an email notification when someone follows us in
FsTweet.

How about adding the support for unfollowing a user?

232
Chapter 20 - Fetching Followers and Following Users

In this chapter, we are going to expose two HTTP JSON endpoints to fetch the list of
followers and following users. Then we will be updating the user profile page front-
end to consume these APIs and populate the Following and Followers Tabs.

Adding Followers API


Let's get started with the implementation of followers API. As we did for other
persistence logic, let's define a new type to represent the find followers persistence
logic.

// src/FsTweet.Web/Social.fs
module Domain =
// ...
type FindFollowers =
UserId -> AsyncResult<User list, Exception>
// ...

233
Chapter 20 - Fetching Followers and Following Users

The implementation function of this type will be leveraging the composable queries
concept to find the given user id's followers

// src/FsTweet.Web/Social.fs
// ...
module Persistence =
// ...
open System.Linq
// ...

// GetDataContext -> userId ->


// AsyncResult<DataContext.``public.UsersEntity`` seq, Exception>
let findFollowers (getDataCtx : GetDataContext) (UserId userId) = asyncTrial {
let ctx = getDataCtx()

let selectFollowersQuery = query {


for s in ctx.Public.Social do
where (s.FollowingUserId = userId)
select s.FollowerUserId
}

let! followers =
query {
for u in ctx.Public.Users do
where (selectFollowersQuery.Contains(u.Id))
select u
} |> Seq.executeQueryAsync |> AR.catch

return followers
}

Using the selectFollowersQuery , we are first getting the list of followers user ids. Then
we are using these identifiers to the get corresponding user details.

One thing to notice here is, we are returning a sequence of


DataContext.public.UsersEntity on success. But what we want to return is its domain
representation, a list of User .

Like what we did for finding the user by username, we need to map the all the user
entities in the sequence to their respective domain model.

234
Chapter 20 - Fetching Followers and Following Users

To do it, we first need to extract the mapping functionality from the mapUser function.

// src/FsTweet.Web/User.fs
// ...
module Persistence =
// ...
open System

// DataContext.``public.UsersEntity`` -> Result<User, Exception>


let mapUserEntityToUser (user : DataContext.``public.UsersEntity``) =
let userResult = trial {
let! username = Username.TryCreate user.Username
let! passwordHash = PasswordHash.TryCreate user.PasswordHash
let! email = EmailAddress.TryCreate user.Email
let userEmailAddress =
match user.IsEmailVerified with
| true -> Verified email
| _ -> NotVerified email
return {
UserId = UserId user.Id
Username = username
PasswordHash = passwordHash
EmailAddress = userEmailAddress
}
}
userResult
|> mapFailure Exception
//...

This extracted method returns Result<User, Exception> and then modify the mapUser

function to use this function.

module Persistence =
// ...
let mapUserEntityToUser ... = ...

// DataContext.``public.UsersEntity`` -> AsyncResult<User, Exception>


let mapUser (user : DataContext.``public.UsersEntity``) =
mapUserEntityToUser user
|> Async.singleton
|> AR

// ...

235
Chapter 20 - Fetching Followers and Following Users

I just noticed that the name mapUser misleading. So, rename it to mapUserEntity to
communicate what it does.

module Persistence =
...
- let mapUser (user : DataContext.``public.UsersEntity``) =
+ let mapUserEntity (user : DataContext.``public.UsersEntity``) =
...

let findUser ... = asyncTrial {


...
- let! user = mapUser user
+ let! user = mapUserEntity user
}

The next step is transforming a sequence of DataContext.``public.UsersEntity`` to a


list of User .

let mapUserEntities (users : DataContext.``public.UsersEntity`` seq) =


users // DataContext.``public.UsersEntity`` seq
|> Seq.map mapUserEntityToUser // Result<User, Exception> seq
// TODO

The next step is to transform Result<User, Exception> seq to Result<User list,

Exception> .

As Chessie library already supports the failure side of the Result as a list, we
don't specify the failure side as Exception list .

To do this transformation, Chessie library provides a function called collect .

let mapUserEntities (users : DataContext.``public.UsersEntity`` seq) =


users // DataContext.``public.UsersEntity`` seq
|> Seq.map mapUserEntityToUser // Result<User, Exception> seq
|> collect // Result<User list, Exception>
// TODO

We are not done yet as the failure side is still a list of Exception but what we want is
single Exception. .NET already supports this through AggregateException.

236
Chapter 20 - Fetching Followers and Following Users

// DataContext.``public.UsersEntity`` seq ->


// AsyncResult<User list, Exception>
let mapUserEntities (users : DataContext.``public.UsersEntity`` seq) =
users // DataContext.``public.UsersEntity`` seq
|> Seq.map mapUserEntityToUser // Result<User, Exception> seq
|> collect // Result<User list, Exception>
|> mapFailure
(fun errs -> new AggregateException(errs) :> Exception)
// Result<User list, Exception>
|> Async.singleton // Async<Result<User list, Exception>>
|> AR // AsyncResult<User list, Exception>

Using the mapFailure function from Chessie, we are transforming the list of
exceptions into an AggregateException , and then we are mapping it to AsyncResult<User

list, Exception> .

With this mapUserEntities function in place, we can now return a User list in the
findFollowers function

module Persistence =
...
+ open User.Persistence
...

let findFollowers ... = asyncTrail {


...
- return followers
+ return! mapUserEntities followers
}

Now we have the persistence layer ready.

The JSON response that we are going to send will have the following structure

{
"users": [
{
"username": "tamizhvendan"
}
]
}

To keep it simple, we are just returning the username of the users.

237
Chapter 20 - Fetching Followers and Following Users

To model the corresponding server-side representation of this JSON object, let's add
some types along with the static member function ToJson which is required by the
Chiron library to serialize the type to JSON.

// src/FsTweet.Web/Social.fs
// ...
module Suave =
// ...

type UserDto = {
Username : string
} with
static member ToJson (u:UserDto) =
json {
do! Json.write "username" u.Username
}

type UserDtoList = UserDtoList of (UserDto list) with


static member ToJson (UserDtoList userDtos) =
let usersJson =
userDtos
|> List.map (Json.serializeWith UserDto.ToJson)
json {
do! Json.write "users" usersJson
}

let mapUsersToUserDtoList (users : User list) =


users
|> List.map (fun user -> {Username = user.Username.Value})
|> UserDtoList

// ...

To expose the findFollowers function as an HTTP API, we first need to specify what
we need to do for both success and failure.

module Suave =
// ...
let onFindUsersFailure (ex : System.Exception) =
printfn "%A" ex
JSON.internalError
let onFindUsersSuccess (users : User list) =
mapUsersToUserDtoList users
|> Json.serialize
|> JSON.ok
// ...

238
Chapter 20 - Fetching Followers and Following Users

Then add a new function which handles the request for fetching user's followers

// FindFollowers -> int -> WebPart


let fetchFollowers (findFollowers: FindFollowers) userId ctx = async {
let! webpart =
findFollowers (UserId userId)
|> AR.either onFindUsersSuccess onFindUsersFailure
return! webpart ctx
}

Finally, add the route for the HTTP endpoint.

let webpart getDataCtx getStreamClient =


...
- POST >=> path "/follow" >=> requiresAuth2 handleFollowUser
+ let findFollowers = findFollowers getDataCtx
+ choose [
+ GET >=> pathScan "/%d/followers" (fetchFollowers findFollowers)
+ POST >=> path "/follow" >=> requiresAuth2 handleFollowUser
+ ]

Adding Following Users API


The API to serve the list of users being followed by the given user follows the similar
structure except for the actual backend query

// src/FsTweet.Web/Social.fs
module Domain =
// ...
type FindFollowingUsers = UserId -> AsyncResult<User list, Exception>

module Persistence =
// ...
let findFollowingUsers (getDataCtx : GetDataContext) (UserId userId) = asyncTrial {
let ctx = getDataCtx()

let selectFollowingUsersQuery = query {


for s in ctx.Public.Social do
where (s.FollowerUserId = userId)
select s.FollowingUserId
}

239
Chapter 20 - Fetching Followers and Following Users

let! followingUsers =
query {
for u in ctx.Public.Users do
where (selectFollowingUsersQuery.Contains(u.Id))
select u
} |> Seq.executeQueryAsync |> AR.catch

return! mapUserEntities followingUsers


}

In the selectFollowingUsersQuery , we are selecting the list of user ids that are being
followed by the provided user id.

Like fetchFollowers , we just have to add fetchFollowingUsers function and expose it


to a new HTTP route

module Suave =
// ...

// FindFollowingUsers -> int -> FindFollowingUsers


let fetchFollowingUsers (findFollowingUsers: FindFollowingUsers) userId ctx = async
{
let! webpart =
findFollowingUsers (UserId userId)
|> AR.either onFindUsersSuccess onFindUsersFailure
return! webpart ctx
}

let webpart getDataCtx getStreamClient =


...
let findFollowers = findFollowers getDataCtx
+ let findFollowingUsers = findFollowingUsers getDataCtx
choose [
GET >=> pathScan "/%d/followers" (fetchFollowers findFollowers)
+ GET >=> pathScan "/%d/following" (fetchFollowingUsers findFollowingUsers)
POST >=> path "/follow" >=> requiresAuth2 handleFollowUser
]

Now we both the endpoints are up and running.

240
Chapter 20 - Fetching Followers and Following Users

Updating UI
To consume these two APIs and to render it on the client side, we need to update
the social.js

// src/FsTweet.Web/assets/js/social.js
$(function(){
// ...
var usersTemplate = `
{{#users}}
<div class="well user-card">
<a href="/{{username}}">@{{username}}</a>
</div>
{{/users}}`;

function renderUsers(data, $body, $count) {


var htmlOutput = Mustache.render(usersTemplate, data);
$body.html(htmlOutput);
$count.html(data.users.length);
}

(function loadFollowers () {
var url = "/" + fsTweet.user.id + "/followers"
$.getJSON(url, function(data){
renderUsers(data, $("#followers"), $("#followersCount"))
})
})();

(function loadFollowingUsers() {
var url = "/" + fsTweet.user.id + "/following"
$.getJSON(url, function(data){
renderUsers(data, $("#following"), $("#followingCount"))
})
})();
});

Using jQuery's getJSON function, we are fetching the JSON object and then
rendering it using Mustache template.

That's it!

241
Chapter 20 - Fetching Followers and Following Users

Summary
In this chapter, we have exposed two HTTP APIs to retrieve the list of followers and
following users.

As we saw in other posts, we are just doing transformations to achieve what we


want. In the process, we are creating some useful abstractions (like what we did
here for mapUserEntityToUser function) which in turn helps us to deliver the features
faster (like the AR.either function).

With this, we are done with all the features that will be part of this initial version of
FsTweet. In the upcoming posts, we are going to add support for logging and learn
how to deploy it to Azure.

As usual, the source code of this chapter is available on GitHub.

242
Chapter 21 - Deploying to Azure App Service

In this chapter, we are going to prepare our code for deployment, and then we'll be
deploying our FsTweet Application on Azure using Azure App Service.

Let's dive in.

Revisiting Database Interaction


The first place that we need to touch to prepare FsTweet for deployment is Db.fs.
Especially, the below lines in this file

[<Literal>]
let private connString =
"Server=127.0.0.1;Port=5432;Database=FsTweet;User Id=postgres;Password=test;"

The SQLProvider requires connection string to be available during compile time to


create the types from the database.

In other words, we need a live database (with schemas defined) to compile the
FsTweet.

In our build script, we are running the migration script to create/modify the tables
before the compilation of the application. So, we don't need to worry about the
database schema.

Similarly, In runtime, we are getting the connection string from an environment


variable and using it to initialize the database connection

// src/FsTweet.Web/FsTweet.Web.fs
// ...
let main argv =
// ...
let fsTweetConnString =
Environment.GetEnvironmentVariable "FSTWEET_DB_CONN_STRING"
// ...
let getDataCtx = dataContext fsTweetConnString
// ...

The real concern is if we are going with the current code as it is while compiling the
code on a cloud machine, that machine has to have a local Postgres database which
can be accessed using the above connection string literal.

243
Chapter 21 - Deploying to Azure App Service

We can have a separate database (accessible from anywhere) for this purpose
alone and uses that as a literal. But there are a lot of drawbacks!

Now we need to maintain two databases, one for compilation and another one
for running in production.

It means our migration script has to run on both the databases.

We also need to makes sure that the database schema should be same in both
the databases.

It's lot of work(!) for a simple task! So, this approach is not practical.

Before arriving at the solution, Let's think about what would be an ideal scenario.

1. Provision a production-ready PostgreSQL database


2. Set the connection string of this database as the value of environment variable
FSTWEET_DB_CONN_STRING

3. Run the migration script


4. Compile (Build) the application
5. Run the application

The first step is manual, and our FAKE build script is already taking care of rest of
the steps.

Later in this chapter, We'll be adding a separate step in our build script to
deploy the application on cloud.

To make this ideal scenario work, we need an intermediate step between three and
four, which takes the connection string from the environment variable and replaces
the connection string literal in Db.fs with this one. After successful compilation, we
need to revert this change.

It's super easy with our build script. Let's make it work!

We are already having the local connection string in the build script which we are
using if there is no value in the FSTWEET_DB_CONN_STRING environment variable.

let connString =
environVarOrDefault
"FSTWEET_DB_CONN_STRING"
@"Server=127.0.0.1;Port=5432;Database=FsTweet;User Id=postgres;Password=test;"

244
Chapter 21 - Deploying to Azure App Service

Let's extract this out and define a binding for this value

+ let localDbConnString =
+ @"Server=127.0.0.1;Port=5432;Database=FsTweet;User Id=postgres;Password=test;"

let connString =
environVarOrDefault
"FSTWEET_DB_CONN_STRING"
- @"Server=127.0.0.1;Port=5432;Database=FsTweet;User Id=postgres;Password=test;"
+ localDbConnString

Then add a build target, to verify the presence of this connection string in the Db.fs
file.

// build.fsx
// ...
let dbFilePath = "./src/FsTweet.Web/Db.fs"

Target "VerifyLocalDbConnString" (fun _ ->


let dbFileContent = System.IO.File.ReadAllText dbFilePath
if not (dbFileContent.Contains(localDbConnString)) then
failwith "local db connection string mismatch"
)
// ...

We are adding this target, to ensure that the local database connection string that we
have it here is same as that of in Db.fs file before replacing it.

Let's define a helper function swapDbFileContent , which swaps the connection string

// build.fsx
// ...
let swapDbFileContent (oldValue: string) (newValue : string) =
let dbFileContent = System.IO.File.ReadAllText dbFilePath
let newDbFileContent = dbFileContent.Replace(oldValue, newValue)
System.IO.File.WriteAllText(dbFilePath, newDbFileContent)
// ...

Then add two targets in the build target, one to change the connection string and
another one to revert the change.

245
Chapter 21 - Deploying to Azure App Service

// build.fsx
// ...
Target "ReplaceLocalDbConnStringForBuild" (fun _ ->
swapDbFileContent localDbConnString connString
)
Target "RevertLocalDbConnStringChange" (fun _ ->
swapDbFileContent connString localDbConnString
)
// ...

As the last step, alter the build order to leverage the targets that we created just now.

// Build order
"Clean"
==> "BuildMigrations"
==> "RunMigrations"
+ ==> "VerifyLocalDbConnString"
+ ==> "ReplaceLocalDbConnStringForBuild"
==> "Build"
+ ==> "RevertLocalDbConnStringChange"
==> "Views"
==> "Assets"
==> "Run"

That's it!

Supporting F# Compiler 4.0


At the time of this writing, The F# Compiler version that has been supported by
Azure App Service is 4.0. But we developed the application using F# 4.1. So, we
have to compile our code using F# 4.0 before deploying.

When we compile our application using F# 4.0 compiler, we'll get a compiler error

...\FsTweet.Web\Json.fs(17,41):
Unexpected identifier in type constraint.
Expected infix operator, quote symbol or other token.

246
Chapter 21 - Deploying to Azure App Service

The piece of code that is bothering here is this one

let inline deserialize< ^a when (^a or FromJsonDefaults)


: (static member FromJson: ^a -> ^a Json)>
req : Result< ^a, string> =
// ...

If you check out the release notes of F# 4.1, you can find there are some
improvements made on Statically Resolved Type Parameter support to fix this error
(or bug).

Fortunately, rest of codebase is intact with F# 4.0, and we just need to fix this one.

As a first step, comment out the deserialize function in the JSON module and then
add the following new implementation.

// src/FsTweet.Web/Json.fs
// ...
// Json -> Choice<'a, string> -> HttpRequest -> Result<'a, string>
let deserialize tryDeserialize req =
parse req
|> bind (fun json -> tryDeserialize json |> ofChoice)

This new version of the deserialize is similar to the old one except that we are going
to get the function Json.tryDeserialize as a parameter ( tryDeserialize ) instead of
using it directly inside the function.

Then we have to update the places where this function is being used

// src/FsTweet.Web/Social.fs
...
let handleFollowUser (followUser : FollowUser) (user : User) ctx = async {
- match JSON.deserialize ctx.request with
+ match JSON.deserialize Json.tryDeserialize ctx.request with
...

// src/FsTweet.Web/Wall.fs
...
let handleNewTweet publishTweet (user : User) ctx = async {
- match JSON.deserialize ctx.request with
+ match JSON.deserialize Json.tryDeserialize ctx.request with
...

247
Chapter 21 - Deploying to Azure App Service

Http Bindings
We are currently using default HTTP bindings provided by Suave. So, when we run
our application locally, the web server will be listening on the default port 8080 .

But when we are running it on Azure or any other cloud vendor, we have to use the
port providied by them.

In addition to that, the default HTTP binding uses the loopback address 127.0.0.1

instead of 0.0.0.0 which makes it non-accessible from the other hosts.

We have to fix both of these, to run our application on the cloud.

// src/FsTweet.Web/FsTweet.Web.fs
// ...
open System.Net
// ...
let main argv =
// ...

+ let ipZero = IPAddress.Parse("0.0.0.0")

+ let port =
+ Environment.GetEnvironmentVariable "PORT"

+ let httpBinding =
+ HttpBinding.create HTTP ipZero (uint16 port)

let serverConfig =
{defaultConfig with
- serverKey = serverKey}
+ serverKey = serverKey
+ bindings=[httpBinding]}

We are getting the port number to listen to from the environment variable PORT and
modifying the defaultConfig to use the custom HTTP binding instead of the default
one.

In Azure App Service, the port to listen is already available in the environment
variable HTTP_PLATFORM_PORT . But we are using PORT here to avoid cloud
vendor specific stuff in the codebase. Later via configuration (outside the

248
Chapter 21 - Deploying to Azure App Service

codebase), we will be mapping these environment variables.

The web.config File


As mentioned in Suave's documentation, we need to have a web.config to instruct
IIS to route the traffic to Suave.

Create a new file web.config in the root directory and update it as below

> touch web.config

<?xml version="1.0" encoding="UTF-8"?>


<configuration>
<system.webServer>

<handlers>
<remove name="httpplatformhandler" />
<add
name="httpplatformhandler"
path="*"
verb="*"
modules="httpPlatformHandler"
resourceType="Unspecified"
/>
</handlers>

<httpPlatform
stdoutLogEnabled="true"
startupTimeLimit="20"
processPath="%HOME%\site\wwwroot\FsTweet.Web.exe"
>

<environmentVariables>
<environmentVariable name="PORT" value="%HTTP_PLATFORM_PORT%" />
</environmentVariables>
</httpPlatform>

</system.webServer>
</configuration>

Most of the above content was copied from the documentation, and we have
modified the following.

249
Chapter 21 - Deploying to Azure App Service

processPath - specifies the location of the FsTweet.Web executable.


environmentVariables - creates a new environment variable PORT with the value
of the environment variable HTTP_PLATFORM_PORT .

Revisiting Build Script


To deploy FsTweet on Azure App Service we are going to use Kudu. The FAKE
library has excellent support for Kudu, and we can deploy our application right from
our build script.

The FAKE library provides a kuduSync function which copies with semantic
appropriate for deploying website files. Before calling kuduSync , we need to stage
the files (in a temporary directory) that has to be copied. This staging directory path
can be retrieved from the FAKE Library's deploymentTemp binding. Then the kuduSync

function syncs the files for deployment.

The deploymentTemp directory is the exact replica of our local build directory on the
delpoyment side. So, instead of staging the files explicitly, we can use this directory
as the build directory. Another benefit is user account which will be deploying has full
access to this directory.

To do the deployment from our build script, we first need to know what is the
environment that we are in through the environment variable FSTWEET_ENVIRONMENT .

// build.fsx
// ...
open Fake.Azure

let env = environVar "FSTWEET_ENVIRONMENT"

250
Chapter 21 - Deploying to Azure App Service

Based on this env value, we can set the build directory.

// build.fsx
// ...

let env = environVar "FSTWEET_ENVIRONMENT"

- // Directories
- let buildDir = "./build/"

+ let buildDir =
+ if env = "dev" then
+ "./build"
+ else
+ Kudu.deploymentTemp

// ...

For dev environment, we'll be using ./build as build directory and


Kudu.deploymentTemp as build directory in the other environments.

Then we need to two more targets

// build.fsx
// ...

Target "CopyWebConfig" ( fun _ ->


FileHelper.CopyFile Kudu.deploymentTemp "web.config")

Target "Deploy" Kudu.kuduSync

// ...

The CopyWebConfig copies the web.config to the Kudu.deploymentTemp directory (aka


staging directory).

The Deploy just calls the Kudu.kuduSync function.

The last thing that we need to revisit in the build script is the build order.

We need two build orders. One to run the application locally (which we already have)
and another one to deploy. In the latter case, we don't need to run the application
explicitly as Azure Web App takes cares of executing our application using the
web.config file.

251
Chapter 21 - Deploying to Azure App Service

To make it possible, Replace the existing build order with the below one

// build.fsx
// ...

// Build order
"Clean"
==> "BuildMigrations"
==> "RunMigrations"
==> "VerifyLocalDbConnString"
==> "ReplaceLocalDbConnStringForBuild"
==> "Build"
==> "RevertLocalDbConnStringChange"
==> "Views"
==> "Assets"

"Assets"
==> "Run"

"Assets"
==> "CopyWebConfig"
==> "Deploy"

Now we have two different Target execution hierarchy. Refer this detailed
documentation to know how the order hierarchy works in FAKE.

Invoking Build Script


We have a build script that automates all the necessary activities to do the
deployment. But, who is going to run this script in the first place?

That's where .deployment file comes into the picture.

Using this file, we can specify what command to run to deploy the application in
Azure App Service.

Let's create this file in the application's root directory and update it to invoke the build
script.

> touch .deployment

252
Chapter 21 - Deploying to Azure App Service

[config]
command = build.cmd Deploy

The build.cmd is a part of the boilerplate that we used in chapter 1.

With this, we completed all the coding changes that are required to perform the
deployment.

PostgreSQL Database Setup


To run FsTweet on the cloud, we need to have a database on the cloud. We can
make use of ElephantSQL which provides a free plan.

Create a new free database instance in ElephantSQL and note down its credentials
to pass it as a connection string to our application.

GetStream.io Setup
Next thing that we need to set up is GetStream.io as we can't use the one that we
used during development.

253
Chapter 21 - Deploying to Azure App Service

Create a new app called fstweet.

And create two flat feed groups, user , and timeline .

254
Chapter 21 - Deploying to Azure App Service

After creation keep a note of the App Id, Key, and Secret

Postmark Setup

255
Chapter 21 - Deploying to Azure App Service

Regarding Postmark, we don't need to create a new server account as we are not
using it in the development environment.

However, we have to modify the signup email template to the use the URL of the
deployed application instead of the localhost URL.

- http://localhost:8080/signup/verify/{{ verification_code }}
+ http://fstweet.azurewebsites.net/signup/verify/{{ verification_code }}

Create Azure App Service


With all the dependent services are up, our next focus is deploying the application in
azure app service.

To deploy the application, we are going to use Azure CLI. It offers a convenient way
to manage Azure resource easily from the command line.

Make sure you are having this CLI installed in your machine as well as an active
Azure Subscription

The first thing that we have to do is Log in to our Azure account from Azure CLI.
There are multiple ways we can log in and authenticate with the Azure CLI and here
we are going to use the Interactive log-in option.

Run the login command and then in the web browser go the given URL and enter the
provided code.

> az login
To sign in, use a web browser to open the page https://aka.ms/devicelogin and
enter the code H2ABMSZR3 to authenticate

Then log in using the subscription that you wanted to use.

Upon successful login, you will get a similar JSON as the output in the command
prompt.

[
{
"cloudName": "AzureCloud",
"id": "900b4d47-d0c4-888a-9e6d-000061c82010",

256
Chapter 21 - Deploying to Azure App Service

"isDefault": true,
"name": "...",
"state": "Enabled",
"tenantId": "9f67d6b5-5cb4-8fc0-a5cc-345f9cd46e7a",
"user": {
"name": "...",
"type": "user"
}
}
]

The next step is creating a new deployment user.

A deployment user is required for doing local git deployment to a web app.

> az webapp deployment user set --user-name fstdeployer --password secret123

{
"id": null,
"kind": null,
"name": "web",
"publishingPassword": null,
"publishingPasswordHash": null,
"publishingPasswordHashSalt": null,
"publishingUserName": "fstdeployer",
"type": "Microsoft.Web/publishingUsers/web",
"userName": null
}

The next thing is creating a resource group in Azure.

A resource group is a logical container into which Azure resources like web apps,
databases, and storage accounts are deployed and managed.

> az group create --name fsTweetResourceGroup --location "Central US"

You can get a list of all the locations available using the az appservice list-

locations command

{
"id": "/subscriptions/{id}/resourceGroups/fsTweetResourceGroup",
"location": "centralus",

257
Chapter 21 - Deploying to Azure App Service

"managedBy": null,
"name": "fsTweetResourceGroup",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null
}

To host our application in Azure App Service, we first need to have an Azure App
Service Plan

Let's creates an App Service plan named fsTweetServicePlan in the Free pricing tier

> az appservice plan create --name fsTweetServicePlan --resource-group fsTweetResource


Group --sku FREE

{
"name": "fsTweetServicePlan",
"provisioningState": "Succeeded",
"resourceGroup": "fsTweetResourceGroup",
"sku": {
...
"tier": "Free"
},
"status": "Ready",
...
}

Then using the az webapp create command, create a new web app in the App
Service.

> az webapp create --name fstweet --resource-group fsTweetResourceGroup \


--plan fsTweetServicePlan --deployment-local-git

Local git is configured with url of


'https://fstdeployer@fstweet.scm.azurewebsites.net/fstweet.git'
{
// a big json object
}

258
Chapter 21 - Deploying to Azure App Service

The --deployment-local-git flag, creates a remote git directory for the web app
and we will be using it to push our local git repository and deploy the changes.

Note down the URL of the git repository as we'll be using it shortly.

We’ve created an empty web app, with git deployment enabled. If you visit the
http://fstweet.azurewebsites.net/ site now, we can see a blank web app page.

We are just two commands away from deploying our application in Azure.

The FsTweet Application uses a set of environment variables to get the application's
configuration parameters (Connection string, GetStream secret, etc.,). To make
these environment variables available for the application, we can leverage the App
Settings.

Open the Azure Portal, Click App Services on the left and then click fstweet from the
list.

In the fstweet app service dashboard, click on Application Settings and enter all the
required configuration parameters and don't forget to click the Save button!

259
Chapter 21 - Deploying to Azure App Service

We can do this using Azure CLI appsettings command as well.

Now all set for deploying the application.

Add the git URL that we get after creating the web app as git remote

> git remote add azure \


https://fstdeployer@fstweet.scm.azurewebsites.net/fstweet.git

This command assumes that the project directory is under git version control.
If you haven't done it yet, use the following commands to setup the git
repository

git init
git add -A
git commit -m "initial commit"

The last step is pushing our local git repository to the azure (alias of the remote git
repository). It will prompt you to enter the password. Provide the password that we
used to create the deployment user.

> git push azure master


Passsword for 'https://fstdeployer@fstweet.scm.azurewebsites.net':

Then sit back and watch the launch!

260
Chapter 21 - Deploying to Azure App Service

Counting objects: 1102, done.


Delta compression using up to 4 threads.
....
....
....
remote: ------------------------------------------------------
remote: Build Time Report
remote: ------------------------------------------------------
remote: Target Duration
remote: ------ --------
remote: Clean 00:00:00.0018425
remote: BuildMigrations 00:00:01.1475457
remote: RunMigrations 00:00:01.9743288
remote: VerifyLocalDbConnString 00:00:00.0035704
remote: ReplaceLocalDbConnStringForBuild 00:00:00.0065504
remote: Build 00:00:45.9225862
remote: RevertLocalDbConnStringChange 00:00:00.0060335
remote: Views 00:00:00.0625286
remote: Assets 00:00:00.0528166
remote: CopyWebConfig 00:00:00.0094524
remote: Deploy 00:00:00.9716061
remote: Total: 00:00:50.2883751
remote: ------------------------------------------------------
remote: Status: Ok
remote: ------------------------------------------------------
remote: Running post deployment command(s)...
remote: Deployment successful.
To https://fstweet.scm.azurewebsites.net/fstweet.git
f40d33c..a2a7732 master -> master

Awesome! We made it!!

Now if you browse the site, we can see the beautiful landing page :)

261
Chapter 21 - Deploying to Azure App Service

Post deployment, if we want to make any change, just do a git commit. After making
the changes and push it to the remote as we did now!

If we don't want to do it manually, we can enable continuous deployment from the


Azure portal.

262
Chapter 21 - Deploying to Azure App Service

Summary
In this chapter, we have made changes to the codebase to enable the deployment
and deployed our application on Azure using Azure CLI.

We owe a lot of thanks to FAKE, which made our job easier.

The source code associated with this chapter is available on GitHub

263
Chapter 22 - Adding Logs using Logary

In this chapter, we are going to improve how we are logging the exceptions in
FsTweet.

What's Wrong With The Current Logic


Can you guess what went wrong by looking at the below log of FsTweet?

System.AggregateException: One or more errors occurred. ---> Stream.StreamException: E


rror: NameResolutionFailure
at Stream.StreamException.FromResponse (RestSharp.IRestResponse response) [0x00075]
in <5158106fb22e4063ad9c9f74906b6f9e>:0
at Stream.StreamFeed+<AddActivity>d__22.MoveNext () [0x00139] in <5158106fb22e4063ad
9c9f74906b6f9e>:0
--- End of inner exception stack trace ---
---> (Inner Exception #0) Stream.StreamException: Error: NameResolutionFailure
at Stream.StreamException.FromResponse (RestSharp.IRestResponse response) [0x00075]
in <5158106fb22e4063ad9c9f74906b6f9e>:0
at Stream.StreamFeed+<AddActivity>d__22.MoveNext () [0x00139] in <5158106fb22e4063ad
9c9f74906b6f9e>:0 <---

We'll get the above error if the internet connection is down when we post a tweet.

And the code that is logging this exception looks like this

let onPublishTweetFailure (err : PublishTweetError) =


match err with
| NotifyTweetError (tweetId, ex) ->
printfn "%A" ex
onPublishTweetSuccess tweetId
// ...

Our current apporach to logging exceptions in FsTweet is so naive, and clearly, it is


not helping us to troubleshoot. Let's fix this before we wrap it up!

Introducing Logary
Logary is a high-performance, semantic logging, health, and metrics library for .Net.
It enables us to log what happened in the application in a meaningful way which in
turn help us a lot in analyzing them.

264
Chapter 22 - Adding Logs using Logary

The Logary NuGet package has a dependency on NodaTime NuGet package.

At the time of this writing, there is an incompatibility issue with NodaTime version 2.0
in Logary and the NodaTime version which works well with Logary is 1.3.2 .

So, before adding the Logary package, we first need to add the NodaTime v1.3.2
package and then the Logary package.

> forge paket add NodaTime --version 1.3.2 \


-p src/FsTweet.Web/FsTweet.Web.fsproj
> forge paket add Logary --version 4.2.1 \
-p src/FsTweet.Web/FsTweet.Web.fsproj

If we just added Logary, while adding, it will pull the NodaTime 2.0 version

Now we have the Logary package added to our Application

The Logging Approach


There are multiple ways that we can leverage Logary in our application. One of the
approaches that fit well with functional programming principles is separating the
communication (of what went wrong) and action (log the error using Logary).

In Suave's world, it translates to the following

265
Chapter 22 - Adding Logs using Logary

Inside use case (User Signup, New Tweet, etc.,) boundary, we communicate what
went wrong with all the required information that can be helpful to troubleshoot. At
the edge of the application, we check is there any error in the request pipeline and
perform the necessary action.

To pass data between WebPart's in the request pipeline, Suave has a useful
property called userState in the HttpContext record. The userState is of type
Map<string, obj> and we can add custom data to it using the setUserData function in
the Writers module.

The setState function that we used while persisting the logged in user is to
persist data in a session (across multiple requests).

The setUserData function is to pass the data between WebPart's during a


request's lifetime and cleared as soon the request has been served.

The Communication Side


In this chapter, we are going to take one use case and improve the way we log.

Rest of the use cases are left as exercises for you to play with!

Let's improve the log of publishing tweet failure

// src/FsTweet.Web/Wall.fs
// ...
let onPublishTweetFailure (err : PublishTweetError) =
match err with
| NotifyTweetError (tweetId, ex) ->
printfn "%A" ex
onPublishTweetSuccess tweetId
| CreateTweetError ex ->
printfn "%A" ex
JSON.internalError
// ...

The first thing that we need is, for which user this error has occurred.

Let's add a new parameter user of type User in the onPublishTweetFailure function
and the pass the user when this function get called

266
Chapter 22 - Adding Logs using Logary

- let onPublishTweetFailure (err : PublishTweetError) =


+ let onPublishTweetFailure (user : User) (err : PublishTweetError) =
...

let handleNewTweet publishTweet (user : User) ctx = async {


...
- |> AR.either onPublishTweetSuccess onPublishTweetFailure
+ |> AR.either onPublishTweetSuccess (onPublishTweetFailure user)
...
}

The Message is a core data model in Logary, which is the smallest unit you can log.
We can make use of this data model to communicate what went wrong.

Let's rewrite the onPublishTweetFailure function as below

// src/FsTweet.Web/Wall.fs
// ...
module Suave =
// ...
open Logary
open Suave.Writers
// ...

let onPublishTweetFailure (user : User) (err : PublishTweetError) =


let (UserId userId) = user.UserId

let msg =
Message.event Error "Tweet Notification Error"
|> Message.setField "userId" userId

match err with


| NotifyTweetError (tweetId, ex) ->
let (TweetId tId) = tweetId
msg // Message
|> Message.addExn ex // Message
|> Message.setField "tweetId" tId // Message
|> setUserData "err" // WebPart
>=> onPublishTweetSuccess tweetId // WebPart
| CreateTweetError ex ->
msg
|> Message.addExn ex // Message
|> setUserData "err" // WebPart
>=> JSON.internalError // WebPart

267
Chapter 22 - Adding Logs using Logary

We are creating a Logary Message of type Event with the name Tweet Notification

Error and set the extra fields using the Message.setField function.

We are also adding the actual exception to the Message using the Message.addExn .
Finally, we save the Message using the setUserData function from Suave's Writers
module.

Now we have captured all the required information on the business side.

Fixing Logary and Chiron Type Conflict


Both Logary and Chiron has the types String and Object . So, if we opened the
Logary namespace after that of Chiron, F# compiler will treat these types are from
the Logary library.

open Chiron
// ...
open Logary

So, we have to change the onPublishTweetSuccess function as below to let the


compiler know that we are using these types from the Chiron library.

let onPublishTweetSuccess (TweetId id) =


- ["id", String (id.ToString())]
+ ["id", Json.String (id.ToString())]
|> Map.ofList
- |> Object
+ |> Json.Object
|> Json.ok

The Action Side


The Action side of logging involves, initializing Logary's logger during the application
bootstrap and log using it if there is an error in the request pipeline.

Initializing Logary
A remarkable feature of Logary is its ability to support multiple targets for the log. We
can configure it to write the log on Console, RabbitMQ, LogStash and much more.

268
Chapter 22 - Adding Logs using Logary

In our case, we are going to the simpler option, Console.

// src/FsTweet.Web/FsTweet.Web.fs
// ...
open Logary.Configuration
open Logary
open Logary.Targets

// ...

let main argv =


// ...

// LogaryConf -> LogaryConf


let target =
withTarget (Console.create Console.empty "console")

// LogaryConf -> LogaryConf


let rule =
withRule (Rule.createForTarget "console")

// LogaryConf -> LogaryConf


let logaryConf =
target >> rule

// ...

Target specifies where to log. We are using the Console.create factory function
from the Logary.Targets module to generate the Console target. The last
argument "console" is the name of the target which can be any arbitrary string.

The Rule specifies when to log. Here we are defining to log for all the cases.
(We can configure it to log only Fatal or Error alone)

The logaryConf composes the target and the rule into single configuration using
the function composition operator.

The next step is initializing the logger using this configuration.

269
Chapter 22 - Adding Logs using Logary

// src/FsTweet.Web/FsTweet.Web.fs
// ...
open Hopac

// ...

let main argv =


// ...

use logary =
withLogaryManager "FsTweet.Web" logaryConf |> run

let logger =
logary.getLogger (PointName [|"Suave"|])

// ...

Logary uses Hopac's Job with Actor model behind the scenes to log the data in the
Targets without the blocking the caller. You can think of this as a lightweight Thread
running parallel along with the main program. If there is anything to log, we just need
to give it this Hopac Job, and we can move on without waiting for it to complete.

Here we are initializing the logaryManager with a name and the configuration and
asking it to run parallel.

Then we get a logger instance by providing the PointName, a location where you
send the log message from.

Wiring Logary With Suave


The final piece that we need to work on is to check is there any error in the request
pipeline and log it using the logger (that we just created) if we found one.

270
Chapter 22 - Adding Logs using Logary

// src/FsTweet.Web/FsTweet.Web.fs
// ...

// HttpContext -> string -> 'value option


let readUserState ctx key : 'value option =
ctx.userState
|> Map.tryFind key
|> Option.map (fun x -> x :?> 'value)

// Logger -> WebPart


let logIfError (logger : Logger) ctx =
readUserState ctx "err" // Message option
|> Option.iter logger.logSimple // unit
succeed // WebPart

let main argv =


// ...

In the readUserState function, we are trying to find an obj with the provided key in
the userState property of HttpContext . If the obj does exist, we are downcasting it
to a generic type 'value .

The logIfError function takes an instance of Logger and uses readUserState

function to find the error log Message and log it using the logSimple function from
Logary. The succeed is an in-built WebPart from Suave

The last step is wiring this logIfError function with the request pipeline

let main argv =


...
let logger = ...
...
let app = ...
...
- startWebServer serverConfig app

+ let appWithLogger =
+ app >=> context (logIfError logger)

+ startWebServer serverConfig appWithLogger

271
Chapter 22 - Adding Logs using Logary

Awesome. The action to perform the actual logging is entirely de-coupled!

Now if we run the application, and post a tweet with internet connection down, we
get the following log

E 2017-11-11T16:57:30.7999800+00:00: Tweet Notification Error [Suave]


errors =>
-
hResult => -2146233088
message => "Error: NameResolutionFailure"
source => "StreamNet"
stackTrace => " at Stream.StreamException.FromResponse (RestSharp.IRestResponse
response) [0x00075] in <5158106fb22e4063ad9c9f74906b6f9e>:0
at Stream.StreamFeed+<AddActivity>d__22.MoveNext () [0x00139] in <5158106fb22e4063ad
9c9f74906b6f9e>:0 "
targetSite => "Stream.StreamException FromResponse(RestSharp.IRestResponse)"
type => "Stream.StreamException"
tweetId => "4d1243a4-9098-46c0-94c9-f780fe10bd4c"
userId => 22

Better and actionable log, isn't it?

Summary
We just scratched the surface of the Logary library in this chapter, and we can make
the logs even more robust by leveraging other features from the Logary's kitty.

Apart from Logary, An another take away is how we separated the communication
and action aspects of logging. This separation enabled us to perform the logging
outside of the business domain (at the edge of the application boundary), and we
didn't pass the logger as a dependency from the main function to the downstream
webpart functions.

The source code associated with this chapter is available on GitHub

272
Chapter 23 - Wrapping Up

Thank you for joining me in the quest of developing a real-world application using
functional programming principles in F#. I believe it has added value to you.

Let's have a quick recap of what we have done so far and discuss the journey ahead
before we wrap up.

Code Organization
Here is the architectural (colorful!) diagram of our FsTweet Application.

The black ovals represent the business use cases and the red circles at the top
represent the common abstractions.

It is an opinionated approach based on the software development principle, high


cohesion, and loose coupling.

Organizing the code around business use cases give us a clarity while trying to
understand the system.

273
Chapter 23 - Wrapping Up

The code is more often read than written.

The Composition Root


We used the Composition Root Pattern to glue the different modules together.

Where should we compose object graphs?

As close as possible to the application's entry point.

A Composition Root is a (preferably) unique location in an application where


modules are composed together - Mark Seemann

The Composition Root pattern along with dependency injection through the partial
application provided us the foundation for our application.

In FsTweet, the main function is the composition root for the entire application

274
Chapter 23 - Wrapping Up

Then for each business use case, the presentation layer Suave's webpart function is
the composition root.

275
Chapter 23 - Wrapping Up

There are other patterns like Free Monad and Reader Monad to solve this differently.
For our use case, the approach that we used helped us to get the job done without
any complexities.

Being said this, the context always wins. For the application that we took, it made
sense. The Free Monad and Reader Monad approaches suit well for particular kind
of problems. Hence, my recommendation would be, learn all the approaches, pick
the one that suits your project and keeps it simple.

I encourage you to try Free Monad and Reader Monad approaches with the FsTweet
codebase and do let me know once you are done. It'd be an excellent resource for
the entire fsharp community!

The Journey Ahead


We can modify/extend/play the FsTweet application like

Replacing Suave with Freya or Girafee or ASP.NET Core

Replacing SQLProvider with Rezoom.SQL or Entity Framework

Replacing GetStream.io with F# Agents or Akka.Net with Event Source

Features like suggesting whom to follow (Machine Learning!), highlights,


hashtag-based searching, unfollow and much more. You can even change the
character limit to 280 characters!

Do drop a note when you make any of these or something else that you found fun
and meaningful.

Once again thanks for joining me :)

There are two ways of spreading light: to be the candle or the mirror that
reflects it. - Edith Wharton

276

You might also like