Jonathan Banashek


A brief peek at Full-Stack F#

Why Full-Stack Applications

I don't remember when the term 'Isomorphic Javascript' started getting tossed around, but I remember a few developers telling me about the MEAN stack and how things like a shared language on the front-end and the server was "the future". They weren't technically wrong. There are a lot of shops running node on the backend and react/angular/vue on the frontend, but I didn't seem to understand the benefit that it would bring. I think that having a functional, type-safe SPA that will always be in sync with it's backend adds enough to the previous argument to make it worthwhile.

Share Everything

Because the base API surface for Fable is not drastically different from that of F# on mono, many things can be shared between the applications. Is as easy as dumping them into a shared folder and referencing the items within from each project. This allows you to share all kinds of code including ViewModels, Validation Logic, Localization/Internationalization, and probably a lot of other things I can't think of off the top of my head at the moment.

A Quick Example

Source for the following example can be found on my github.

The first thing we do is create our suave and fable-elmish projects. I'd recommend starting either with one of the samples found here, or with the WIP Fable-Suave-Scaffold.

Next we create our shared Types. In this example, we're modeling pokemon.

Types.fs

 1 module SharedTypes.Types
 2 
 3 type PokemonType =
 4     | Normal
 5     | Fire
 6     | Water
 7     | Electric
 8     | Grass
 9     | Ice
10     | Fighting
11     | Poison
12     | Ground
13     | Flying
14     | Psychic
15     | Bug
16     | Rock
17     | Ghost
18     | Dragon
19     | Dark
20     | Steel
21     | Fairy
22 
23 type BriefPokemon = {
24     num : string
25     name : string
26 }
27 
28 type Pokemon = {
29     id : int
30     num : string
31     name : string
32     img : string
33     pokemonType : PokemonType list
34     height : float
35     weight : float
36     weaknesses : PokemonType list
37     nextEvolutions : BriefPokemon list
38     previousEvolutions : BriefPokemon list
39 }

After adding the file to both projects, we can then create the back-end producer and front-end consumer portions.

Server App.fs

17 let app =
18     choose [
19         path "/" >=> Files.browseFileHome "index.html"
20         path "/bundle.js" >=> Files.browseFileHome "bundle.js"
24         path "/pokemon" >=> (OK (JsonConvert.SerializeObject(allPokemon, JsonConverter())))
25             >=> setMimeType "application/json; charset=utf-8"
26         NOT_FOUND "404 - Not found."
27     ]

Client App.fs

 27 let getPokemon (f : string) =
 28   promise {
 29     return! Fable.PowerPack.Fetch.fetchAs<PokeList> ("/pokemon") []
 30   }

Since we share the models, we can do things like match the Pokemon Type using exaustive pattern matching to return the appropriate background color:

 45 let getPokeBGColor t =
 46   match t with
 47   | Normal -> "#CDCDCD"
 48   | Fire -> "#D00006"
 49   | Water -> "#4A82FF"
 50   | Electric -> "#B79D04"
 51   | Grass -> "#549057"
 52   | Ice -> "#73C4CB"
 53   | Fighting -> "#924036"
 54   | Poison -> "#8400DC"
 55   | Ground -> "#803909"
 56   | Flying -> "#80ADFF"
 57   | Psychic -> "#2B0F48"
 58   | Bug -> "#687F35"
 59   | Rock -> "#363636"
 60   | Ghost -> "#6257AD"
 61   | Dragon -> "#818C6B"
 62   | Dark -> "#818C6B"
 63   | Steel -> "#818C6B"
 64   | Fairy -> "#818C6B"

Final Product

animated gif example of the program

linkedin-square github-square