Skip to content
Back to skills

Fsharp Backend

ASecurity

Implement F# backend using Giraffe + Fable.Remoting with proper layer separation. Use when: "implement backend", "add API", "create endpoint", "server logic", "business rules", "backend for X", "API implementation", "server-side", "Giraffe", "Fable.Remoting". Layers: Validation → Domain (pure) → Persistence (I/O) → API. Creates code in src/Server/: Validation.fs, Domain.fs, Persistence.fs, Api.fs.

  • 666 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added February 8, 2026
businessgobashsqltestingapidatabasebackenddocumentation

Works with

  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill fsharp-backend --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fsharp Backend?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fsharp Backend
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-fsharp-backend/badge)](https://www.skillsdirectory.com/skills/majiayu000-fsharp-backend)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fsharp-backend
description: |
  Implement F# backend using Giraffe + Fable.Remoting with proper layer separation.
  Use when: "implement backend", "add API", "create endpoint", "server logic", "business rules",
  "backend for X", "API implementation", "server-side", "Giraffe", "Fable.Remoting".
  Layers: Validation → Domain (pure) → Persistence (I/O) → API.
  Creates code in src/Server/: Validation.fs, Domain.fs, Persistence.fs, Api.fs.
allowed-tools: Read, Edit, Write, Grep, Glob, Bash
---

# F# Backend Implementation

## When to Use This Skill

Activate when:
- User requests "implement backend for X"
- Need to add API endpoints
- Implementing business logic
- Creating server-side functionality
- Project has src/Server/ directory with Giraffe

## Architecture Layers

```
API (Fable.Remoting)
    ↓
Validation (Input checking)
    ↓
Domain (Pure business logic - NO I/O)
    ↓
Persistence (Database/File I/O)
```

## Layer 1: Validation (`src/Server/Validation.fs`)

**Purpose:** Validate input at API boundary before processing

> For detailed validation patterns, see the **fsharp-validation** skill.

### Basic Pattern
```fsharp
module Validation

open Shared.Domain

let validateCreateRequest (req: CreateTodoRequest) : Result<CreateTodoRequest, string list> =
    let errors = [
        if String.IsNullOrWhiteSpace(req.Title) then "Title is required"
        if req.Title.Length > 100 then "Title too long"
    ]
    if errors.IsEmpty then Ok req else Error errors
```

**Key points:**
- Validate at API boundary, before domain logic
- Return `Result<'T, string list>` to accumulate errors
- Convert to single string for API: `String.concat "; " errors`

## Layer 2: Domain (`src/Server/Domain.fs`)

**Purpose:** Pure business logic - NO side effects, NO I/O

### Pattern
```fsharp
module Domain

open System
open Shared.Domain

// ✅ PURE transformations only
let processNewTodo (request: CreateTodoRequest) : TodoItem =
    {
        Id = 0  // Will be set by persistence
        Title = request.Title.Trim()
        Description = request.Description |> Option.map (fun d -> d.Trim())
        Priority = request.Priority
        Status = Active
        CreatedAt = DateTime.UtcNow
        UpdatedAt = DateTime.UtcNow
    }

let completeTodo (item: TodoItem) : TodoItem =
    { item with Status = Completed; UpdatedAt = DateTime.UtcNow }

let updateTodo (existing: TodoItem) (updates: CreateTodoRequest) : TodoItem =
    {
        existing with
            Title = updates.Title.Trim()
            Description = updates.Description |> Option.map (fun d -> d.Trim())
            Priority = updates.Priority
            UpdatedAt = DateTime.UtcNow
    }

// ✅ PURE calculations
let calculatePriority (items: TodoItem list) : Priority =
    items
    |> List.map (fun item -> item.Priority)
    |> List.maxBy (function Urgent -> 4 | High -> 3 | Medium -> 2 | Low -> 1)
```

**CRITICAL:**
```fsharp
// ❌ BAD - I/O in domain
let processItem itemId =
    let item = Persistence.getItem itemId  // NO!
    { item with Status = Processed }

// ✅ GOOD - Pure function
let processItem item =
    { item with Status = Processed }
```

## Layer 3: Persistence (`src/Server/Persistence.fs`)

**Purpose:** All database and file I/O operations

### SQLite with Dapper
```fsharp
module Persistence

open System.Data
open Microsoft.Data.Sqlite
open Dapper
open Shared.Domain

let connectionString = "Data Source=./data/app.db"
let getConnection () = new SqliteConnection(connectionString)

let initializeDatabase () =
    use conn = getConnection()
    conn.Execute("""
        CREATE TABLE IF NOT EXISTS TodoItems (
            Id INTEGER PRIMARY KEY AUTOINCREMENT,
            Title TEXT NOT NULL,
            Description TEXT,
            Priority INTEGER NOT NULL,
            Status INTEGER NOT NULL,
            CreatedAt TEXT NOT NULL,
            UpdatedAt TEXT NOT NULL
        )
    """) |> ignore

// READ operations
let getAllTodos () : Async<TodoItem list> =
    async {
        use conn = getConnection()
        let! todos = conn.QueryAsync<TodoItem>(
            "SELECT * FROM TodoItems ORDER BY CreatedAt DESC"
        ) |> Async.AwaitTask
        return todos |> Seq.toList
    }

let getTodoById (id: int) : Async<TodoItem option> =
    async {
        use conn = getConnection()
        let! todo = conn.QuerySingleOrDefaultAsync<TodoItem>(
            "SELECT * FROM TodoItems WHERE Id = @Id",
            {| Id = id |}
        ) |> Async.AwaitTask
        return if isNull (box todo) then None else Some todo
    }

// WRITE operations
let insertTodo (todo: TodoItem) : Async<TodoItem> =
    async {
        use conn = getConnection()
        let! id = conn.ExecuteScalarAsync<int64>(
            """INSERT INTO TodoItems (Title, Description, Priority, Status, CreatedAt, UpdatedAt)
               VALUES (@Title, @Description, @Priority, @Status, @CreatedAt, @UpdatedAt)
               RETURNING Id""",
            {|
                Title = todo.Title
                Description = todo.Description
                Priority = int todo.Priority
                Status = int todo.Status
                CreatedAt = todo.CreatedAt.ToString("o")
                UpdatedAt = todo.UpdatedAt.ToString("o")
            |}
        ) |> Async.AwaitTask
        return { todo with Id = int id }
    }

let updateTodo (todo: TodoItem) : Async<unit> =
    async {
        use conn = getConnection()
        let! _ = conn.ExecuteAsync(
            """UPDATE TodoItems
               SET Title = @Title, Description = @Description,
                   Priority = @Priority, Status = @Status, UpdatedAt = @UpdatedAt
               WHERE Id = @Id""",
            todo
        ) |> Async.AwaitTask
        return ()
    }

let deleteTodo (id: int) : Async<unit> =
    async {
        use conn = getConnection()
        let! _ = conn.ExecuteAsync(
            "DELETE FROM TodoItems WHERE Id = @Id",
            {| Id = id |}
        ) |> Async.AwaitTask
        return ()
    }
```

**Key points:**
- Use parameterized queries (SQL injection prevention)
- Always use `async` for I/O
- Dispose connections (`use` keyword)
- Return options for "not found" cases

## Layer 4: API (`src/Server/Api.fs`)

**Purpose:** Implement Fable.Remoting contracts, orchestrate layers

### Pattern
```fsharp
module Api

open Fable.Remoting.Server
open Fable.Remoting.Giraffe
open Shared.Api
open Shared.Domain

let todoApi : ITodoApi = {
    getAll = fun () ->
        Persistence.getAllTodos()

    getById = fun id -> async {
        match! Persistence.getTodoById id with
        | Some todo -> return Ok todo
        | None -> return Error "Todo not found"
    }

    create = fun request -> async {
        // 1. Validate
        match Validation.validateCreateRequest request with
        | Error errors -> return Error (String.concat "; " errors)
        | Ok validRequest ->
            // 2. Process with domain logic
            let newTodo = Domain.processNewTodo validRequest

            // 3. Persist
            let! saved = Persistence.insertTodo newTodo
            return Ok saved
    }

    update = fun todo -> async {
        // 1. Validate
        match Validation.validateTodoItem todo with
        | Error errors -> return Error (String.concat "; " errors)
        | Ok validTodo ->
            // 2. Check exists
            match! Persistence.getTodoById todo.Id with
            | None -> return Error "Todo not found"
            | Some existing ->
                // 3. Process with domain logic
                let updated = Domain.updateTodo existing validTodo

                // 4. Persist
                do! Persistence.updateTodo updated
                return Ok updated
    }

    complete = fun id -> async {
        match! Persistence.getTodoById id with
        | None -> return Error "Todo not found"
        | Some todo ->
            let completed = Domain.completeTodo todo
            do! Persistence.updateTodo completed
            return Ok completed
    }

    delete = fun id -> async {
        do! Persistence.deleteTodo id
        return Ok ()
    }
}

// Create HTTP handler
let webApp =
    Remoting.createApi()
    |> Remoting.fromValue todoApi
    |> Remoting.buildHttpHandler
```

**Orchestration pattern:**
1. Validate input
2. Return error if invalid
3. Transform with domain logic (pure)
4. Perform persistence operations
5. Return result

### Error Handling Patterns

**Not Found:**
```fsharp
getById = fun id -> async {
    match! Persistence.getById id with
    | Some item -> return Ok item
    | None -> return Error "Not found"
}
```

**Validation Errors:**
```fsharp
save = fun item -> async {
    match Validation.validate item with
    | Error errs -> return Error (String.concat "; " errs)
    | Ok valid ->
        do! Persistence.save valid
        return Ok valid
}
```

**Exception Handling:**
```fsharp
save = fun item -> async {
    try
        do! Persistence.save item
        return Ok item
    with
    | ex -> return Error $"Failed to save: {ex.Message}"
}
```

## Integration with Program.fs

```fsharp
module Program

open Microsoft.AspNetCore.Builder
open Giraffe

let configureApp (app: IApplicationBuilder) =
    // Initialize persistence
    Persistence.ensureDataDir()
    Persistence.initializeDatabase()

    app.UseStaticFiles() |> ignore
    app.UseRouting() |> ignore
    app.UseGiraffe(Api.webApp)

[<EntryPoint>]
let main args =
    let builder = WebApplication.CreateBuilder(args)
    builder.Services.AddGiraffe() |> ignore

    let app = builder.Build()
    configureApp app
    app.Run()
    0
```

## Complete Example

```fsharp
// Validation.fs
module Validation
open Shared.Domain

let validateCreateRequest (req: CreateTodoRequest) =
    let errors = [
        if String.IsNullOrWhiteSpace(req.Title) then "Title required"
        if req.Title.Length > 100 then "Title too long"
    ]
    if errors.IsEmpty then Ok req else Error errors

// Domain.fs
module Domain
open System
open Shared.Domain

let processNewTodo (req: CreateTodoRequest) : TodoItem =
    {
        Id = 0
        Title = req.Title.Trim()
        Description = req.Description
        Priority = req.Priority
        Status = Active
        CreatedAt = DateTime.UtcNow
        UpdatedAt = DateTime.UtcNow
    }

// Persistence.fs (SQLite example above)

// Api.fs
module Api
open Shared.Api

let todoApi : ITodoApi = {
    create = fun request -> async {
        match Validation.validateCreateRequest request with
        | Error errs -> return Error (String.concat "; " errs)
        | Ok valid ->
            let todo = Domain.processNewTodo valid
            let! saved = Persistence.insertTodo todo
            return Ok saved
    }
}
```

## Verification Checklist

- [ ] Validation in `src/Server/Validation.fs`
- [ ] Domain logic in `src/Server/Domain.fs` (PURE - no I/O)
- [ ] Persistence in `src/Server/Persistence.fs`
- [ ] API implementation in `src/Server/Api.fs`
- [ ] Proper error handling (Result types)
- [ ] Database initialized (if using SQLite)
- [ ] All async operations used for I/O
- [ ] No I/O in domain layer
- [ ] Parameterized queries (SQL injection prevention)

## Common Pitfalls

❌ **Don't:**
- Put database calls in Domain.fs
- Skip validation
- Use string concatenation for SQL queries
- Forget to dispose database connections
- Mix concerns across layers

✅ **Do:**
- Keep domain logic pure
- Validate at API boundary
- Use parameterized queries
- Handle all error cases explicitly
- Follow layer responsibilities strictly

## Related Skills

- **fsharp-validation** - Detailed validation patterns
- **fsharp-persistence** - More persistence patterns
- **fsharp-tests** - Testing backend logic
- **fsharp-shared** - Type definitions

## Related Documentation

- `/docs/03-BACKEND-GUIDE.md` - Detailed backend guide
- `/docs/05-PERSISTENCE.md` - Persistence patterns
- `/docs/09-QUICK-REFERENCE.md` - Quick templates

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.