Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jordanmarr/fable-lit-fullstack-template
A SAFE-style template with Fable.Lit, Fable.Remoting and Giraffe
https://github.com/jordanmarr/fable-lit-fullstack-template
Last synced: 3 months ago
JSON representation
A SAFE-style template with Fable.Lit, Fable.Remoting and Giraffe
- Host: GitHub
- URL: https://github.com/jordanmarr/fable-lit-fullstack-template
- Owner: JordanMarr
- License: mit
- Created: 2022-05-16T00:09:32.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-04-27T19:51:04.000Z (9 months ago)
- Last Synced: 2024-05-02T05:04:59.141Z (9 months ago)
- Language: F#
- Size: 1.7 MB
- Stars: 53
- Watchers: 4
- Forks: 1
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# fable-lit-fullstack-template [![NuGet version (fable-lit-fullstack-template)](https://img.shields.io/nuget/v/fable-lit-fullstack-template.svg?style=flat-square)](https://www.nuget.org/packages/fable-lit-fullstack-template/)
A SAFE-style template with Fable.Lit, Fable.Remoting and Giraffe
Based on:
https://github.com/Zaid-Ajaj/SAFE.Simplified (thank you Zaid!)## Features
### WebLit.fsproj (Client)
* RPC to WebApi via [Fable.Remoting](https://zaid-ajaj.github.io/Fable.Remoting/#/)
* Page routing via [Fable.LitRouter](https://github.com/JordanMarr/Fable.LitRouter)
* Shared context via [Fable.LitStore](https://www.nuget.org/packages/Fable.LitStore)
* `Shoelace` and `FluentUI` web components imported (cherry-picked)
* A minimal `vite.config.js` file that configures https proxy server + a common proxy redirects
* `"vite-plugin-mkcert` plugin installed for https support for proxy server
* Bootstrap icons + a `bs-icon` custom element control.
* Toast notifications
* Form Validation (rules lives with entities in Shared.fs)### WebApi.fsproj (Server)
* Giraffe
* Fable.Remoting + custom error handler
* A very simple REST module
* Environment specific settings files already configured
* Serilog logger
* Entity Validation (rules live with entities in Shared.fs)## Install Template [![NuGet version (fable-lit-fullstack-template)](https://img.shields.io/nuget/v/fable-lit-fullstack-template.svg?style=flat-square)](https://www.nuget.org/packages/fable-lit-fullstack-template/)
```cmd
dotnet new install fable-lit-fullstack-template
```## Use Template
This will create a new subfolder, `MyLitApp`, which will contain a `MyLitApp.sln`:```cmd
dotnet new flft -o MyLitApp
```## Build
### Initial Restore
To do the initial restore of both the WebApi and WebLit projects:
* :open_file_folder: Build: `dotnet run Restore`Or you can manually restore each:
* :open_file_folder: WebApi: `dotnet restore`
* :open_file_folder: WebLit: `npm install`### Run in Debug Mode
* :open_file_folder: WebApi: `dotnet watch`
* :open_file_folder: WebLit: `npm start`### Pack in Release Mode
To build WebApi and WebLit in Release mode and output to the `Template/dist` folder:
* :open_file_folder: Build: `dotnet run Pack`
or
* :open_file_folder: Build: `dotnet run PackNoTests`## Highlight Extension
Be sure to install the appropriate IDE extension for html and css syntax coloring within your `html $""" """` templates!If using VS Code:
* [Highlight HTML/SQL Templates in F#](https://marketplace.visualstudio.com/items?itemName=alfonsogarciacaro.vscode-template-fsharp-highlight)If using Visual Studio:
* [Html for F# (Lit Template)](https://marketplace.visualstudio.com/items?itemName=daniel-hardt.html-for-fsharp-lit-template)Currently, VS Code with the "Highlight HTML/SQL Templates in F#" extension provides the best experience because it actually provides contextual IntelliSense for the HTML and CSS, plus you can use all the other amazing HTML extensions.
![image](https://github.com/JordanMarr/fable-lit-fullstack-template/assets/1030435/a80afa1c-544d-402c-8bc0-4200eb41ef67)
## Toast Module
![image](https://user-images.githubusercontent.com/1030435/193339122-fdf130d7-ed00-4f18-92e2-a87cba44d0ef.png)
You can create toast messages in two ways:
1) Call a `Toast` function directly:
```F#
Toast.success $"Name changed to {username}."
```2) Return a `Toast` `Cmd` (if using Elmish):
```F#
let update (msg: Msg) (model: Model) =
match msg with
| Save ->
model, Cmd.OfAsync.either Server.api.SaveProjectFiles model.Files SaveCompleted OnError
| SaveCompleted _ ->
model, Toast.Cmd.success "Files saved."
| OnError ex ->
model, Toast.Cmd.error ex.Message
```## Validation
The `Validation.fs` module lives in the Shared.fs project and contains functions for creating validation rules.Usage:
### Shared.fs
1) Create a custom validation method (or function) alongside your entity in the Shared.fs project:```F#
type CatInfo =
{
Name: string
Age: int
LastVetCheckup: System.DateTime
}
member this.Validate() =
rules
|> rulesFor (nameof this.Name) [
this.Name |> Rules.required
this.Name |> Rules.maxLen 10
]
|> rulesFor (nameof this.Age) [
Rules.isTrue (this.Age > 0) "Age must be a positive number."
]
|> rulesFor (nameof this.LastVetCheckup) [
// A custom rule
let timeSinceLastVetCheckup = System.DateTime.Today - this.LastVetCheckup.Date
printfn $"Total days since last checkup: {timeSinceLastVetCheckup.TotalDays}"
if this.Age >= 10 && timeSinceLastVetCheckup.TotalDays > 90 then
Error "Cats over 10 years old should get a vet checkup every three months."
elif timeSinceLastVetCheckup.TotalDays > 180 then
Error "Cats under 10 years old should get a vet checkup every six months."
else
Ok ()
]
|> validate
```### WebLit.fs
2) In your WebLit.fs UI / form, track the entity state in your model using the `ValidationResult`:```F#
type Model =
{
Cat: CatInfo
Validation: ValidationResult
Saved: bool
}
let init () =
{
Cat =
{ CatInfo.Name = ""
; CatInfo.Age = 0
; CatInfo.LastVetCheckup = System.DateTime.MinValue }
Validation = noErrors
Saved = false
}, Cmd.none
```3) In the Elmish `update` function, update the `Validation` state by calling the custom `Validate` method when saving:
```F#
let update msg model =
match msg with
| Save ->
let validation = model.Cat.Validate()
{ model with
Validation = validation
Saved = validation.HasErrors() = false
}, Toast.Cmd.success "Changes saved."
```4) In the form, set the `invalid` attributes of your inputs by checking the `model.Validation` state property for the given property:
```F#
SetCat { model.Cat with Name = e.target.Value } |> dispatch)}>
SetCat { model.Cat with Age = e.target?valueAsNumber } |> dispatch)}>
let date = System.DateTime.Parse(e.target.Value)
SetCat { model.Cat with LastVetCheckup = date } |> dispatch
)}>
```5) At the top of the form, display the validation errors using the `Ctrls.ValidationSummary`:
```F#
{ValidationSummary(model.Validation)}
```### WebApi.fs
6) The validation rules may also be reused on the server side:
```F#
let saveCatInfo(catInfo: CatInfo) =
match catInfo.Validate().IsValid() with
| true -> // save
| false -> // reject
```