United Kingdom: +44 (0)208 088 8978

WebSockets with SAFE Stack

We're hiring Software Developers

Click here to find out more

SAFE Stack already has support for push-based messaging through websockets via the Elmish Bridge component. However, if you're looking for something a little more lightweight - or something where you want to expose websockets and are not in control of the client, Saturn's Channels feature may be what you're looking for.

We've recently published a simple example of working with channels which is available here. It illustrates how to create a simple application that supports push-based messaging between client and server:

Whilst Saturn's support for Channels is relatively new, it has a set of useful abstractions that simplify the process. Importantly, it does not couple you to an F# / Fable front-end - it emits raw JSON messages that can easily be consumed by any client (although in the sample above, we've adopted an approach that takes advantage of F# types on both client and server).

Taking full advantage of web sockets with SAFE

If you have control of the client and server with a full SAFE application, you can use the full richness of F#'s type system and seamlessly integrate websocket messages from the server in your client Elmish applications:

  1. Create a set of messages for your web socket push messages:
// Add more messages that can go from server -> client here...
type WebSocketServerMessage =
    | BroadcastMessage of {| Time : DateTime; Message : string |}
  1. Send your messages to clients by serialising them using e.g. the Thoth library:
/// Sends a message to all connected clients.
let broadcastMessage (hub:Channels.ISocketHub) (payload:WebSocketServerMessage) = task {
    let payload = Encode.Auto.toString(0, payload)
    do! hub.SendMessageToClients "/channel" "" payload
}
  1. On the client, integrate your server-side messages with your existing Elmish message type:
type Msg =
    | ReceivedFromServer of WebSocketServerMessage
    // other message types elided...
  1. Next, set up an Elmish subscription that handles websocket messages into the Elmish pipeline:
let sub dispatch =
    /// Handles push messages from the server and relays them into Elmish messages.
    let onWebSocketMessage (msg:MessageEvent) =
        let msg = msg.data |> decode<{| Payload : string |}>
        msg.Payload
        |> decode<WebSocketServerMessage>
        |> ReceivedFromServer
        |> dispatch

    // Naive web socket connection management
    let ws = WebSocket.Create "ws://localhost:8085/channel"
    ws.onmessage <- onWebSocketMessage

Notice how once we've deserialized the message, we dispatch it into the standard Elmish message loop. Once you've set up this plumbing, standard Elmish message handling applies - so whether a message comes from a websocket or internally within the client application, you can treat them the same:

let update msg model : Model * Cmd<Msg> =
    match msg with
    | ReceivedFromServer (BroadcastMessage message) ->
        { model with ReceivedMessages = message :: model.ReceivedMessages }, Cmd.none

Summary

Saturn Channels are a standards-compliant way of utilising websockets from F#. If you're writing SAFE applications, these messages can also be rapidly integrated into your existing message loop without rewriting the entire application, thanks to Elmish subscriptions. We're looking forward to seeing further enhancements to the Channels feature in Saturn, such as improved state management and the ability create groupings of connections.

Have (fun _ -> ())!

Isaac