Falco Framework

repository·master·Indexed 20 days ago

https://github.com/falcoframework/falco

A functional-first toolkit for building full-stack web applications in F# leveraging ASP.NET Core. It provides a routing API, a native F# view engine, and built-in support for authentication, authorization, and XSRF protection. The ecosystem includes specialized libraries such as Falco.Markup, Falco.Htmx, Falco.OpenApi, and Falco.UnionRoutes. Supports deployment as self-contained executables and Native AOT compilation.

Tokens
18.6K
Snippets
70
Records
75
Agent score
71%

What's inside Falco

  1. Overview of Falco features

    master

    Falco provides several core capabilities for web development in F#:

    • Routing: A simple and powerful routing API.
    • Request Data: A uniform API for accessing any request data.
    • View Engine: A native F# view engine for markup.
    • Asynchronous Handling: Built-in support for asynchronous request handling.
    • Security: Utilities for Authentication and Cross-Site Request Forgery (CSRF) protection.
    • Data Handling: Built-in support for large uploads (multipart/form-data binding) and binary responses (Content-Disposition).
  2. Explore Falco documentation and guides

    master

    Falco is a toolset built on .NET and ASP.NET Core designed for building full-stack web applications. The documentation is organized into several key functional areas:

    Core Web Concepts

    • Routing: How to define and handle application routes.
    • Writing responses: How to send data back to the client.
    • Accessing request data: How to read incoming request information.
    • View engine: Working with markup and templates.

    Security

    • Cross Site Request Forgery (XSRF): Implementing protection against XSRF attacks.
    • Authentication & Authorization: Managing user identity and permissions.

    Infrastructure

    • Host Configuration: Configuring the application host.
    • Deployment: Preparing your application for production environments.
  3. Extract route parameters from URL templates

    master

    Falco uses route templates (e.g., /hello/{name:alpha}) to capture segments of a URL.

    1. Route Constraints: You can append constraints to parameters, such as :alpha, to restrict matches to specific character types.
    2. Accessing Values: Captured parameters are stored in HttpRequest.RouteValues. You can access them using the Request.getRoute method and then retrieving the typed value (e.g., route.GetString "name").

    There are two primary ways to handle parameters:

    • Inline: Access the route directly within the handler using Request.getRoute ctx.
    • Using mapGet: Use the mapGet function to extract a specific parameter and pass it as an argument to your handler function.
    // Method 1: Inline extraction
    let endpoints = [
        get "/hello/{name:alpha}" (fun ctx ->
            let route = Request.getRoute ctx
            let name = route.GetString "name"
            Response.ofPlainText (sprintf "Hello %s" name) ctx)
    ]
    
    // Method 2: Using mapGet to pass parameter to handler
    let greetingHandler name : HttpHandler = 
        let message = sprintf "Hello %s" name
        Response.ofPlainText message
    
    let endpoints = [
        mapGet "/hello/{name:alpha}" (fun route -> route.GetString "name") greetingHandler
    ]
  4. Implement Routing and URL Generation

    master

    For scalable applications, define static route templates and helper functions to generate URLs. This provides a typed API for navigating your application and avoids hardcoded strings throughout your controllers and views.

    1. Define Route Templates: Use string placeholders like {name} for dynamic segments.
    2. Create URL Generators: Write functions that take parameters and replace the placeholders in the templates.
    module Route =
        let index = "/"
        let greetPlainText = "/greet/text/{name}"
    
    module Url =
        let greetPlainText name = Route.greetPlainText.Replace("{name}", name)
  5. Modify responses with Response Modifiers

    master

    Response modifiers allow you to manipulate the HttpResponse before it is returned. These functions take an HttpContext and return an HttpContext, making them ideal for use with function composition (>>).

    Set status code

    Use Response.withStatusCode to set the HTTP status code.

    Add headers

    Use Response.withHeaders to add a list of key-value header pairs.

    Add cookies

    Use Response.withCookie to add a cookie. You can provide a simple name/value pair or a CookieOptions object for advanced configuration (like Expires).

    IMPORTANT: Do not use Response.withCookie for authentication. Use Response.signInAndRedirect and Response.signOutAndRedirect from the Authentication module instead.

    // Set status code
    let notFoundHandler : HttpHandler =
        Response.withStatusCode 404
        >> Response.ofPlainText "Not found"
    
    // Add headers
    let handlerWithHeaders : HttpHandler =
        Response.withHeaders [ "Content-Language", "en-us" ]
        >> Response.ofPlainText "Hello world"
    
    // Add cookie
    let handlerWithCookie : HttpHandler =
        Response.withCookie "greeted" "1"
        >> Response.ofPlainText "Hello world"
    
    // Add cookie with options
    let handlerWithCookieOptions : HttpHandler =
        let options = CookieOptions()
        options.Expires <- DateTime.Now.Minutes(15)
        Response.withCookie options "greeted" "1"
        >> Response.ofPlainText "Hello world"
  6. Understand RequestData and RequestValue semantics

    master

    Falco uses a uniform API via the RequestData type (and its derivative FormData) to access data from various sources like routes, queries, forms, headers, and cookies.

    Under the hood, RequestData uses a recursive discriminated union called RequestValue to parse key/value collections. This allows you to submit complex, nested structures using specific key syntaxes:

    • Object Notation (Dot Notation): Keys like user.name are interpreted as nested objects.
    • List Notation (Square Bracket Notation): Keys like season[0]=summer or hobbies[]=hiking are interpreted as lists. This supports both indexed and non-indexed variants.
    // Example of Object Notation: user.name=john%20doe&user.email=abc@def123.com
    // Interpreted as:
    // RObject [ "user", RObject [ "name", RString "john doe"; "email", RString "abc@def123.com" ] ]
    
    // Example of List Notation: name=john&season[0]=summer&hobbies[]=hiking
    // Interpreted as:
    // RObject [ "name", RString "john"; "season", RList [ RString "summer" ]; "hobbies", RList [ RString "hiking" ] ]
  7. Create Views using Falco.Markup HTML DSL

    master

    Falco uses a pure F# HTML DSL (via Falco.Markup) to create views. This ensures views are compile-time checked and live alongside your logic.

    Key components of the DSL:

    • Elem: Produces HTML elements.
    • Attr: Produces HTML element attributes.
    • Text: Produces HTML text nodes.

    You can wrap views in a shared layout function to maintain consistent HTML5 structures (e.g., including <head> tags or CSS links).

    module View =
        let layout content =
            Templates.html5 "en"
                [ _link [ _href_ "/style.css"; _rel_ "stylesheet" ] ]
                content
    
        module GreetingView =
            let detail greeting =
                layout [ 
                    _h1' $"Hello {greeting.Name}" 
                ]
  8. Understand the core modules of Falco.Markup

    master

    Falco.Markup is organized into three primary modules used to generate markup:

    • Elem: Used to generate elements (tags).
    • Attr: Used to generate attributes.
    • Text: Used to generate text nodes.

    Elements are categorized into two types:

    1. ParentNode: Elements that can contain other elements. These functions receive two inputs: a list of attributes and a list of child elements.
    2. SelfClosingNode: Elements that do not contain children (e.g., <hr>). These functions receive only one input: a list of attributes.

    You can access these modules directly or use the "underscore syntax" (e.g., _h1 [] [] for Elem.h1 or _class_ "my-class" for Attr.class).

    let markup =
        _div [ _class_ "heading" ] [ 
            _h1' "Hello world!" 
        ]
  9. Quickstart with Falco

    master

    Falco is a toolkit for building functional-first, full-stack web applications using F#. It is built on ASP.NET Core and integrates seamlessly with existing .NET Core middleware. You can create a basic web application by using WebApplication.Create() and responding with Response.ofPlainText.

    open Falco
    open Microsoft.AspNetCore.Builder
    
    let wapp = WebApplication.Create()
    
    wapp.Run(Response.ofPlainText "Hello world")
  10. Set up a Falco project manually

    master

    To create a new Falco application and add the necessary dependencies for a SQLite-backed REST API, use the following commands:

    # Create a new Falco project
    dotnet new falco -o BasicRestApiApp
    cd BasicRestApiApp
    
    # Add SQLite and Donald (database access library) packages
    dotnet add package System.Data.SQLite
    dotnet add package Donald
    > dotnet new falco -o BasicRestApiApp
    > cd BasicRestApiApp
    > dotnet add package System.Data.SQLite
    > dotnet add package Donald
  11. Configure OpenAPI and Swagger services in Falco

    master

    To enable OpenAPI generation and the Swagger UI in your Falco application, you must register the services in the WebApplicationBuilder and add the corresponding middleware to the application pipeline.

    1. Call .AddFalcoOpenApi() and .AddSwaggerGen() on the service collection.
    2. Call .UseSwagger() and .UseSwaggerUI() on the application instance.
    let bldr = WebApplication.CreateBuilder(args)
    
    bldr.Services
        .AddFalcoOpenApi()
        .AddSwaggerGen()
        |> ignore
    
    let wapp = bldr.Build()
    
    wapp.UseHttpsRedirection()
        .UseSwagger()
        .UseSwaggerUI()
    |> ignore