Frank 7.2.0
dotnet add package Frank --version 7.2.0
NuGet\Install-Package Frank -Version 7.2.0
<PackageReference Include="Frank" Version="7.2.0" />
<PackageVersion Include="Frank" Version="7.2.0" />
<PackageReference Include="Frank" />
paket add Frank --version 7.2.0
#r "nuget: Frank, 7.2.0"
#:package Frank@7.2.0
#addin nuget:?package=Frank&version=7.2.0
#tool nuget:?package=Frank&version=7.2.0
Frank
F# computation expressions, or builders, for configuring the Microsoft.AspNetCore.Hosting.IWebHostBuilder and defining routes for HTTP resources using Microsoft.AspNetCore.Routing.
This project was inspired by @filipw's Building Microservices with ASP.NET Core (without MVC).
Packages
Features
WebHostBuilder- computation expression for configuringWebHostResourceBuilder- computation expression for configuring resources (routing)- No pre-defined view engine - use your preferred view engine implementation, e.g. Falco.Markup, Oxpecker.ViewEngine, or Hox
- Easy extensibility - just extend the
Builderwith your own methods!
Basic Example
module Program
open System.IO
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Routing
open Microsoft.AspNetCore.Routing.Internal
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Logging
open Frank
open Frank.Builder
let home =
resource "/" {
name "Home"
get (fun (ctx:HttpContext) ->
ctx.Response.WriteAsync("Welcome!"))
}
[<EntryPoint>]
let main args =
webHost args {
useDefaults
logging (fun options-> options.AddConsole().AddDebug())
plugWhen isDevelopment DeveloperExceptionPageExtensions.UseDeveloperExceptionPage
plugWhenNot isDevelopment HstsBuilderExtensions.UseHsts
plugBeforeRouting HttpsPolicyBuilderExtensions.UseHttpsRedirection
plugBeforeRouting StaticFileExtensions.UseStaticFiles
resource home
}
0
Middleware Pipeline
Frank provides two middleware operations with different positions in the ASP.NET Core pipeline:
Request → plugBeforeRouting → UseRouting → plug → Endpoints → Response
plugBeforeRouting
Use for middleware that must run before routing decisions are made:
- HttpsRedirection - redirect before routing
- StaticFiles - serve static files without routing overhead
- ResponseCompression - compress all responses
- ResponseCaching - cache before routing
webHost args {
plugBeforeRouting HttpsPolicyBuilderExtensions.UseHttpsRedirection
plugBeforeRouting StaticFileExtensions.UseStaticFiles
resource myResource
}
plug
Use for middleware that needs routing information (e.g., the matched endpoint):
- Authentication - may need endpoint metadata
- Authorization - requires endpoint to check policies
- CORS - may use endpoint-specific policies
webHost args {
plug AuthenticationBuilderExtensions.UseAuthentication
plug AuthorizationAppBuilderExtensions.UseAuthorization
resource protectedResource
}
Conditional Middleware
Both plugWhen and plugWhenNot run in the plug position (after routing):
webHost args {
plugWhen isDevelopment DeveloperExceptionPageExtensions.UseDeveloperExceptionPage
plugWhenNot isDevelopment HstsBuilderExtensions.UseHsts
resource myResource
}
Conditional Before-Routing Middleware
Both plugBeforeRoutingWhen and plugBeforeRoutingWhenNot run in the plugBeforeRouting position (before routing):
let isDevelopment (app: IApplicationBuilder) =
app.ApplicationServices
.GetService<IWebHostEnvironment>()
.IsDevelopment()
webHost args {
// Only redirect to HTTPS in production
plugBeforeRoutingWhenNot isDevelopment HttpsPolicyBuilderExtensions.UseHttpsRedirection
// Only serve static files locally in development (CDN in production)
plugBeforeRoutingWhen isDevelopment StaticFileExtensions.UseStaticFiles
resource myResource
}
Frank.Auth
Frank.Auth provides resource-level authorization for Frank applications, integrating with ASP.NET Core's built-in authorization infrastructure.
Installation
dotnet add package Frank.Auth
Protecting Resources
Add authorization requirements directly to resource definitions:
open Frank.Builder
open Frank.Auth
// Require any authenticated user
let dashboard =
resource "/dashboard" {
name "Dashboard"
requireAuth
get (fun ctx -> ctx.Response.WriteAsync("Welcome to Dashboard"))
}
// Require a specific claim
let adminPanel =
resource "/admin" {
name "Admin"
requireClaim "role" "admin"
get (fun ctx -> ctx.Response.WriteAsync("Admin Panel"))
}
// Require a role
let engineering =
resource "/engineering" {
name "Engineering"
requireRole "Engineering"
get (fun ctx -> ctx.Response.WriteAsync("Engineering Portal"))
}
// Reference a named policy
let reports =
resource "/reports" {
name "Reports"
requirePolicy "CanViewReports"
get (fun ctx -> ctx.Response.WriteAsync("Reports"))
}
// Compose requirements (AND semantics — all must pass)
let sensitive =
resource "/api/sensitive" {
name "Sensitive"
requireAuth
requireClaim "scope" "admin"
requireRole "Engineering"
get (fun ctx -> ctx.Response.WriteAsync("Sensitive data"))
}
Application Wiring
Configure authentication and authorization services using Frank's builder syntax:
[<EntryPoint>]
let main args =
webHost args {
useDefaults
useAuthentication (fun auth ->
// Configure your authentication scheme here
auth)
useAuthorization
authorizationPolicy "CanViewReports" (fun policy ->
policy.RequireClaim("scope", "reports:read") |> ignore)
resource dashboard
resource adminPanel
resource reports
}
0
Authorization Patterns
| Pattern | Operation | Behavior |
|---|---|---|
| Authenticated user | requireAuth |
401 if unauthenticated, 200 if authenticated |
| Claim (single value) | requireClaim "type" "value" |
403 if claim missing or wrong value |
| Claim (multiple values) | requireClaim "type" ["a"; "b"] |
200 if user has any listed value (OR) |
| Role | requireRole "Admin" |
403 if user not in role |
| Named policy | requirePolicy "PolicyName" |
Delegates to registered policy |
| Multiple requirements | Stack multiple require* |
AND semantics — all must pass |
| No requirements | (default) | Publicly accessible, zero overhead |
Frank.OpenApi
Frank.OpenApi provides native OpenAPI document generation for Frank applications, with first-class support for F# types and declarative metadata using computation expressions.
Installation
dotnet add package Frank.OpenApi
HandlerBuilder Computation Expression
Define handlers with embedded OpenAPI metadata using the handler computation expression:
open Frank.Builder
open Frank.OpenApi
type Product = { Name: string; Price: decimal }
type CreateProductRequest = { Name: string; Price: decimal }
let createProductHandler =
handler {
name "createProduct"
summary "Create a new product"
description "Creates a new product in the catalog"
tags [ "Products"; "Admin" ]
produces typeof<Product> 201
accepts typeof<CreateProductRequest>
handle (fun (ctx: HttpContext) -> task {
let! request = ctx.Request.ReadFromJsonAsync<CreateProductRequest>()
let product = { Name = request.Name; Price = request.Price }
ctx.Response.StatusCode <- 201
do! ctx.Response.WriteAsJsonAsync(product)
})
}
let productsResource =
resource "/products" {
name "Products"
post createProductHandler
}
HandlerBuilder Operations
| Operation | Description |
|---|---|
name "operationId" |
Sets the OpenAPI operationId |
summary "text" |
Brief summary of the operation |
description "text" |
Detailed description |
tags [ "Tag1"; "Tag2" ] |
Categorize endpoints |
produces typeof<T> statusCode |
Define response type and status code |
produces typeof<T> statusCode ["content/type"] |
Response with content negotiation |
producesEmpty statusCode |
Empty responses (204, 404, etc.) |
accepts typeof<T> |
Define request body type |
accepts typeof<T> ["content/type"] |
Request with content negotiation |
handle (fun ctx -> ...) |
Handler function (supports Task, Task<'a>, Async<unit>, Async<'a>) |
F# Type Schema Generation
Frank.OpenApi automatically generates JSON schemas for F# types:
// F# records with required and optional fields
type User = {
Id: Guid
Name: string
Email: string option // Becomes nullable in schema
}
// Discriminated unions (anyOf/oneOf)
type Response =
| Success of data: string
| Error of code: int * message: string
// Collections
type Products = {
Items: Product list
Tags: Set<string>
Metadata: Map<string, string>
}
WebHostBuilder Integration
Enable OpenAPI document generation in your application:
[<EntryPoint>]
let main args =
webHost args {
useDefaults
useOpenApi // Adds /openapi/v1.json endpoint
resource productsResource
}
0
The OpenAPI document will be available at /openapi/v1.json.
Content Negotiation
Define multiple content types for requests and responses:
handler {
name "getProduct"
produces typeof<Product> 200 [ "application/json"; "application/xml" ]
accepts typeof<ProductQuery> [ "application/json"; "application/xml" ]
handle (fun ctx -> task { (* ... *) })
}
Backward Compatibility
Frank.OpenApi is fully backward compatible with existing Frank applications. You can:
- Mix
HandlerDefinitionand plainRequestDelegatehandlers in the same resource - Add OpenAPI metadata incrementally without changing existing code
- Use the library only where you need API documentation
Frank.Datastar
Frank.Datastar provides seamless integration with Datastar, enabling reactive hypermedia applications using Server-Sent Events (SSE).
Version 7.1.0 features a native SSE implementation with zero external dependencies, delivering high-performance Server-Sent Events directly via ASP.NET Core's IBufferWriter<byte> API. Supports .NET 8.0, 9.0, and 10.0.
Installation
dotnet add package Frank.Datastar
Example
open Frank.Builder
open Frank.Datastar
let updates =
resource "/updates" {
name "Updates"
datastar (fun ctx -> task {
// SSE stream starts automatically
do! Datastar.patchElements "<div id='status'>Loading...</div>" ctx
do! Task.Delay(500)
do! Datastar.patchElements "<div id='status'>Complete!</div>" ctx
})
}
// With explicit HTTP method
let submit =
resource "/submit" {
name "Submit"
datastar HttpMethods.Post (fun ctx -> task {
let! signals = Datastar.tryReadSignals<FormData> ctx
match signals with
| ValueSome data ->
do! Datastar.patchElements $"<div id='result'>Received: {data.Name}</div>" ctx
| ValueNone ->
do! Datastar.patchElements "<div id='error'>Invalid data</div>" ctx
})
}
Available Operations
Datastar.patchElements- Update HTML elements in the DOMDatastar.patchSignals- Update client-side signalsDatastar.removeElement- Remove elements by CSS selectorDatastar.executeScript- Execute JavaScript on the clientDatastar.tryReadSignals<'T>- Read and deserialize signals from request
Each operation also has a WithOptions variant for advanced customization.
Frank.Analyzers
Frank.Analyzers provides compile-time static analysis to catch common mistakes in Frank applications.
Installation
dotnet add package Frank.Analyzers
Available Analyzers
FRANK001: Duplicate HTTP Handler Detection
Detects when multiple handlers for the same HTTP method are defined on a single resource. Only the last handler would be used at runtime, so this is almost always a mistake.
// This will produce a warning:
resource "/example" {
name "Example"
get (fun ctx -> ctx.Response.WriteAsync("First")) // Warning: FRANK001
get (fun ctx -> ctx.Response.WriteAsync("Second")) // This one takes effect
}
IDE Integration
Frank.Analyzers works with:
- Ionide (VS Code)
- Visual Studio with F# support
- JetBrains Rider
Warnings appear inline as you type, helping catch issues before you even compile.
Building
Make sure the following requirements are installed in your system:
- dotnet SDK 8.0 or higher
dotnet build
Sample Applications
The sample/ directory contains several example applications:
| Sample | Description |
|---|---|
Sample |
Basic Frank application |
Frank.OpenApi.Sample |
Product Catalog API demonstrating OpenAPI document generation |
Frank.Datastar.Basic |
Datastar integration with minimal HTML |
Frank.Datastar.Hox |
Datastar with Hox view engine |
Frank.Datastar.Oxpecker |
Datastar with Oxpecker.ViewEngine |
Frank.Falco |
Frank with Falco.Markup |
Frank.Giraffe |
Frank with Giraffe.ViewEngine |
Frank.Oxpecker |
Frank with Oxpecker.ViewEngine |
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FSharp.Core (>= 10.0.102)
-
net8.0
- FSharp.Core (>= 10.0.102)
-
net9.0
- FSharp.Core (>= 10.0.102)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Frank:
| Package | Downloads |
|---|---|
|
Frank.Datastar
Datastar SSE integration for Frank web framework with F# computation expression support. Native SSE implementation with no external dependencies, supports .NET 8.0/9.0/10.0. |
|
|
Frank.Auth
Resource-level authorization extensions for Frank web framework |
|
|
Frank.OpenApi
OpenAPI document generation extensions for Frank web framework |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 7.2.0 | 136 | 2/10/2026 |
| 7.1.0 | 112 | 2/8/2026 |
| 7.0.0-build.0 | 46 | 2/6/2026 |
| 6.5.0 | 111 | 2/5/2026 |
| 6.4.1 | 116 | 2/4/2026 |
| 6.4.1-build.0 | 48 | 2/4/2026 |
| 6.4.0 | 91 | 2/2/2026 |
| 6.4.0-build.0 | 45 | 2/2/2026 |
| 6.3.0 | 447 | 3/15/2025 |
| 6.2.0 | 5,457 | 11/18/2020 |
| 6.1.0 | 1,892 | 6/11/2020 |
| 6.0.0 | 913 | 6/2/2020 |
| 5.0.5 | 1,072 | 1/7/2019 |
| 5.0.4 | 1,020 | 1/6/2019 |
| 5.0.3 | 1,074 | 1/5/2019 |
| 5.0.2 | 1,074 | 1/5/2019 |
| 5.0.1 | 1,058 | 1/5/2019 |
| 5.0.0 | 1,053 | 1/5/2019 |
| 4.0.0 | 1,835 | 3/28/2018 |
### New in 7.2.0 (Released 2026-02-10)
**Frank.OpenApi - Native OpenAPI Document Generation Support**
- **New Library:** Frank.OpenApi extension library for declarative OpenAPI metadata
- **HandlerBuilder CE:** Computation expression for defining handlers with embedded OpenAPI metadata:
- `name` — operationId for the endpoint
- `summary` / `description` — operation documentation
- `tags` — endpoint categorization
- `produces typeof<T> statusCode [contentTypes]` — response types with optional content negotiation
- `producesEmpty statusCode` — empty responses (204, 404, etc.)
- `accepts typeof<T> [contentTypes]` — request types with optional content negotiation
- `handle` — supports Task, Task<'a>, Async<unit>, Async<'a>
- **ResourceBuilder Extensions:** All HTTP method operations (`get`, `post`, `put`, `delete`, `patch`, `head`, `options`) accept HandlerDefinition
- **F# Type Schemas:** Automatic JSON Schema generation for F# types via FSharpSchemaTransformer:
- Records with required and optional fields
- Discriminated unions with anyOf/oneOf
- Collections (list, Set, Map)
- Option types as nullable
- **WebHostBuilder Integration:** `useOpenApi` operation to enable OpenAPI document generation at `/openapi/v1.json`
- **Content Negotiation:** Full support for multiple content types (application/json, application/xml, etc.)
- **No Breaking Changes:** Per-handler metadata via method-specific conventions — fully backward compatible
- **Multi-Targeting:** Supports .NET 10.0 (LTS)
- **Core Fix:** Added MethodInfo to endpoint metadata for OpenAPI discovery (required by ASP.NET Core's EndpointMetadataApiDescriptionProvider)
**Example Usage:**
```fsharp
handler {
name "createProduct"
summary "Create a new product"
tags [ "Products"; "Admin" ]
produces typeof<Product> 201
accepts typeof<CreateProductRequest>
handle (fun ctx -> async { return! createProduct ctx })
}
```
### New in 7.1.0 (Released 2026-02-07)
**Frank.Datastar - Native SSE Implementation & Stream-Based HTML Generation**
- **Performance:** Replaced StarFederation.Datastar.FSharp dependency with native SSE implementation using `IBufferWriter<byte>` for zero-copy buffer writing
- **Zero External Dependencies:** Frank.Datastar now has no external NuGet dependencies beyond framework references and Frank core
- **Multi-Targeting Restored:** Supports .NET 8.0, 9.0, and 10.0 (`net8.0;net9.0;net10.0`)
- **API Compatibility:** Zero breaking changes — seamless upgrade from 7.0.x with identical public API surface
- **Performance Optimizations:**
- Pre-allocated byte arrays for SSE field prefixes (no runtime UTF-8 encoding)
- Zero-allocation string segmentation via `StringTokenizer` for multi-line payloads
- Direct buffer writing without intermediate copies
- Per-event flushing for immediate delivery
- **ADR Compliance:** Full conformance to Datastar SDK ADR specification for SSE message format
- **Added:** `Attributes` field to `ExecuteScriptOptions` for custom script tag attributes (additive, non-breaking)
- **Public API:** `ServerSentEventGenerator` now public for advanced SSE event construction
- **Stream-Based Overloads:** Added stream-based SSE operations for zero-allocation HTML rendering:
- All SSE operations now have stream-based overloads accepting `TextWriter -> Task` writer functions
- `streamPatchElements`, `streamPatchSignals`, `streamRemoveElement`, `streamExecuteScript` module functions
- Eliminates full HTML string materialization — 50%+ allocation reduction in high-throughput scenarios (1000+ events/sec)
- Compatible with view engines supporting `TextWriter` output (e.g., Hox `Render.toTextWriter`)
- String-based API remains unchanged for backward compatibility
- Internal `SseDataLineWriter` handles SSE line-splitting transparently
### New in 7.0.0 (Released 2026-02-05)
- **Breaking:** Added `Metadata` field to `ResourceSpec` and `AddMetadata` to `ResourceBuilder` for composable endpoint metadata conventions
- Added `plugBeforeRoutingWhen` for conditional middleware before routing when condition is true
- Added `plugBeforeRoutingWhenNot` for conditional middleware before routing when condition is false
- Added **Frank.Auth** library for resource-level authorization:
- `requireAuth` — require authenticated user
- `requireClaim` — require a specific claim type and value(s)
- `requireRole` — require a specific role
- `requirePolicy` — require a named authorization policy
- `useAuthentication` / `useAuthorization` — configure auth services and middleware on the web host
- `authorizationPolicy` — define named authorization policies on the web host
### New in 6.5.0 (Released 2026-02-04)
- Fixed middleware pipeline ordering: `plug` middleware now runs after `UseRouting` and before `UseEndpoints`
- Added `plugBeforeRouting` for middleware that must run before routing (e.g., StaticFiles, HttpsRedirection)
- Added middleware ordering tests
### New in 6.4.1 (Released 2026-02-04)
- Add Frank.Analyzers to assist with validating resource definitions
- Added additional Frank.Datastar helpers to use more StarFederation.Datastar options
### New in 6.4.0 (Released 2026-02-02)
- Updated to target net8.0, net9.0, and net10.0
- Add Frank.Datastar
- Updated samples and added samples for Frank.Datastar
### New in 6.3.0 (Released 2025-03-14)
- Updated to target net8.0 and net9.0
- Updated examples
### New in 6.2.0 (Released 2020-11-18)
- Updated samples
### New in 6.1.0 (Released 2020-06-11)
- Encapsulate `IHostBuilder` and expose option to use web builder defaults with `useDefaults`.
- Server application can now be simply a standard console application. See [samples](https://github.com/frank-fs/frank/tree/master/sample).
### New in 6.0.0 (Released 2020-06-02)
- Update to .NET Core 3.1
- Use Endpoint Routing
- Pave the way for built-in generation of Open API spec
### New in 5.0.0 (Released 2019-01-05)
- Starting over based on ASP.NET Core Routing and Hosting
- New MIT license
- Computation expression for configuring IWebHostBuilder
- Computation expression for specifying HTTP resources
- Sample using simple ASP.NET Core web application
- Sample using standard Giraffe template web application
### New in 4.0.0 - (Released 2018/03/27)
- Update to .NETStandard 2.0 and .NET 4.6.1
- Now more easily used with Azure Functions or ASP.NET Core
### New in 3.1.1 - (Released 2014/12/07)
- Use FSharp.Core from NuGet
### New in 3.1.0 - (Released 2014/10/13)
- Remove dependency on F#x
- Signatures remain equivalent, but some type aliases have been removed.
### New in 3.0.19 - (Released 2014/10/13)
- Merge all implementations into one file and add .fsi signature
### New in 3.0.18 - (Released 2014/10/12)
- Use Paket for package management
- FSharp.Core 4.3.1.0
- NOTE: Jumped to 3.0.18 due to bad build script configuration
### New in 3.0.0 - (Released 2014/05/24)
- Updated dependencies to Web API 2.1 and .NET 4.5
### New in 2.0.3 - (Released 2014/02/07)
- Add SourceLink to link to GitHub sources (courtesy Cameron Taggart).
### New in 2.0.2 - (Released 2014/01/26)
- Remove FSharp.Core.3 as a package dependency.
### New in 2.0.0 - (Released 2014/01/07)
- Generate documentation with every release
- Fix a minor bug in routing (leading '/' was not stripped)
- Reference FSharp.Core.3 NuGet package
- Release assembly rather than current source packages:
- FSharp.Net.Http
- FSharp.Web.Http
- Frank
- Adopt the FSharp.ProjectScaffold structure
### New in 1.1.1 - (Released 2014/01/01)
- Correct spacing and specify additional types in HttpContent extensions.
### New in 1.1.0 - (Released 2014/01/01)
- Remove descriptor-based implementation.
### New in 1.0.2 - (Released 2013/12/10)
- Restore Frank dependency on FSharp.Web.Http. Otherwise, devs will have to create their own routing mechanisms. A better solution is on its way.
### New in 1.0.1 - (Released 2013/12/10)
- Change Web API dependency to Microsoft.AspNet.WebApi.Core.
### New in 1.0.0 - (Released 2013/12/10)
- First official release.
- Use an Option type for empty content.