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