Date: 2023.11.29 | create | fsharp | giraffe | html | tech |
DISCLOSURE: If you buy through affiliate links, I may earn a small commission. (disclosures)
For the past several years I've experimented with different tech stacks with the goal of making building apps more simple and fun. CloudSeed is my best answer so far - an F# web API that can interface with the frontend technology of your choice. This allows most of the app to be built in my favorite language F# while allowing flexibility to use whatever tech you like for your frontend.
The problem is that all apps really need a frontend. So by not supplying a rock-solid default, users must bring / figure out their own. CloudSeed currently defaults to SvelteKit - my current top choice for simple, flexible frontends (I like SvelteKit over React-based projects). But not everyone likes / knows SvelteKit so they either have to integrate their own or learn a new framework. Even if they do like and know SvelteKit, there's still the problem of context switching between the two tech stacks.
Recently I've been reading up on the hype around HTMX and have undergone somewhat of a brainblast exploring its possibilities. It promises all the simplicity and power of server-side rendering with the reactivity of a clientside framework (a la Svelte, React, Vue, etc). This seems like it could solve my problem - allowing me to build 3S (Simple Scalable System) fullstack apps with 90% of the code in F# all while sacrificing little in the way of frontend power.
In this post we're going to explore the first part of this journey - server-side HTML rendering with F# / Giraffe.
Q: How can we build type-safe server-side HTML rendering with F# / Giraffe?
In this post we'll explore an example app built with F# / Giraffe. It renders a simple HTML table displaying a list of data from its database.
This example will show you how to build:
The app itself:
In this post, we'll specifically be diving into the App / UI layers (including important source code). The rest is just there to serve as a "tracer bullet" to prove this works in a fullstack system.
To learn more about the rest of the stack, read:
The full source code of this example app is available:
The first thing we want is an ability to do HTML templates.
Why?
Well we could just use raw string concatenation (and many people do). In most cases this is fine.
BUT there are some edge cases where this could cause unexpected problems like if you try to use a special character like &, <, > etc. These are all reserved characters by the html spec so you'd need to first translate them into a different form (their entity name / number) if you want the browser to render them as text instead of trying to render it as html.
With raw string concatenation you'd just need to remember to parse these special characters into their correct form. This is doable but it seems like a pit of failure. The default is to forget so you need extra work to remember and not fail.
By starting with everything as a template, it's clear we need to do the HTML parsing anyway which takes care of this whole failure case for us (a pit of success). Plus most templating languages give us some nice tools for conditionals, iterations, and value usage so that's a plus.
Picking an HTML Template library
For this example app, I chose to go with Scriban. I liked Scriban because it is:
You can install the package via nuget (Scriban Nuget):
dotnet add package Scriban
Once installed, we can utilize Scriban to parse our html templates and fill it with type-safe data models with a helper function like this:
let buildHtmlTemplateParser<'TTemplateProps>
(template : string)
=
let compiledTemplate = Scriban.Template.Parse(template)
match compiledTemplate.HasErrors with
| true -> raise (System.SystemException($"Failed to parse template {compiledTemplate.Messages}"))
| false -> ()
fun (props : 'TTemplateProps) ->
compiledTemplate.Render(
props,
memberRenamer = fun m -> m.Name)
This will:
We'll use this more in the next section
Now that we have a way to parse and render our HTML strings, let's look at how we can build a page with "components" similar to how you might split up rendering concerns in a clientside framework.
We're basically trying to build a simple table that pulls in data from our DB and renders it. For this example, the data is just a giant list of Sentinels - a model that has a few strings in it.

To render this, we'll build:
Let's start with the SentinelTable. We can build this pretty simply by creating a new function renderSentinelTableComponent that takes in our Sentinel list and returns the HTML string. While the string itself isn't very type-safe, the input Props are - utilizing the full power of the F# type system.
type SentinelTableComponentProps =
{
Sentinels : Sentinel list
}
let renderSentinelTableComponent
: SentinelTableComponentProps -> string
=
let template =
"""
<table>
<tr>
<th>ID</th>
<th>Data</th>
</tr>
{{ for sentinel in Sentinels}}
<tr>
<td>{{ sentinel.id }}</td>
<td>{{ sentinel.data.name }}</td>
</tr>
{{ end }}
</table>
"""
let parser = buildHtmlTemplateParser<SentinelTableComponentProps> template
fun
(props : SentinelTableComponentProps)
->
// HAMY: The template is cached as we know it is static
parser props
In the above code:
Looking at it this way, it's very similar to how you might build a React functional component. But this is all done serverside, right in F#!
Note: In this example, we're caching the parsed template because I wasn't sure how much of a performance impact re-parsing every time would cause. My Scriban benchmarks indicate that re-parsing takes about 0.2 ms which in most UI usecases is negligible so I'd recommend not caching unless you need it.
Next we'll build our main page which will utilize this component to render its table of Sentinel values.
As this is the root page, this is probably where you'd want to do any sorts of db accesses to build up page data the UI components will render. The SentinelCount props it receives comes directly from the F# / Giraffe endpoint we'll look at in the next section. This is similar to the SSR style of SvelteKit and NextJS with server-side data loading.
We are using simple string concatenation to insert the SentinelTableComponent into our HTML body. I think this is pretty simple, easy to read, and safe-ish as we know its doing safe HTML parsing internally but I'd be curious if anyone has suggestions for more type-safe ways to do this.
type MainPageProps =
{
SentinelCount : int
}
let renderMainPageAsync (serviceTree : SentinelServiceTree) (props : MainPageProps) =
async {
let! sentinelResult =
sendGetSentinelsQueryAsync serviceTree { count = props.SentinelCount }
let sentinels =
match sentinelResult with
| Ok s ->
s
|> Seq.toList
| Error s -> raise (System.SystemException("Failed to get Sentinels"))
do!
sendCreateSentinelCommandAsync serviceTree
|> Async.Ignore
let template =
$"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sentinels Example</title>
</head>
<body>
<main>
<h1>Sentinels Table</h1>
</main>
{(renderSentinelTableComponent {Sentinels = sentinels})}
</body>
</html>
"""
let parser = buildHtmlTemplateParser<MainPageProps> template
return parser props
}
In the above code, we:
At this point, we should have a decent idea of how we can build Component-like renderers with type-safe functions. All that's left to do is hook up our Giraffe endpoints to actually render this page.
Explaining the Giraffe web framework and how endpoint routing works is beyond the scope of this article so we won't go into detail. If you want to learn more, check out:
Basically what we've got to do to return this HTML via Giraffe is:
This seems like a lot and I'll be honest it is a bit of a faff to get fully setup with Giraffe routing. But this approach is very flexible when you inevitably need middleware and for the most part can be handled simply once you've built a helper function.
We're going to build the above steps backwards so we can see how everything is defined and flows into the next.
First we build an HTTPHandler for our main page. This is a type registered by Giraffe that takes in the request context and the next function in the call stack (useful for building composable middleware).
let mainPageHttpHandler
(sentinelServiceTree : SentinelServiceTree)
=
fun (next : HttpFunc) (ctx : HttpContext) ->
async {
return!
renderMainPageAsync
sentinelServiceTree
{
SentinelCount = 10
}
}
In the above code we:
Next we're going to tell Giraffe that it needs to return the string we give it as html (not a raw string!). We're going to build a helper function because we don't want to have to write this every time. This helper function is basically a composable middleware that is acting on the request / return values - converting the returned string into an html result Giraffe understands.
let renderView
(handler : HttpFunc -> HttpContext -> Async<string>)
: HttpHandler
=
fun(next : HttpFunc) (ctx : HttpContext) ->
task {
let! result = handler next ctx
return! htmlString result next ctx
}
In this code, we:
htmlStringNow that we've got that handled, we just need to register our endpoint so Giraffe knows to send requests from this url to this handler. Hopefully this will make it clear how all these utilities work together.
let constructEndpoints (serviceTree : SentinelServiceTree) =
[
GET [
route "/sentinels" (
renderView (
MainPageView.mainPageHttpHandler serviceTree
)
)
]
]
Here we:
constructEndpoints which will create a list of endpoints that the root of your app can then use to register with Giraffe
/sentinels which calls our renderView function for the results of our mainPageHttPhandlerThere you have it - server-side rendered HTML with F# / Giraffe, using simple F# functions giving us all the ergonomics and type-safety of the language. I'll be exploring this more on my journey to experiment with reactive server-side UI using tech like HTMX so lmk if you have any Qs and I'll try to answer them next time.
If you liked this post, you might like:
The best way to support my work is to like / comment / share for the algorithm and subscribe for future updates.