Skip to content

lucasteles/FSharp.MinimalApi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

e449d3d Â· Dec 13, 2024

History

37 Commits
Apr 9, 2024
Dec 26, 2023
Mar 20, 2024
Jan 8, 2024
Jan 8, 2024
Jun 5, 2024
Dec 13, 2024
Feb 16, 2023
Feb 16, 2023
Feb 16, 2023
Nov 14, 2023
Feb 16, 2023
Dec 26, 2023
Dec 26, 2023
Feb 16, 2023
Feb 16, 2023
Feb 16, 2023
Feb 16, 2023
Dec 26, 2023

Repository files navigation

CI Nuget

FSharp.MinimalApi

Easily define your routes in your ASP.NET Core MinimalAPI with TypedResults support

Getting started

NuGet package available:

$ dotnet add package FSharp.MinimalApi

💡 You can check a complete sample HERE

Defining Routes

open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Http.HttpResults

open FSharp.MinimalApi
open FSharp.MinimalApi.Builder
open type TypedResults

type CustomParams =
    { [<FromRoute>]
      foo: int
      [<FromQuery>]
      bar: string
      [<FromServices>]
      logger: ILogger<MyDbContext> }

let routes =
    endpoints {
        get "/hello" (fun () -> "world")

        // request bindable parameters must be mapped to objects/records
        get "/ping/{x}" (fun (req: {| x: int |}) -> $"pong {req.x}")

        get "/inc/{v:int}" (fun (req: {| v: int; n: Nullable<int> |}) -> req.v + (req.n.GetValueOrDefault 1))

        get "/params/{foo}" (fun (param: CustomParams) ->
            param.logger.LogInformation "Hello Params"
            $"route={param.foo}; query={param.bar}")

        // better static/openapi typing
        get "/double/{v}" produces<Ok<int>> (fun (req: {| v: int |}) -> Ok(req.v * 2))

        get "/even/{v}" produces<Ok<string>, BadRequest> (fun (req: {| v: int; logger: ILogger<_> |}) ->
            (if req.v % 2 = 0 then
                 // TypedResult relies havely on implict convertions
                 // the (!!) operator help us to call the implicit cast
                 !! Ok("even number!")
             else
                 req.logger.LogInformation $"Odd number: {req.v}"
                 !! BadRequest()))
        
        // nesting
        endpoints {
            group "user"
            tags "Users"

            get "/" produces<Ok<User[]>> (fun (req: {| db: MyDbContext |}) ->
                task {
                    let! users = req.db.Users.ToArrayAsync()
                    return Ok(users)
                })

            get "/{userId}" produces<Ok<User>, NotFound> (fun (req: {| userId: Guid; db: MyDbContext |}) ->
                task {
                    let! res = req.db.Users.Where(fun x -> x.Id = UserId req.userId).TryFirstAsync()

                    match res with
                    | Some user -> return !! Ok(user)
                    | None -> return !! NotFound()
                })

            // group mappping
            route "profile" {
                allowAnonymous

                post
                    "/"
                    produces<Created<User>, Conflict, ValidationProblem>
                    (fun (req: {| userInfo: NewUser; db: MyDbContext |}) ->
                        task {
                            match NewUser.parseUser req.userInfo with
                            | Error err -> return !! ValidationProblem(err)
                            | Ok newUser ->
                                let! exists = req.db.Users.TryFirstAsync(fun x -> x.Email = newUser.Email)

                                match exists with
                                | Some _ -> return !! Conflict()
                                | None ->
                                    req.db.Users.add newUser
                                    do! req.db.saveChangesAsync ()
                                    return !! Created($"/user/{newUser.Id.Value}", newUser)
                        })

                delete "/{userId}" produces<NoContent, NotFound> (fun (req: {| userId: Guid; db: MyDbContext |}) ->
                    task {
                        let! exists = req.db.Users.TryFirstAsync(fun x -> x.Id = UserId req.userId)

                        match exists with
                        | None -> return !! NotFound()
                        | Some user ->
                            req.db.Users.remove user
                            do! req.db.saveChangesAsync ()
                            return !! NoContent()
                    })

            }
        }
    }

[<EntryPoint>]
let main args =
    let builder = WebApplication.CreateBuilder(args)
    // ... builder configuration ...
    app.MapGroup("api").WithTags("Root") |> routes.Apply |> ignore
    // ... app configuration ...
    app.Run()
    0