United Kingdom: +44 (0)208 088 8978

SAFE Bookstore v5

Ever wondered what how easy it would be to create a production ready SAFE stack application? In this post we look at the updated SAFE Bookstore and some important features needed in production applications.

We're hiring Software Developers

Click here to find out more

Following the release of the SAFE Stack v5 template, we have recently completed work on upgrading one of the demo applications that utilises the template, namely SAFE Bookstore.

Overview

This application uses a few features that are not included in the SAFE template and shows how easy it is to includes them.

Client

Server

  • Communicating with Azure storage
  • Authentication
  • Background services

Key features

Authentication and Authorisation

The application has a way to sign in and sign out which is using Saturn’s very simple to configure JWT token authentication. The token provided is used to

  • Determine which routes the user can access on the Client
  • Authenticate calls to the Fable Remoting configured endpoints for accessing the books Wishlist.

There are quite a few moving pieces to the authentication puzzle so take a look in the following files in the repository

  • Server.fs
  • Authorise.fs
  • Index.fs
  • Login.fs

Connection to storage account using Role Based Access Control

In development the Server connection to the Azure storage account uses a simple connection string. Ideally though in a deployed environment, we do not want to worry about connection strings and having to keep them secure.

For this we use Azure Role Based Access Control. When deploying the application using Farmer, we can instruct the deployment to set up the connection between the storage account and the server and assign access roles to the server. More information on the approach can be found here.

Build.fs

    let web = webApp {
        system_identity
        setting "StorageAccountName" storageAccountName
        //other settings ommitted
    }

    let storage = storageAccount {
        grant_access web.SystemIdentity Roles.StorageTableDataContributor
        grant_access web.SystemIdentity Roles.StorageBlobDataContributor
        //other settings ommitted
    }

Server.fs

    //add the blob and table storage clients to the dependency injection container
    services.AddAzureClients(fun builder ->
        let blobStorage = "https://{storageAccountName}.blob.core.windows.net"
        let tableStorage = "https://{storageAccountName}.table.core.windows.net"
        builder.AddBlobServiceClient(blobStorage) |> ignore
        builder.AddTableServiceClient(tableStorage) |> ignore
        builder.UseCredential(DefaultAzureCredential()) |> ignore)

Scheduled background services using Quartz

Every 2 hours the server will reset the stored Wishlist back to a default set of books. For this we register a hosted service and use Quartz.net

Server.fs

    services
        .AddQuartz(fun config ->
            let jobName = JobKey "reset-storage"

            config
                .AddJob<ResetStorageJob>(jobName)
                .AddTrigger(fun trigger -> 
                        trigger.ForJob(jobName)
                                  .WithCronSchedule("0 0 */2 * * ?") |> ignore)
            |> ignore)
        .AddQuartzHostedService(fun options -> options.WaitForJobsToComplete <- true) |> ignore

ResetStorage.fs

type ResetStorageJob(blobService: BlobServiceClient, tableService: TableServiceClient) =
    member this.BlobService = blobService
    member this.TableService = tableService

    interface IJob with
        member this.Execute _ =
            printfn $"Resetting wishlist to default"
            resetStorage this.BlobService this.TableService

Summary

The SAFE Stack template can be built on to provide powerful functionality that can be used in deployed production applications.