Type-safe communication layer (RPC-style) for F# featuring Fable and .NET Apps
Clone or download
Zaid-Ajaj Merge pull request #84 from mvsmal/docs-typo
Fixed typos on Error-handling page
Latest commit 9ddec44 Oct 18, 2018
Permalink
Failed to load latest commit information.
.paket Update paket.lock, bump Client v2 and publish Sep 21, 2018
.vscode Initial WORKING dotnet client proxy using quoutations May 3, 2018
ClientV2Tests More array tests without 'do', update dotnet-fable to 2.0.4, #83 Oct 12, 2018
Fable.Remoting.AspNetCore Moaar integration tests, support for recursive types, prepate for pub… Aug 18, 2018
Fable.Remoting.Client Publish Client 3.x for Fable 1 support Oct 7, 2018
Fable.Remoting.ClientV2 Update client v2 with parsimmon and build steps, publish Oct 11, 2018
Fable.Remoting.DotnetClient Add docs for C# support, publish dotnet client Jul 25, 2018
Fable.Remoting.Giraffe.Tests Fix giraffe typo and match versions Jul 9, 2018
Fable.Remoting.Giraffe Moaar integration tests, support for recursive types, prepate for pub… Aug 18, 2018
Fable.Remoting.IntegrationTests Update Suave to 2.5.0 and publish new Suave remoting adapter Oct 13, 2018
Fable.Remoting.Json.Tests Moaar integration tests, support for recursive types, prepate for pub… Aug 18, 2018
Fable.Remoting.Json Moaar integration tests, support for recursive types, prepate for pub… Aug 18, 2018
Fable.Remoting.Server.Tests fix docs authentication example Jul 16, 2018
Fable.Remoting.Server Moaar integration tests, support for recursive types, prepate for pub… Aug 18, 2018
Fable.Remoting.Suave.Tests Rewrite suave adapter: the definition of clean Jul 6, 2018
Fable.Remoting.Suave Update Suave to 2.5.0 and publish new Suave remoting adapter Oct 13, 2018
UITests Move Selenium to main group Oct 13, 2018
documentation Fixed typos on Error-handling page Oct 18, 2018
.gitattributes Remove unnecessary Client.Tests because we have integration tests Jul 24, 2018
.gitignore remove generated docs website Mar 10, 2018
.travis.yml Add travis step to run tests from Client V2 Aug 16, 2018
LICENSE Initial commit Mar 19, 2017
Nuget.Config Add solution-wide nuget config Dec 20, 2017
README.md Update syntax for SAFE template Oct 9, 2018
build.cmd Saturn version Feb 8, 2018
build.fsx Add publish target for Client v2 Aug 18, 2018
build.sh Ce based api (#21) Mar 8, 2018
paket.dependencies Move Selenium to main group Oct 13, 2018
paket.lock Update Suave to 2.5.0 and publish new Suave remoting adapter Oct 13, 2018

README.md

Fable.Remoting

Build Status

Full Documentation

In-depth Introduction (Blog)

Fable.Remoting is a library that enables type-safe client-server communication (RPC) for Fable and .NET Apps. It abstracts away http and lets you think of your client-server interactions only in terms of pure functions and being only a part of the webserver.

The library runs everywhere on the backend: As Suave WebPart, as Giraffe/Saturn HttpHandler or any other framework as Asp.net core middleware. On the client you can use Fable or .NET.

Quick Start

Use the SAFE Template where Fable.Remoting is a scaffolding option:

# install the template
dotnet new -i SAFE.Template

# scaffold a new Fable/Saturn project with Fable.Remoting
dotnet new SAFE --communication remoting

# Or use Giraffe as your server
dotnet new SAFE --server giraffe --communication remoting

# Or use Suave as your server
dotnet new SAFE --server suave --communication remoting

Available Packages:

Library Version
Fable.Remoting.Client Nuget
Fable.Remoting.Suave Nuget
Fable.Remoting.Giraffe Nuget
Fable.Remoting.AspNetCore Nuget
Fable.Remoting.DotnetClient Nuget

Scaffold from scratch - Suave

Create a new F# console app:

dotnet new console -lang F#

Define the types you want to share between client and server:

// SharedTypes.fs
module SharedTypes

type Student = {
    Name : string
    Age : int
}

// Shared specs between Server and Client
type IStudentApi = {
    studentByName : string -> Async<Student option>
    allStudents : Async<list<Student>>
}

The type IStudentApi is very important, this is the specification of the protocol between your server and client. Fable.Remoting expects such type to only have functions returning Async on the final result:

Async<A>
A -> Async<B>
A -> B -> Async<C>
// etc...

Try to put such types in seperate files to reference these files later from the Client

Then provide an implementation for IStudentApi on the server:

open SharedTypes

let getStudents() = async {
    return [
        { Name = "Mike";  Age = 23; }
        { Name = "John";  Age = 22; }
        { Name = "Diana"; Age = 22; }
    ]
}

let findStudentByName name = async {
    let! students = getStudents() 
    let student = List.tryFind (fun student -> student.Name = name) students
    return student 
}

let studentApi : IStudentApi = {
    studentByName = findStudentByName
    allStudents = getStudents() 
}

Now that we have the implementation studentApi, you can expose it as a web service from different web frameworks. We start with Suave

Install the library from Nuget using Paket:

paket add Fable.Remoting.Suave --project /path/to/Project.fsproj

Create a WebPart from the value studentApi

open Suave
open Fable.Remotion.Server
open Fable.Remoting.Suave

let webApp : WebPart = 
    Remoting.createApi()
    |> Remoting.fromValue studentApi
    |> Remoting.buildWebPart 

// start the web server
startWebServer defaultConfig webApp

Yes, it is that simple. You can think of the webApp value as if it was the following in pseudo-code:

let webApp =
 choose [
  POST
   >=> path "/IStudentApi/studentByName"
   >=> (* deserialize request body (from json) *)
   >=> (* invoke studentApi.getStudentByName with the deserialized input *)
   >=> (* give client the output back serialized (to json) *)

 // other routes
 ]

You can enable diagnostic logging from Fable.Remoting.Server (recommended) to see how the library is doing it's magic behind the scenes :)

let webApp = 
    Remoting.createApi()
    |> Remoting.fromValue studentApi
    |> Remoting.withDiagnosticsLogger (printfn "%s")
    |> Remoting.buildWebPart 

AspNetCore Middleware

Install the package from Nuget using paket

paket add Fable.Remoting.AspNetCore --project /path/to/Project.fsproj

Now you can configure your remote handler as AspNetCore middleware

let webApp = 
    Remoting.createApi()
    |> Remoting.fromValue studentApi

let configureApp (app : IApplicationBuilder) =
    // Add Remoting handler to the ASP.NET Core pipeline
    app.UseRemoting webApp

[<EntryPoint>]
let main _ =
    WebHostBuilder()
        .UseKestrel()
        .Configure(Action<IApplicationBuilder> configureApp)
        .Build()
        .Run()
    0

Giraffe

You can follow the Suave part up to the library installation, where it will become:

paket add Fable.Remoting.Giraffe --project /path/to/Project.fsproj

Now instead of a WebPart, by opening the Fable.Remoting.Giraffe namespace, you will get a HttpHandler from the value server:

open Giraffe
open Fable.Remoting.Server
open Fable.Remoting.Giraffe

let webApp : HttpHandler = 
    Remoting.createApi()
    |> Remoting.fromValue studentApi
    |> Remoting.buildHttpHandler 

let configureApp (app : IApplicationBuilder) =
    // Add Giraffe to the ASP.NET Core pipeline
    app.UseGiraffe webApp

let configureServices (services : IServiceCollection) =
    // Add Giraffe dependencies
    services.AddGiraffe() |> ignore

[<EntryPoint>]
let main _ =
    WebHostBuilder()
        .UseKestrel()
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        .Build()
        .Run()
    0

Saturn

You can use the same webApp generated by the Giraffe library.

open Saturn
open Fable.Remoting.Server
open Fable.Remoting.Giraffe

let webApp : HttpHandler = 
    Remoting.createApi()
    |> Remoting.fromValue studentApi
    |> Remoting.buildHttpHandler 

let app = application {
    url "http://127.0.0.1:8083/"
    router webApp
}

run app

Fable Client

Install Fable.Remoting.Client from nuget using Paket:

paket add Fable.Remoting.Client --project /path/to/Project.fsproj

Reference the shared types to your client project

<Compile Include="path/to/SharedTypes.fs" />

Start using the library:

open Fable.Remoting.Client
open SharedTypes

// studentApi : IStudentApi
let studentApi =
    Remoting.createApi()
    |> Remoting.buildProxy<IStudentApi>

async {
  // students : Student[]
  let! students = studentApi.allStudents()
  for student in students do
    // student : Student
    printfn "Student %s is %d years old" student.Name student.Age
}
|> Async.StartImmediate

Finally, when you are using webpack-dev-server, you have to change the config from this:

devServer: {
  contentBase: resolve('./public'),
  port: 8080
}

to this:

devServer: {
  contentBase: resolve('./public'),
  port: 8080,
  proxy: {
    '/*': { // tell webpack-dev-server to re-route all requests from client to the server
      target: "http://localhost:8083",// assuming the suave server is hosted op port 8083
      changeOrigin: true
    }
}

That's it!

Adding a new route

  • Add another record field function to IStudentApi
  • Implement that function
  • Restart server

Done! You can now use that function from the client too.

See the following article if you are interested in how this library is implemented (a bit outdated but gives you an overview of the mechanism) Statically Typed Client-Server Communication with F#: Proof of Concept