Create a F# Web API using Falco and Donald
Tags: fsharp
Published: 2023-07-09 20:36:46

## Introduction

I have been studying F# in the last months, in this post I want to share some of my knowledge.

In this article, I’ll be covering how to create a REST API from scratch using F#, Falco and Donald.

## Web API Overview

We will create an e-commerce application which has the following endpoints:

- **GET /api/products** - Returns all products;
- **POST /api/products** - Inserts a product;

## Project Structure

I used Visual Studio Code with the extension Ionide for F#.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r1akcyjoter9uoirf4y6.png)

### Project Description

- *Amazon.Catalog.Core*: Project for the domain, it has domain entities classes and [pure functions](https://en.wikipedia.org/wiki/Pure_function) of the business logic.
- *Amazon.Catalog.Adapters*: Project for the database's classes, in this project I am using the [repository pattern](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design) and the library [Donald](https://github.com/pimbrouwers/Donald).
- *Amazon.Catalog.WebApi*: Project for controllers using the library [Falco](https://www.falcoframework.com/docs/get-started.html).
- *Amazon.Catalog.Application*: Project for handling use cases/commands.

## ShowMeTheCode

Lets start creating the Product type which is located in the assembly *Amazon.Catalog.Core* under the folder "Entities".

```fsharp
namespace Amazon.Catalog.Core.Entities

module Product =
  open System

  open Amazon.Catalog.Core

  type T = { Id: Guid 
             Name: string
             Description: string
             Price: decimal
             Active: bool }

  let validate prod =
    Utils.domainValidate prod [
      (String.noEmpty prod.Name, "Invalid name.")
      (String.noEmpty prod.Description, "Invalid description.")
      (Decimal.IsPositive prod.Price, "Invalid price.")]
```

I decided to follow an approach that is more common in functional language thus I created the module `Product` and under it the type `T` which represents Product. Moreover, the module `Product` has the method `validate` that validates product's properties.

Next step is to create the ProductRepository in the assembly *Amazon.Catalog.Adapters*. For interacting with Postgres database I am using the library [Donald] (https://github.com/pimbrouwers/Donald).

```fsharp
namespace Amazon.Catalog.Adapters.Data.Repositories

module ProductRepository =
  open Donald
  open System.Data

  open Amazon.Catalog.Adapters.Data
  open Amazon.Catalog.Core
  open Amazon.Catalog.Core.Entities

  let insert(prod: Product.T) =
    Database.conn
    |> Db.newCommand "INSERT INTO public.product VALUES (@Id,@Name,@Description,@Price,@Active)"
    |> Db.setParams [
      "@Id", SqlType.Guid prod.Id
      "@Name", SqlType.String prod.Name
      "@Description", SqlType.String prod.Description
      "@Price", SqlType.Decimal prod.Price
      "@Active", SqlType.Boolean prod.Active
    ]
    |> Db.exec
    |> function
      | Ok _      -> Ok prod
      | Error err -> err |> Helper.convertDbError

  let ofDataReader (rd: IDataReader): Product.T =
    { Id          = rd.ReadGuid "Id"
      Name        = rd.ReadString "Name"
      Description = rd.ReadString "Description"
      Price       = rd.ReadDecimal "Price"
      Active      = rd.ReadBoolean "Active" }
    
  let get limit offset : Result<Product.T list, Error> =
    Database.conn
    |> Db.newCommand "SELECT * FROM public.product LIMIT @Limit OFFSET @Offset  "
    |> Db.setParams [
      "@Limit", SqlType.Int limit
      "@Offset", SqlType.Int offset 
    ]
    |> Db.query ofDataReader
    |> function
      | Ok prods        -> Ok prods 
      | Error err       -> err |> Helper.convertDbError
```

The database connection must be provide to access it.

```fsharp
let conn = new NpgsqlConnection("Server=127.0.0.1;Port=5432;Database=postgres;User Id=postgres;Password=postgres;")
```

Next, we will create the ´CreateProductCommand´ module, it is responsible to perform impure business logic and to call every method needed for creating a product.

```fsharp
namespace Amazon.Catalog.Application.Comands

[<RequireQualifiedAccess>]
module CreateProductCommand =
  open System
   
  open Amazon.Catalog.Core
  open Amazon.Catalog.Core.Entities
  open Amazon.Catalog.Adapters.Data.Repositories

  type Request = { Name: string
                   Description: string
                   Price: decimal }

  let createEntity req : Product.T =
    { Id          = Guid.NewGuid()
      Name        = req.Name
      Description = req.Description
      Price       = req.Price
      Active      = true }

  let checkIfProductExist (prod: Product.T) =
    ProductRepository.getByName prod.Name
    |> function
      | Ok result ->
        match result with
        | Some _ -> Error (DomainError ("Product already exists."))
        | None   -> Ok prod
      | Error err  -> Error err

  let handle (req: Request) =
    createEntity req
    |> Product.validate
    |> Result.bind checkIfProductExist
    |> Result.bind ProductRepository.insert
```

Important notes:
- The command module can have impure business logic methods;
- The ´handle´ method is like a pipeline, it calls every function responsible to perform the given command;
- The code is not throwing any exception, instead functions returns the type ´Result<Ok, Error>´ thus it is possible to compose functions even when the function is impure (for example `ProductRepository.insert`);

Once the business logic is ready, we can create the API in the *Amazon.Catalog.WebApi* assembly. I split the routes and controller implementation code.

In the `Program` module it is defined the routes.

```fsharp
module Amazon.Catalog.WebApi.Program

open Falco
open Falco.Routing
open Falco.HostBuilder
open Microsoft.Extensions.Logging

open Amazon.Catalog.WebApi.Controllers

[<EntryPoint>]
let main args =

  webHost args {
    endpoints [
      post "/api/products"  ProductController.create

      get "/api/products" ProductController.getProduct
    ]
  }
  0
```

Inside `ProductController` is defined the implementation of each endpoints.

```fsharp
namespace Amazon.Catalog.WebApi.Controllers

module ProductController =
  open Falco

  open Amazon.Catalog.Adapters.Data.Repositories.ProductRepository
  open Amazon.Catalog.Core
  open Amazon.Catalog.WebApi.Controllers.BaseController
  open Amazon.Catalog.Application.Comands

  let getProducts: HttpHandler = fun ctx ->
    let q = Request.getQuery ctx
    let page = q.GetInt ("page", 0)
    let pageSize = q.GetInt("pageSize", 10)
    let offset = if page = 0 then page else page * pageSize
    handleResponse <| get pageSize offset <| ctx 

  let create: HttpHandler =
    let handleCreate req : HttpHandler =
      req 
      |> CreateProductCommand.handle
      |> handleResponse

    Request.mapJson handleCreate
```

Falco provides different ways for getting information from the request (body, header, query string, and so on), you can check on [Falco documentation](https://www.falcoframework.com/docs/request.html).

You can find the complete code [here](https://github.com/samuelc92/amazon-catalog). I am still using this project for studying F# thus feel free to contribute.

If you have any doubt or any questions comment below.

## References
https://fsharpforfunandprofit.com/posts/organizing-functions/
https://dev.to/tunaxor/f-s-mean-1g2b