Introducing Fable.Remoting: Automated Type-Safe Client-Server Communication for Fable Apps

Zaid Ajaj
10 min readMay 31, 2017

A while ago, I started experiementing with Fable’s reflection (a.k.a. type introspection) capabilities. I was interested in applying reflection to allow type-safe communacation for Fable client apps via HTTP. The idea is that the client F# app could call server code, also written in F#, just by calling a function on the server. Both server and client would only be sharing the definition of that function. Behind the scenes, the client and server are communicating via HTTP. The library Fable.Remoting would take care of serialization, deserialization, both on server and client. Also, it takes care of generating the routes on the server and the mapping from these routes to actual server code. Sources are hosted on Github here. The README in the repo is a TL;DR version of this article.

“Show me the code!”

In summary, the server side:

The client side:

When I was experiementing, I came up with a proof-of-concept implementation. That code supported only working with primitive types (string, bool, int etc…) but after improvements to Fable’s reflection support, that proof-of-concept evolved to support almost any complex type (records, unions, generic types, lists of generic types etc.) and became this library.

In this article, I invite you on a little journey to build a minimal client-server app with Suave on the server and Fable on the client, both using the Fable.Remoting library.

Project structure

Client-Server apps consist of two projects at a minimum. One is the server and another is the client. We also need to share files containing the types and possibly some logic between client and server. It doesn’t matter in which project you put your shared files as long as you reference them from the other project. The server uses that server-specific library of Fable.Remoting, for a Suave server, we will use Fable.Remoting.Suave .

Currently, Suave is only sever type that is supported but I have plans of supporting other .net web frameworks like Nancy or Freya on in the short term. It could also be interesting to support Node specific server libraries like Express.

Update (23–10–2017):

  • Fable.Remoting.Suave now works on netcore2.0
  • Giraffe is now also supported, package Fable.Remoting.Giraffe

On the client Fable app, we will be using Fable.Remoting.Client to create the typed proxies that call server code and return typed results.

Building the Server side

.NET Core 2.0

Start by scaffolding a new console application in F#

dotnet new console -lang F#

then install the library from Nuget targeting .NET Core:

dotnet add package Fable.Remoting.Suave

Because the library already depends on Suave and Newtonsoft.Json, you don’t have to intall anything else and you are good to go

That’s it!

Sharing code

Before we use the library we need some types to share between client and server. I created a file called SharedTypes.fs in the server project:

Notice carefully the definition of IServer . This record type is a specification of what the server does. Also notice the shape of the record, only functions of type A' -> Async<B'> . This is what you will use whenever you are using Fable.Remoting. Currently only one paramter functions are supported and if need more parameters, you can use a single record with multiple fields as your input.

On the server, an implementation (instance) of IServer must be provided. On the client, a proxy will be created of type IServer. When a method (record field, technically) is called on the client, the proxy will delegate the work to the server and call the actual implementation to come back with the results.

Implementing 'IServer'

This should be straightforward:

It doesn’t matter where you put that code in your server project, for now I am putting everything inside Program.fs.

Using the FableSuaveAdapter module from Fable.Remoting.Suave you can create a Suave WebPart from the value server . In the next piece of code, I am enabling logging for us to see what is happening, then create the WebPart (second line) and the last line just runs the Suave server with the given WebPart.

Mapping the routes to a single WebPart is great because it makes it easy to add and compose other WebParts in your application:

So it is easy to build your Suave server they way you want it and have some parts of it be automatic routes for a Fable client to use. For now, our minimal app is just that WebPart we created earlier.

Full Program.fs file so far:

Run the app to see what the logger has to say:

By default, Fable.Remoting will map a record function named y of record type named x to a POST route of path /{x}/{y} .

If you want to generate the route paths on your own, it is quite simple, you can use webPartWithBuilderFor instead of webPartFor . Say you want routes prefixed with “/api/” (i.e. /api/{x}/{y} ), here is how you do it:

Now if you restart the server, you should see this:

For this app, I will stick with the routes prefixed with /api/, in a moment you will see why this is convenient for the client app too.

The Suave server part of the app is now finished and handles POST requests to one of these routes. Before I start with the client app, I want to test the server real quick just to show you the magic “behind the scenes”, for that I will use Postman:

Here I am sending a POST request to /api/IServer/getAllStudents with an empty body content , this is what I get back:

I get an array of students back (copied from Postman’s output pane), serialized to JSON. Also take a look at your console:

Lets test the function getStudentByName by sending a string as the input:

Because this method actually has input type, namely a string and not just unit, the body content of the request gets deserialized into the apropriate type of the input parameter of the function, here is the log from the console:

If you sent a string of a student’s name that did not exist in the list, you would get “null” back and that is expected because None is serialized to null as a convention by Fable.

Then the last function, getStudentSubjects accepts a Student as input and returns a string array containing the subjects of that input Student:

Then see the result:

Client App

Now that the server is working we turn to the client, for this app I will be using Fable 1.0-beta introduced here with the Paket intergration. First things first, I want to get the default Fable app running before we do any modifications. The default app is the app you get when you intall the template.

Setting up the client app

Start by installing the simple Fable template, if you already have the template installed, it will get updated:

dotnet new --install "Fable.Template::*"

Now, in the same directory as your solution App.sln file, create a new directory called Client:

Start your terminal and cd your way to the Client directory, after that create a new simple Fable app from the template:

cd Client
dotnet new fable

Install the npm dependencies using yarn:

yarn install

Install Fable and dotnet dependencies (this will actually call Paket to restore the packages):

dotnet restore

At this point, you can go take a look at your src/App.fs file where you should have tooltips and auto-completion working. I use Visual Studio Code with the awesome Ionide extension for Fable app developement:

Start the Fable app in developement mode:

dotnet fable npm-run start

Navigate to the http://localhost:8080 to see you app running:

Great, the Fable template is running, it is a simple canvas app that we now can delete from src/App.fs and only leave this:

Save the file and the app will be automatically recomiled and your browser automatically refreshed thanks to webpack developement server:

Installing a library on the client

Time to install Fable.Remoting.Client, from the terminal within the Client directory, run:

".paket/paket.exe" add nuget Fable.Remoting.Client project Client.fsproj

This tells paket to install a nuget package Fable.Remoting.Client and to reference it from project Client.fsproj.

Make sure Fable.Core and dotnet-fable have matching versions and that they are both 1.0.7 or newer

Restore the references again

dotnet restore --no-cache

Reference the shared types from server

Inside your Client.fsproj you can reference the SharedTypes.fs file from the server directory to your compilation list, my Client.fsproj looks now like this:

Notice the part:

<Compile Include="../App/SharedTypes.fs" />

That is how you reference the the file used in the server or other files, making the types available both on client and server.

Making Http Request from Client to Server

This section applies generally when working with client-server apps with a webpack developement server and is not specific when using Fable.Remoting

When working with Fable, we obviously want to use webpack-dev-server, it really makes for a very convenient workflow and a fast feedback loop from your client app. When I started using it, I couldn’t look back to the way we used to do things by manual recompilation and refreshing the browser every time. But there is a catch when you are building a client app with webpack-dev-server and a server app at the same time because both servers (Sauve and Webpack development server) will run on different ports. The Suave server runs by default on port 8083:

http://localhost:8083

The webpack-dev-server by default runs on port 8080:

http://localhost:8080

When making HTTP requests, you can’t just send a request to /api/IServer/getAllStudents because that request is sent to the webpack-dev-server and not to the Suave server. You could try to send requests directly to Suave from webpack-dev-server by fully qualifing the url, i.e. calling http://localhost:8083/api/IServer/getAllStudents but then you get problems of cross-origin requests. Also, you would need to change your code when building for production. What do we do then?

Webpack proxy to the rescue!

This problem is known to javascript developers and they have already solved it. The solution is to make the webpack-dev-server a proxy server that re-routes the requests to a different host as if the requests were originating from there. In our example and this is the way I recommend to use: configure it in a way such that it only re-routes requests when they start with /api/ because you don’t want to re-route GET requests to static assets like stylesheets or javascript files. Here is how do it: in your webpack.config.js you have this property:

You need to add proxy propery instructing webpack-dev-server to proxy the request to another host when they start with /api/ :

This is also the reason why we created routes on the server prefixed with /api/ .

Creating the IServer proxy from the client

Now that we configured our webpack.config.js, it is time to call the server from the client. It is very simple, you use the Proxy module from Fable.Remoting.Client:

That is it! because we generating custom routes on the server, we have to use the same route builder on the client as well. From that point, just use the server value:

Will result in:

Hell yeah! it worked 😄. To convince yourself that the code actually made a trip to the server, see your Suave app console log:

And everything is type-safe:

Inspiration

This library is inspired by the WebSharper’s Remoting feature. I was amazed the first time I saw that something like that was possible in F# and when I learned more about Fable, I decided to go ahead and try to implement it. Although it is worth noting that WebSharper a very different mechanism for sharing types, namely using attribute annotations like [<Remote>] and [<Rpc>] to define what is callable from client code. Fable.Remoting uses a record type that you would share between client and server. For WebSharper, it is a compiler feature but with Fable.Remoting, is it just an opt-in library that uses reflection at run-time.

Special thanks to Alfonso Garcia-Caro , the author of Fable

When I published the proof-of-concept article, not only he was kind to review it for correctness but he also asked what reflection features I needed from Fable to improve the reflection support and consequenctly to improve the library. The needed features were implemented in no time (see here, and here), thanks a lot Alfonso 🙏!

And Ofcourse thanks to Sergey Tihon for the awesome Fable logo above in the article.

The End

We now come to the end of this article, I hope you learnt something today and maybe you will use Fable.Remoting in your next project. If you liked this article, hit that 💚 button and share! For any question, I am usaully hanging out on Fable’s gitter channel.

--

--