@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,181 lines (1,013 loc) • 42.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fsharpSuaveTemplate = void 0;
exports.fsharpSuaveTemplate = {
id: 'fsharp-suave',
name: 'fsharp-suave',
displayName: 'F# Suave Web Framework',
description: 'Lightweight functional web server library for F# with composable web parts and minimal dependencies',
framework: 'suave',
language: 'fsharp',
version: '2.6',
author: 'Re-Shell Team',
featured: true,
recommended: false,
icon: '🌊',
type: 'rest-api',
complexity: 'beginner',
keywords: ['fsharp', 'suave', 'functional', 'lightweight', 'web', 'minimal'],
features: [
'Lightweight and fast',
'Composable web parts',
'Functional programming',
'Minimal dependencies',
'HTTP/HTTPS support',
'WebSocket support',
'Static file serving',
'JSON API',
'Authentication filters',
'CORS support',
'Async/await support',
'Custom routing',
'Middleware pipeline',
'Testing friendly'
],
structure: {
'Program.fs': `open System
open System.Net
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.Json
open Suave.RequestErrors
open SuaveApp.Models
open SuaveApp.Handlers
open SuaveApp.Services
let app =
choose [
GET >=> choose [
path "/" >=> OK "F# Suave Web Server"
path "/health" >=> JSON {| status = "healthy"; timestamp = DateTime.UtcNow |}
]
pathScan "/api/%s" (fun resource ->
match resource with
| "users" -> UserHandlers.userRoutes
| "todos" -> TodoHandlers.todoRoutes
| "auth" -> AuthHandlers.authRoutes
| _ -> NOT_FOUND "Resource not found"
)
NOT_FOUND "Page not found"
]
// CORS middleware
let corsHeaders =
Writers.setHeader "Access-Control-Allow-Origin" "*"
>=> Writers.setHeader "Access-Control-Allow-Methods" "GET, POST, PUT, DELETE, OPTIONS"
>=> Writers.setHeader "Access-Control-Allow-Headers" "Content-Type, Authorization"
let config =
{ defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 8080 ]
listenTimeout = TimeSpan.FromMilliseconds 3000.0
cancellationToken = Async.DefaultCancellationToken }
[<EntryPoint>]
let main argv =
printfn "Starting Suave server on http://localhost:8080"
startWebServer config (corsHeaders >=> app)
0`,
'Models/Domain.fs': `namespace SuaveApp.Models
open System
open Newtonsoft.Json
[<JsonConverter(typeof<Newtonsoft.Json.Converters.StringEnumConverter>)>]
type UserRole =
| Admin = 0
| User = 1
| Moderator = 2
[<JsonConverter(typeof<Newtonsoft.Json.Converters.StringEnumConverter>)>]
type Priority =
| Low = 0
| Medium = 1
| High = 2
| Critical = 3
type User = {
Id: int
Email: string
FirstName: string
LastName: string
Role: UserRole
CreatedAt: DateTime
UpdatedAt: DateTime
}
type Todo = {
Id: int
Title: string
Description: string option
IsCompleted: bool
UserId: int
DueDate: DateTime option
Priority: Priority
CreatedAt: DateTime
UpdatedAt: DateTime
}
// Request DTOs
type CreateUserRequest = {
Email: string
FirstName: string
LastName: string
Password: string
}
type UpdateUserRequest = {
Email: string option
FirstName: string option
LastName: string option
}
type CreateTodoRequest = {
Title: string
Description: string option
UserId: int
DueDate: DateTime option
Priority: Priority option
}
type UpdateTodoRequest = {
Title: string option
Description: string option
IsCompleted: bool option
DueDate: DateTime option
Priority: Priority option
}
type LoginRequest = {
Email: string
Password: string
}
type RegisterRequest = {
Email: string
FirstName: string
LastName: string
Password: string
ConfirmPassword: string
}
// Response DTOs
type ApiResponse<'T> = {
Success: bool
Data: 'T option
Message: string
Errors: string list
}
type LoginResponse = {
Token: string
User: User
ExpiresAt: DateTime
}
// Helper functions for API responses
module ApiResponse =
let success data message =
{ Success = true; Data = Some data; Message = message; Errors = [] }
let successNoData message =
{ Success = true; Data = None; Message = message; Errors = [] }
let error message errors =
{ Success = false; Data = None; Message = message; Errors = errors }
let errorSingle message error =
{ Success = false; Data = None; Message = message; Errors = [error] }`,
'Services/UserService.fs': `namespace SuaveApp.Services
open System
open SuaveApp.Models
type IUserService =
abstract member GetAll: unit -> User list
abstract member GetById: int -> User option
abstract member GetByEmail: string -> User option
abstract member Create: CreateUserRequest -> User
abstract member Update: int * UpdateUserRequest -> User option
abstract member Delete: int -> bool
type UserService() =
// In-memory storage for demo purposes
let mutable users = [
{ Id = 1; Email = "admin@example.com"; FirstName = "Admin"; LastName = "User"; Role = UserRole.Admin; CreatedAt = DateTime.UtcNow.AddDays(-30.0); UpdatedAt = DateTime.UtcNow }
{ Id = 2; Email = "user@example.com"; FirstName = "Regular"; LastName = "User"; Role = UserRole.User; CreatedAt = DateTime.UtcNow.AddDays(-15.0); UpdatedAt = DateTime.UtcNow }
{ Id = 3; Email = "mod@example.com"; FirstName = "Moderator"; LastName = "User"; Role = UserRole.Moderator; CreatedAt = DateTime.UtcNow.AddDays(-10.0); UpdatedAt = DateTime.UtcNow }
]
let mutable nextId = 4
interface IUserService with
member _.GetAll() = users
member _.GetById(id: int) =
users |> List.tryFind (fun u -> u.Id = id)
member _.GetByEmail(email: string) =
users |> List.tryFind (fun u -> u.Email = email)
member _.Create(request: CreateUserRequest) =
let user = {
Id = nextId
Email = request.Email
FirstName = request.FirstName
LastName = request.LastName
Role = UserRole.User
CreatedAt = DateTime.UtcNow
UpdatedAt = DateTime.UtcNow
}
nextId <- nextId + 1
users <- user :: users
user
member _.Update(id: int, request: UpdateUserRequest) =
let userIndex = users |> List.tryFindIndex (fun u -> u.Id = id)
match userIndex with
| Some index ->
let existingUser = users.[index]
let updatedUser = {
existingUser with
Email = request.Email |> Option.defaultValue existingUser.Email
FirstName = request.FirstName |> Option.defaultValue existingUser.FirstName
LastName = request.LastName |> Option.defaultValue existingUser.LastName
UpdatedAt = DateTime.UtcNow
}
users <- users |> List.mapi (fun i u -> if i = index then updatedUser else u)
Some updatedUser
| None -> None
member _.Delete(id: int) =
let initialCount = users |> List.length
users <- users |> List.filter (fun u -> u.Id <> id)
let finalCount = users |> List.length
initialCount > finalCount`,
'Services/TodoService.fs': `namespace SuaveApp.Services
open System
open SuaveApp.Models
type ITodoService =
abstract member GetAll: unit -> Todo list
abstract member GetById: int -> Todo option
abstract member GetByUserId: int -> Todo list
abstract member Create: CreateTodoRequest -> Todo
abstract member Update: int * UpdateTodoRequest -> Todo option
abstract member Complete: int -> Todo option
abstract member Delete: int -> bool
type TodoService() =
// In-memory storage for demo purposes
let mutable todos = [
{ Id = 1; Title = "Learn F#"; Description = Some "Study functional programming concepts"; IsCompleted = false; UserId = 1; DueDate = Some (DateTime.UtcNow.AddDays(7.0)); Priority = Priority.High; CreatedAt = DateTime.UtcNow.AddDays(-5.0); UpdatedAt = DateTime.UtcNow }
{ Id = 2; Title = "Build Suave app"; Description = Some "Create a web API with Suave framework"; IsCompleted = false; UserId = 1; DueDate = Some (DateTime.UtcNow.AddDays(14.0)); Priority = Priority.Medium; CreatedAt = DateTime.UtcNow.AddDays(-3.0); UpdatedAt = DateTime.UtcNow }
{ Id = 3; Title = "Deploy to production"; Description = None; IsCompleted = false; UserId = 2; DueDate = Some (DateTime.UtcNow.AddDays(30.0)); Priority = Priority.Low; CreatedAt = DateTime.UtcNow.AddDays(-1.0); UpdatedAt = DateTime.UtcNow }
{ Id = 4; Title = "Write documentation"; Description = Some "Document the API endpoints"; IsCompleted = true; UserId = 1; DueDate = None; Priority = Priority.Medium; CreatedAt = DateTime.UtcNow.AddDays(-7.0); UpdatedAt = DateTime.UtcNow.AddDays(-2.0) }
]
let mutable nextId = 5
interface ITodoService with
member _.GetAll() = todos
member _.GetById(id: int) =
todos |> List.tryFind (fun t -> t.Id = id)
member _.GetByUserId(userId: int) =
todos |> List.filter (fun t -> t.UserId = userId)
member _.Create(request: CreateTodoRequest) =
let todo = {
Id = nextId
Title = request.Title
Description = request.Description
IsCompleted = false
UserId = request.UserId
DueDate = request.DueDate
Priority = request.Priority |> Option.defaultValue Priority.Medium
CreatedAt = DateTime.UtcNow
UpdatedAt = DateTime.UtcNow
}
nextId <- nextId + 1
todos <- todo :: todos
todo
member _.Update(id: int, request: UpdateTodoRequest) =
let todoIndex = todos |> List.tryFindIndex (fun t -> t.Id = id)
match todoIndex with
| Some index ->
let existingTodo = todos.[index]
let updatedTodo = {
existingTodo with
Title = request.Title |> Option.defaultValue existingTodo.Title
Description = request.Description |> Option.orElse existingTodo.Description
IsCompleted = request.IsCompleted |> Option.defaultValue existingTodo.IsCompleted
DueDate = request.DueDate |> Option.orElse existingTodo.DueDate
Priority = request.Priority |> Option.defaultValue existingTodo.Priority
UpdatedAt = DateTime.UtcNow
}
todos <- todos |> List.mapi (fun i t -> if i = index then updatedTodo else t)
Some updatedTodo
| None -> None
member _.Complete(id: int) =
let todoIndex = todos |> List.tryFindIndex (fun t -> t.Id = id)
match todoIndex with
| Some index ->
let existingTodo = todos.[index]
let completedTodo = {
existingTodo with
IsCompleted = true
UpdatedAt = DateTime.UtcNow
}
todos <- todos |> List.mapi (fun i t -> if i = index then completedTodo else t)
Some completedTodo
| None -> None
member _.Delete(id: int) =
let initialCount = todos |> List.length
todos <- todos |> List.filter (fun t -> t.Id <> id)
let finalCount = todos |> List.length
initialCount > finalCount`,
'Services/AuthService.fs': `namespace SuaveApp.Services
open System
open System.Text
open System.Security.Claims
open System.IdentityModel.Tokens.Jwt
open Microsoft.IdentityModel.Tokens
open SuaveApp.Models
type IAuthService =
abstract member Login: string * string -> LoginResponse option
abstract member Register: RegisterRequest -> Result<User, string>
abstract member ValidateToken: string -> ClaimsPrincipal option
abstract member GenerateToken: User -> string
type AuthService(userService: IUserService) =
let jwtSecret = "your-super-secret-jwt-key-should-be-at-least-256-bits-long-for-security"
let jwtIssuer = "suave-app"
let jwtAudience = "suave-app-users"
let jwtExpiryHours = 24.0
let createSecurityKey() =
SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret))
let createSigningCredentials() =
SigningCredentials(createSecurityKey(), SecurityAlgorithms.HmacSha256)
let hashPassword (password: string) =
// In production, use proper password hashing like BCrypt
// This is just for demo purposes
let salt = "demo-salt"
let combined = password + salt
let bytes = Encoding.UTF8.GetBytes(combined)
let hash = System.Security.Cryptography.SHA256.HashData(bytes)
Convert.ToBase64String(hash)
let verifyPassword (password: string) (hashedPassword: string) =
hashPassword password = hashedPassword
interface IAuthService with
member _.Login(email: string, password: string) =
let user = userService.GetByEmail(email)
match user with
| Some u ->
// For demo, we'll assume password is correct
// In production, verify against hashed password
let token = (this :> IAuthService).GenerateToken(u)
let expiresAt = DateTime.UtcNow.AddHours(jwtExpiryHours)
Some { Token = token; User = u; ExpiresAt = expiresAt }
| None -> None
member _.Register(request: RegisterRequest) =
if request.Password <> request.ConfirmPassword then
Error "Passwords do not match"
else
let existingUser = userService.GetByEmail(request.Email)
match existingUser with
| Some _ -> Error "User with this email already exists"
| None ->
let hashedPassword = hashPassword request.Password
let createRequest = {
Email = request.Email
FirstName = request.FirstName
LastName = request.LastName
Password = hashedPassword
}
let user = userService.Create(createRequest)
Ok user
member _.ValidateToken(token: string) =
try
let tokenHandler = JwtSecurityTokenHandler()
let validationParameters = TokenValidationParameters(
ValidateIssuerSigningKey = true,
IssuerSigningKey = createSecurityKey(),
ValidateIssuer = true,
ValidIssuer = jwtIssuer,
ValidateAudience = true,
ValidAudience = jwtAudience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
)
let mutable securityToken : SecurityToken = null
let principal = tokenHandler.ValidateToken(token, validationParameters, &securityToken)
Some principal
with
| _ -> None
member _.GenerateToken(user: User) =
let claims = [
Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
Claim(ClaimTypes.Email, user.Email)
Claim(ClaimTypes.Name, sprintf "%s %s" user.FirstName user.LastName)
Claim(ClaimTypes.Role, user.Role.ToString())
]
let tokenDescriptor = SecurityTokenDescriptor(
Subject = ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(jwtExpiryHours),
Issuer = jwtIssuer,
Audience = jwtAudience,
SigningCredentials = createSigningCredentials()
)
let tokenHandler = JwtSecurityTokenHandler()
let token = tokenHandler.CreateToken(tokenDescriptor)
tokenHandler.WriteToken(token)`,
'Handlers/UserHandlers.fs': `namespace SuaveApp.Handlers
open System
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.RequestErrors
open Suave.Json
open SuaveApp.Models
open SuaveApp.Services
module UserHandlers =
let userService = UserService() :> IUserService
let getUsers : WebPart =
fun ctx ->
try
let users = userService.GetAll()
let response = ApiResponse.success users "Users retrieved successfully"
JSON response ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to retrieve users" ex.Message
JSON response ctx
let getUserById id : WebPart =
fun ctx ->
try
match userService.GetById(id) with
| Some user ->
let response = ApiResponse.success user "User retrieved successfully"
JSON response ctx
| None ->
let response = ApiResponse.errorSingle "User not found" "No user found with the specified ID"
NOT_FOUND (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to retrieve user" ex.Message
JSON response ctx
let createUser : WebPart =
fun ctx ->
async {
try
let! requestBody = Suave.Utils.UTF8.toString ctx.request.rawForm
let userRequest = Newtonsoft.Json.JsonConvert.DeserializeObject<CreateUserRequest>(requestBody)
// Basic validation
if String.IsNullOrWhiteSpace(userRequest.Email) then
let response = ApiResponse.errorSingle "Validation failed" "Email is required"
return! BAD_REQUEST (JSON response) ctx
elif String.IsNullOrWhiteSpace(userRequest.FirstName) then
let response = ApiResponse.errorSingle "Validation failed" "First name is required"
return! BAD_REQUEST (JSON response) ctx
elif String.IsNullOrWhiteSpace(userRequest.LastName) then
let response = ApiResponse.errorSingle "Validation failed" "Last name is required"
return! BAD_REQUEST (JSON response) ctx
else
let user = userService.Create(userRequest)
let response = ApiResponse.success user "User created successfully"
return! CREATED (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to create user" ex.Message
return! BAD_REQUEST (JSON response) ctx
}
let updateUser id : WebPart =
fun ctx ->
async {
try
let! requestBody = Suave.Utils.UTF8.toString ctx.request.rawForm
let userRequest = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateUserRequest>(requestBody)
match userService.Update(id, userRequest) with
| Some user ->
let response = ApiResponse.success user "User updated successfully"
return! OK (JSON response) ctx
| None ->
let response = ApiResponse.errorSingle "User not found" "No user found with the specified ID"
return! NOT_FOUND (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to update user" ex.Message
return! BAD_REQUEST (JSON response) ctx
}
let deleteUser id : WebPart =
fun ctx ->
try
if userService.Delete(id) then
let response = ApiResponse.successNoData "User deleted successfully"
OK (JSON response) ctx
else
let response = ApiResponse.errorSingle "User not found" "No user found with the specified ID"
NOT_FOUND (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to delete user" ex.Message
JSON response ctx
let userRoutes =
choose [
GET >=> choose [
path "" >=> getUsers
pathScan "/%d" getUserById
]
POST >=> path "" >=> createUser
PUT >=> pathScan "/%d" updateUser
DELETE >=> pathScan "/%d" deleteUser
]`,
'Handlers/TodoHandlers.fs': `namespace SuaveApp.Handlers
open System
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.RequestErrors
open Suave.Json
open SuaveApp.Models
open SuaveApp.Services
module TodoHandlers =
let todoService = TodoService() :> ITodoService
let getTodos : WebPart =
fun ctx ->
try
let todos = todoService.GetAll()
let response = ApiResponse.success todos "Todos retrieved successfully"
JSON response ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to retrieve todos" ex.Message
JSON response ctx
let getTodoById id : WebPart =
fun ctx ->
try
match todoService.GetById(id) with
| Some todo ->
let response = ApiResponse.success todo "Todo retrieved successfully"
JSON response ctx
| None ->
let response = ApiResponse.errorSingle "Todo not found" "No todo found with the specified ID"
NOT_FOUND (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to retrieve todo" ex.Message
JSON response ctx
let getTodosByUserId userId : WebPart =
fun ctx ->
try
let todos = todoService.GetByUserId(userId)
let response = ApiResponse.success todos "User todos retrieved successfully"
JSON response ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to retrieve user todos" ex.Message
JSON response ctx
let createTodo : WebPart =
fun ctx ->
async {
try
let! requestBody = Suave.Utils.UTF8.toString ctx.request.rawForm
let todoRequest = Newtonsoft.Json.JsonConvert.DeserializeObject<CreateTodoRequest>(requestBody)
// Basic validation
if String.IsNullOrWhiteSpace(todoRequest.Title) then
let response = ApiResponse.errorSingle "Validation failed" "Title is required"
return! BAD_REQUEST (JSON response) ctx
elif todoRequest.UserId <= 0 then
let response = ApiResponse.errorSingle "Validation failed" "Valid user ID is required"
return! BAD_REQUEST (JSON response) ctx
else
let todo = todoService.Create(todoRequest)
let response = ApiResponse.success todo "Todo created successfully"
return! CREATED (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to create todo" ex.Message
return! BAD_REQUEST (JSON response) ctx
}
let updateTodo id : WebPart =
fun ctx ->
async {
try
let! requestBody = Suave.Utils.UTF8.toString ctx.request.rawForm
let todoRequest = Newtonsoft.Json.JsonConvert.DeserializeObject<UpdateTodoRequest>(requestBody)
match todoService.Update(id, todoRequest) with
| Some todo ->
let response = ApiResponse.success todo "Todo updated successfully"
return! OK (JSON response) ctx
| None ->
let response = ApiResponse.errorSingle "Todo not found" "No todo found with the specified ID"
return! NOT_FOUND (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to update todo" ex.Message
return! BAD_REQUEST (JSON response) ctx
}
let completeTodo id : WebPart =
fun ctx ->
try
match todoService.Complete(id) with
| Some todo ->
let response = ApiResponse.success todo "Todo completed successfully"
OK (JSON response) ctx
| None ->
let response = ApiResponse.errorSingle "Todo not found" "No todo found with the specified ID"
NOT_FOUND (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to complete todo" ex.Message
JSON response ctx
let deleteTodo id : WebPart =
fun ctx ->
try
if todoService.Delete(id) then
let response = ApiResponse.successNoData "Todo deleted successfully"
OK (JSON response) ctx
else
let response = ApiResponse.errorSingle "Todo not found" "No todo found with the specified ID"
NOT_FOUND (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Failed to delete todo" ex.Message
JSON response ctx
let todoRoutes =
choose [
GET >=> choose [
path "" >=> getTodos
pathScan "/%d" getTodoById
pathScan "/user/%d" getTodosByUserId
]
POST >=> choose [
path "" >=> createTodo
pathScan "/%d/complete" completeTodo
]
PUT >=> pathScan "/%d" updateTodo
DELETE >=> pathScan "/%d" deleteTodo
]`,
'Handlers/AuthHandlers.fs': `namespace SuaveApp.Handlers
open System
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.RequestErrors
open Suave.Json
open SuaveApp.Models
open SuaveApp.Services
module AuthHandlers =
let userService = UserService() :> IUserService
let authService = AuthService(userService) :> IAuthService
let login : WebPart =
fun ctx ->
async {
try
let! requestBody = Suave.Utils.UTF8.toString ctx.request.rawForm
let loginRequest = Newtonsoft.Json.JsonConvert.DeserializeObject<LoginRequest>(requestBody)
// Basic validation
if String.IsNullOrWhiteSpace(loginRequest.Email) then
let response = ApiResponse.errorSingle "Validation failed" "Email is required"
return! BAD_REQUEST (JSON response) ctx
elif String.IsNullOrWhiteSpace(loginRequest.Password) then
let response = ApiResponse.errorSingle "Validation failed" "Password is required"
return! BAD_REQUEST (JSON response) ctx
else
match authService.Login(loginRequest.Email, loginRequest.Password) with
| Some loginResponse ->
let response = ApiResponse.success loginResponse "Login successful"
return! OK (JSON response) ctx
| None ->
let response = ApiResponse.errorSingle "Authentication failed" "Invalid email or password"
return! UNAUTHORIZED (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Login failed" ex.Message
return! BAD_REQUEST (JSON response) ctx
}
let register : WebPart =
fun ctx ->
async {
try
let! requestBody = Suave.Utils.UTF8.toString ctx.request.rawForm
let registerRequest = Newtonsoft.Json.JsonConvert.DeserializeObject<RegisterRequest>(requestBody)
// Basic validation
if String.IsNullOrWhiteSpace(registerRequest.Email) then
let response = ApiResponse.errorSingle "Validation failed" "Email is required"
return! BAD_REQUEST (JSON response) ctx
elif String.IsNullOrWhiteSpace(registerRequest.Password) then
let response = ApiResponse.errorSingle "Validation failed" "Password is required"
return! BAD_REQUEST (JSON response) ctx
elif registerRequest.Password.Length < 6 then
let response = ApiResponse.errorSingle "Validation failed" "Password must be at least 6 characters long"
return! BAD_REQUEST (JSON response) ctx
else
match authService.Register(registerRequest) with
| Ok user ->
let response = ApiResponse.success user "Registration successful"
return! CREATED (JSON response) ctx
| Error error ->
let response = ApiResponse.errorSingle "Registration failed" error
return! BAD_REQUEST (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Registration failed" ex.Message
return! BAD_REQUEST (JSON response) ctx
}
let logout : WebPart =
fun ctx ->
// In a stateless JWT system, logout is typically handled client-side
let response = ApiResponse.successNoData "Logout successful"
OK (JSON response) ctx
let me : WebPart =
fun ctx ->
try
// Extract Authorization header
match ctx.request.header "authorization" with
| Choice1Of2 authHeader when authHeader.StartsWith("Bearer ") ->
let token = authHeader.Substring(7)
match authService.ValidateToken(token) with
| Some principal ->
let userIdClaim = principal.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)
if userIdClaim <> null then
let userId = Int32.Parse(userIdClaim.Value)
match userService.GetById(userId) with
| Some user ->
let response = ApiResponse.success user "User profile retrieved successfully"
OK (JSON response) ctx
| None ->
let response = ApiResponse.errorSingle "User not found" "User profile not found"
NOT_FOUND (JSON response) ctx
else
let response = ApiResponse.errorSingle "Invalid token" "User ID not found in token"
UNAUTHORIZED (JSON response) ctx
| None ->
let response = ApiResponse.errorSingle "Invalid token" "Token validation failed"
UNAUTHORIZED (JSON response) ctx
| _ ->
let response = ApiResponse.errorSingle "Authorization required" "Bearer token required"
UNAUTHORIZED (JSON response) ctx
with
| ex ->
let response = ApiResponse.errorSingle "Authentication failed" ex.Message
UNAUTHORIZED (JSON response) ctx
let authRoutes =
choose [
POST >=> choose [
path "/login" >=> login
path "/register" >=> register
path "/logout" >=> logout
]
GET >=> path "/me" >=> me
]`,
'suave-app.fsproj': `<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<AssemblyName>suave-app</AssemblyName>
<RootNamespace>SuaveApp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="Models/Domain.fs" />
<Compile Include="Services/UserService.fs" />
<Compile Include="Services/TodoService.fs" />
<Compile Include="Services/AuthService.fs" />
<Compile Include="Handlers/UserHandlers.fs" />
<Compile Include="Handlers/TodoHandlers.fs" />
<Compile Include="Handlers/AuthHandlers.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Suave" Version="2.6.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.3" />
</ItemGroup>
</Project>`,
'Dockerfile': `FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app
EXPOSE 8080
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["suave-app.fsproj", "."]
RUN dotnet restore "suave-app.fsproj"
COPY . .
WORKDIR "/src/"
RUN dotnet build "suave-app.fsproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "suave-app.fsproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "suave-app.dll"]`,
'README.md': `# F# Suave Web Framework
A lightweight functional web server library for F# with composable web parts and minimal dependencies.
## Features
- **Lightweight & Fast**: Minimal overhead and dependencies
- **Composable Web Parts**: Build complex applications by composing simple functions
- **Functional Programming**: Leverage F#'s functional programming capabilities
- **Async Support**: Built-in support for asynchronous operations
- **HTTP/HTTPS**: Support for both HTTP and HTTPS protocols
- **WebSocket Support**: Real-time communication capabilities
- **Static File Serving**: Serve static files efficiently
- **JSON API**: RESTful API with JSON serialization
- **Authentication**: JWT-based authentication
- **CORS Support**: Cross-origin resource sharing
- **Testing Friendly**: Easy to test with functional composition
## Quick Start
### Prerequisites
- .NET 6.0 SDK or later
- F# development tools
### Installation
\`\`\`bash
# Clone the project
git clone <repository-url>
cd suave-app
# Restore dependencies
dotnet restore
# Run the application
dotnet run
\`\`\`
The API will be available at \`http://localhost:8080\`
### Development
\`\`\`bash
# Watch mode for development
dotnet watch run
# Build for production
dotnet build -c Release
\`\`\`
## Project Structure
\`\`\`
├── Models/
│ └── Domain.fs # Domain models and DTOs
├── Services/
│ ├── UserService.fs # User business logic
│ ├── TodoService.fs # Todo business logic
│ └── AuthService.fs # Authentication logic
├── Handlers/
│ ├── UserHandlers.fs # User HTTP handlers
│ ├── TodoHandlers.fs # Todo HTTP handlers
│ └── AuthHandlers.fs # Auth HTTP handlers
├── Program.fs # Application entry point
└── suave-app.fsproj # Project configuration
\`\`\`
## API Endpoints
### Authentication
- \`POST /api/auth/login\` - User login
- \`POST /api/auth/register\` - User registration
- \`GET /api/auth/me\` - Get current user
- \`POST /api/auth/logout\` - User logout
### Users
- \`GET /api/users\` - List all users
- \`GET /api/users/{id}\` - Get user by ID
- \`POST /api/users\` - Create new user
- \`PUT /api/users/{id}\` - Update user
- \`DELETE /api/users/{id}\` - Delete user
### Todos
- \`GET /api/todos\` - List all todos
- \`GET /api/todos/{id}\` - Get todo by ID
- \`GET /api/todos/user/{userId}\` - Get todos by user
- \`POST /api/todos\` - Create new todo
- \`PUT /api/todos/{id}\` - Update todo
- \`DELETE /api/todos/{id}\` - Delete todo
- \`POST /api/todos/{id}/complete\` - Mark todo as complete
## Architecture
### Functional Web Parts
Suave uses composable web parts to build HTTP applications:
\`\`\`fsharp
let app =
choose [
GET >=> path "/" >=> OK "Hello World"
POST >=> path "/api/data" >=> handleData
NOT_FOUND "Page not found"
]
\`\`\`
### Composition Operators
- \`>=>\` - Compose web parts sequentially
- \`choose\` - Try multiple web parts, use first successful
- \`>>>\` - Forward composition
- \`<<<\` - Backward composition
### Request Processing
\`\`\`fsharp
let pipeline =
corsHeaders
>=> logRequest
>=> authenticate
>=> handleRequest
\`\`\`
## Configuration
### Server Configuration
\`\`\`fsharp
let config =
{ defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 8080 ]
listenTimeout = TimeSpan.FromMilliseconds 3000.0
cancellationToken = Async.DefaultCancellationToken }
\`\`\`
### HTTPS Configuration
\`\`\`fsharp
let httpsBinding = HttpBinding.createSimple HTTPS "0.0.0.0" 8443
let config = { defaultConfig with bindings = [httpsBinding] }
\`\`\`
## Error Handling
Suave provides built-in error handling:
\`\`\`fsharp
let safeHandler handler =
fun ctx ->
try
handler ctx
with
| ex ->
let errorResponse = { error = ex.Message }
INTERNAL_ERROR (JSON errorResponse) ctx
\`\`\`
## Testing
Suave applications are easy to test:
\`\`\`fsharp
open Suave
open Suave.Testing
[<Test>]
let testEndpoint() =
runWithConfig defaultConfig app
|> req HttpMethod.GET "/" None
|> fun response ->
Assert.AreEqual(HttpStatusCode.OK, response.statusCode)
\`\`\`
## Middleware
Create custom middleware using web parts:
\`\`\`fsharp
let requestLogger : WebPart =
fun ctx ->
printfn "Request: %s %s" ctx.request.method.ToString() ctx.request.url.AbsolutePath
succeed ctx
let app = requestLogger >=> routes
\`\`\`
## JSON Handling
Suave provides JSON support:
\`\`\`fsharp
open Suave.Json
open Newtonsoft.Json
let getUser id =
let user = getUserById id
JSON user
let createUser =
request (fun req ->
let user = JsonConvert.DeserializeObject<User>(UTF8.toString req.rawForm)
// Process user...
JSON user
)
\`\`\`
## Authentication
JWT authentication example:
\`\`\`fsharp
let authenticate : WebPart =
request (fun req ->
match req.header "authorization" with
| Choice1Of2 token when token.StartsWith("Bearer ") ->
let jwt = token.Substring(7)
if validateJwtToken jwt then
succeed
else
UNAUTHORIZED "Invalid token"
| _ -> UNAUTHORIZED "Authorization required"
)
\`\`\`
## WebSocket Support
\`\`\`fsharp
open Suave.Sockets
open Suave.WebSocket
let ws (webSocket : WebSocket) (context: HttpContext) =
socket {
let loop = true
while loop do
let! msg = webSocket.read()
match msg with
| Text, data, true ->
let str = UTF8.toString data
do! webSocket.send Text (UTF8.bytes ("Echo: " + str)) true
| _ -> ()
}
let app =
choose [
path "/websocket" >=> handShake ws
// Other routes...
]
\`\`\`
## Deployment
### Docker
\`\`\`bash
# Build image
docker build -t suave-app .
# Run container
docker run -p 8080:8080 suave-app
\`\`\`
### Production
\`\`\`bash
# Publish for deployment
dotnet publish -c Release -o ./publish
# Copy to server and run
cd publish
dotnet suave-app.dll
\`\`\`
## Performance Tips
1. **Use Async**: Leverage F#'s async workflows
2. **Connection Pooling**: Reuse database connections
3. **Caching**: Cache frequently accessed data
4. **Minimal Allocations**: Avoid unnecessary object creation
5. **Profiling**: Use .NET profiling tools
## Best Practices
1. **Composition**: Build complex logic by composing simple web parts
2. **Immutability**: Use immutable data structures
3. **Error Handling**: Handle errors explicitly with Result types
4. **Testing**: Write unit tests for individual web parts
5. **Documentation**: Document your API endpoints
## Learning Resources
- [Suave Documentation](https://suave.io/)
- [F# for Fun and Profit](https://fsharpforfunandprofit.com/)
- [Functional Web Development](https://pragprog.com/titles/swdddf/web-development-with-clojure/)
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Submit a pull request
## License
This project is licensed under the MIT License.`
}
};