with F# and dotnet fsi 🌴
Minimal APIs have gained lots of traction recently in the .NET ecosystem, but what is a Minimal API anyways? Here is a quick definition:
… HTTP APIs with minimal dependencies. They are ideal for microservices and apps that want to include only the minimum files, features, and dependencies in ASP.NET Core. — [1]
Based on the above definition, a novel approach to minimal APIs for .NET can be achieved with F#, taking advantage of its interactive mode or REPL (read evaluate and print loop).
What does it look like in C#?
/*
here is the directory structure.. more on this later
MinimalWebApi
│ appsettings.json
│ Program.cs
│ WebApplication.csproj
*/
// with a .csproj file so within a C# project
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
C# with dotnet interactive can now probably achieve something similar by installing some extra global packages, but F# is naturally equipped with a REPL as a dotnet tool: dotnet fsi
. And is awesome.
F# interactive programming in .fsx
scripts has great IDE support in Ionide, Rider, and VS. If you have ever programmed in Python or Node.js you have already used a REPL.
C# Project File?
MinimalWebApi
│ appsettings.json
│ Program.cs
│ WebApplication.csproj
The usual “minimal APIs” in C# have a minor drawback though, a separate file for dependencies: .csproj
file.
This is not a big deal, but when looking for dependencies, you do “need to know” how it works, and look for this file specifically.
<!-- .csproj file of a C# project -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<!-- plus when referencing actual libraries they will live here -->
</PropertyGroup>
</Project>
Now let’s see how to create a minimal API in F# taking advantage of the interactive mode within a single .fsx
script file.
Note: Other languages and frameworks like Deno in the “Nodejs” ecosystem, have taken a similar approach of small script APIs with direct in-file package reference.
Getting Started with F#
First, you will need a working .NET SDK installed on your machine 🤖, in case you are on Mac like me:
brew install dotnet
Now you can play with your dotnet interactive and learn how to exit the interactive session:
dotnet fsi
> 5;; // a value
val it: int = 5
> let x = 5;; // a value binding
val x: int = 5
> let sumPlusOne firstArg secondArg = firstArg + secondArg + 1;; // a function
val sumPlusOne: firstArg: int -> secondArg: int -> int
> #clear;; // clear screen
> #quit;; // exit the interactive section
Generate Load Scripts for F# Interactive
Thanks to theAngryByrd, who is also the author of the amazing IcedTask library, you can run THIS linked script to generate a folder containing your aspnetcore load-scripts.
dotnet fsi generateAspnetcore.fsx
The #load
directive will then be placed at the top of your single file .fsx
app to load all the required assemblies to run aspnetcore in the interactive environment.
If needed you can exclude this generated folder using a .gitignore
and regenerate it each time you run your build pipeline, so you will not have to commit to this same folder.
Saturn app
// generate your runtime-scripts folder first, see above 👆
#load "runtime-scripts/Microsoft.AspNetCore.App-latest-8.fsx"
#r "nuget: Saturn"
#r "nuget: Newtonsoft.Json"
open Saturn
open Giraffe
let app = application {
use_router (text "Hello World from Saturn")
}
run app
Now let’s create a test-folder
for our playground 🚗
mkdir test-folder && cd test-folder
dotnet fsi generateAspnetcore.fsx
Now within this folder, you can create a single file (your whole app) named saturn.fsx
and you can run this code, and you can run it in interactive mode! 🍸 🍰
dotnet fsi saturn.fsx
Another way is also opening it with VsCode and Ionide.
Here is the result, after making an HTTP GET call to localhost:5000 🌝
To close your app a regular CTRL+C
will send a signal to your process, and it will nicely show us the expected AspNetCore termination logs.
HTTP in F#
Saturn is a web development framework written in F# which implements the server-side MVC pattern. Many of its components and concepts will seem familiar to those of us with experience in other web frameworks like Ruby on Rails or Python’s Django. It’s heavily inspired by Elixir’s Phoenix.
Built on top of the battle-tested ASP.NET Core foundation and the highly flexible, extendable model of Giraffe, Saturn provides high level abstractions, helpers and tools to enable high developer productivity, at the same time keeping high application performance provided by Kestrel and Giraffe. — [2]
Saturn and Falco are amazing web API frameworks, Saturn works with and on top of Giraffe, making it easier to configure and set up aspnetcore using computation expressions. It is also part of the SAFE full-stack framework which combines Saturn in the backend and Fable in the frontend to transpile F# to javascript (for another time!).
Giraffe is the de-facto amazing and most used framework, heavily inspired by Suave which introduced the 🐟 >=>
operator, useful for composing HTTP async functions.
In Suave we program by chaining functions of type WebPart.
type WebPart = HttpContext -> Async<HttpContext option>
Suave uses the
>=>
operator to chain WebParts together. — [3]
Giraffe uses a very similar concept for the same operator.
Falco 🦅 app
Below is a similar example but using Falco 🐦 framework, which is very slim and unique as well!
With similar results…
And yet, you could still use the older and amazing Suave framework which has a full HTTP server written in F# and not using Kestrel, but you would miss much of the benefits of running on AspNetCore and some interop if you are migrating from a C# solution, for example, if you also use Microsoft DI and builders. In the latter case when using Suave you would have to arrange your own custom solution.