United Kingdom: +44 (0)208 088 8978

Why we love SAFE Stack – Fable.Remoting

In the first instalment of our "why we love SAFE Stack series", Matt discusses what's great about Fable.Remoting.

We're hiring Software Developers

Click here to find out more

I've taken the slightly unusual decision to kick off this series about SAFE Stack in the middle: Fable.Remoting is in the middle of both the stack and the flow of data. Despite that, I think that it's a particularly good component to start the series with because it's one of SAFE Stack's killer features. There are a few reasons why I think this:

  • You don't have to worry about serialization, which can be a big burden in other web app stacks.
  • Fable.Remoting just works™.
  • It enables you to use one language across the whole stack.
  • It enables IDE navigation between the front end and back end.

Let's dive in.

No serialization headaches

When it comes to full-stack web app development, the vast majority of apps use different languages for the frontend and backend. Usually the mix is JavaScript/TypeScript and something else. Consequently, the vast majority of web app developers must think about serialization and deserialization for data sent between their frontend and backend.

As Einar W. Høst explains well in his post, "On the complexity of JSON serialization", this is often a deceptively complex part of an application's codebase.

on every single software project or product I’ve worked on, JSON serialization has been a endless source of pain and bugs (sic.)

I agree with Einar's assessment of the problem (using libraries that translate directly between a rich domain model and JSON documents) and his suggested solution (use an intermediate type in your code which exactly matches the JSON structure and code the mapping to the rich domain models by hand). If I were working with a stack that required sticking to an agreed JSON schema, this would be my preferred approach.

It's worth noting however that there's an implicit assumption in the above argument: that the JSON representation is important. This is usually true because the JSON forms a contract, either between an app's frontend and backend, or between an API and its (sometimes unknown) clients; if one side starts sending JSON in breach of this contract, things will go wrong!

Things are different in SAFE Stack. Because the same language is used on the frontend and the backend, we can work at a higher level of abstraction for the contract between them: F# types rather than JSON.

This is where Fable.Remoting comes in. I can tell it to send an F# data structure from the frontend to the backend (or vice versa) and it will take care of serialization and deserializtion and guarantee that what is hydrated on the other end of the wire is exactly what you started off with.

It just works

Fable.Remoting just works. I've been using SAFE Stack professionally for 3 years now and I've barely had to think about Fable.Remoting. It's an incredibly simple abstraction that behaves exactly as you'd expect but hides a lot of potential complexity. Absolutely wonderful 😌 Below is an example from the SAFE Template.

The Shared project has an API definition defined as an F# record of functions:

type ITodosApi = {
    getTodos: unit -> Async<Todo list>
    addTodo: Todo -> Async<Todo>
}

The server creates an API record delegating to other functions:

let todosApi ctx = {
    getTodos = fun () -> async { return Storage.todos |> List.ofSeq }
    addTodo =
        fun todo -> async {
            return
                match Storage.addTodo todo with
                | Ok() -> todo
                | Error e -> failwith e
        }
}

let webApp = Api.make todosApi

How routing is hooked up will be covered in a later post, but be assured that it's simple!

The client creates a proxy for the API and uses it to send requests and receive responses (more detail in a later post):

let todosApi = Api.makeProxy<ITodosApi> ()
...
let update msg model =
    match msg with
    ...
    | LoadTodos msg ->
        match msg with
        | Start() ->
            let loadTodosCmd = Cmd.OfAsync.perform todosApi.getTodos () (Finished >> LoadTodos)

            { model with Todos = Loading }, loadTodosCmd
        | Finished todos -> { model with Todos = Loaded todos }, Cmd.none

No JSON serialization code or attributes in sight, with none of the drawbacks Einar identified from avoiding doing it by hand!

Enables one language for the whole stack

As mentioned above, most web app developers work with different languages for their frontend and backend code. Not only does this mean having to worry about how your types get translated to/from JSON, but also having duplicate type definitions for representing the same type (one per language) and context switching between different languages' syntax and behaviour.

In contrast, when you have one language for the whole stack, you can share type definitions between frontend and backend and there's no context switching caused by the language. A further bonus is that you can share functions between the frontend and backend, for example to validate form data on the client and on the server.

The SAFE Template has a shared Todo.isValid function used on the client and the server:

module Todo =
    let isValid (description: string) =
        String.IsNullOrWhiteSpace description |> not

IDE navigation between front end and back end

Imagine the situation where you want to quickly get from the code that defines a button on the client to the code on the server that runs when the button is pressed. In applications where the client and server don't share types, you'd likely be able to navigate quickly to the client code that calls an API endpoint, then have to do something like a textual search in your codebase for an endpoint name.

When using Fable.Remoting, things are much simpler because the client and server share types. For example, starting at an API invocation on the client...

Cmd.OfAsync.perform todosApi.addTodo todo (Finished >> SaveTodo)

I can use my IDE's "Go to Declaration" feature to be taken to the field on the API record...

addTodo: Todo -> Async<Todo>

Then use my IDE's "Show Usages of Symbol" popup to quickly get to the part of the server code that defines the API...

addTodo =
    fun todo -> async {
        return
            match Storage.addTodo todo with
            | Ok() -> todo
            | Error e -> failwith e
    }

This really removes friction, allowing me to get all the way from the declaration of a button in client code to the SQL that's run in server code using only my IDE's code navigation features.

Summary

For me, Fable.Remoting has been a major contributor to SAFE Stack feeling like a breath of fresh air when it comes to web app development. After reading this post, I'm sure that you can see why 💡 Thanks Zaid for writing such a fabulous library!

Next in this series, we'll be looking at Giraffe and Saturn, and how they revolutionize the ASP.NET Core development experience.