Routing & Handlers
Learn how to define routes, handle HTTP requests, and build RESTful APIs with Suave.
Basic Routing
Suave uses the choose combinator to select between different handlers:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.RequestErrors
let app =
choose [
path "/hello" >=> OK "Hello GET"
path "/goodbye" >=> OK "Good bye GET"
RequestErrors.NOT_FOUND "Path not found"
]
HTTP Methods
Use GET, POST, PUT, DELETE to match specific HTTP methods:
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
let app =
choose [
GET >=> path "/users" >=> OK "List users"
POST >=> path "/users" >=> OK "Create user"
PUT >=> path "/users" >=> OK "Update user"
DELETE >=> path "/users" >=> OK "Delete user"
]
Path Parameters
Extract parameters from URL paths using pathScan:
open Suave
open Suave.Filters
open Suave.Successful
open Suave.RequestErrors
let app =
choose [
pathScan "/users/%d" (fun userId ->
OK (sprintf "User ID: %d" userId))
pathScan "/posts/%d/comments/%d" (fun (postId, commentId) ->
OK (sprintf "Post %d, Comment %d" postId commentId))
RequestErrors.NOT_FOUND "Not found"
]
Query Strings
Access query parameters from the request context:
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
let app =
choose [
GET >=> path "/search" >=> fun ctx ->
match ctx.request.queryParam "q" with
| Choice1Of2 query -> OK (sprintf "Searching for: %s" query) ctx
| Choice2Of2 _ -> RequestErrors.BAD_REQUEST "Missing 'q' parameter" ctx
]
Regular Expression Routes
Match paths using regular expressions:
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
let app =
choose [
pathRegex "(.*?)\.(dll|mdb|log)$" >=>
RequestErrors.FORBIDDEN "Access denied"
pathRegex "^/api/v[0-9]+/.*" >=>
OK "Versioned API"
]
REST API Example
Building a complete REST API for users:
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
type User = { id: int; name: string; email: string }
let users =
[ { id = 1; name = "Alice"; email = "alice@example.com" }
{ id = 2; name = "Bob"; email = "bob@example.com" } ]
let getUser ctx =
match ctx.request.queryParam "id" with
| Choice1Of2 idStr when System.Int32.TryParse(idStr) |> fst ->
let id = System.Int32.Parse(idStr)
match users |> List.tryFind (fun u -> u.id = id) with
| Some user ->
OK (sprintf "Name: %s, Email: %s" user.name user.email) ctx
| None ->
RequestErrors.NOT_FOUND (sprintf "User %d not found" id) ctx
| _ -> RequestErrors.BAD_REQUEST "Invalid ID" ctx
let app =
choose [
GET >=> path "/users" >=>
OK "Users: Alice (alice@example.com), Bob (bob@example.com)"
GET >=> path "/users/search" >=> getUser
POST >=> path "/users" >=>
OK "User created"
RequestErrors.NOT_FOUND "Not found"
]
Combining Routes
Use the choose combinator to organize routes hierarchically:
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
let userRoutes =
choose [
GET >=> OK "List users"
POST >=> OK "Create user"
]
let postRoutes =
choose [
GET >=> OK "List posts"
POST >=> OK "Create post"
]
let app =
choose [
path "/users" >=> userRoutes
path "/posts" >=> postRoutes
RequestErrors.NOT_FOUND "Not found"
]
Advanced: Custom Handlers
Create custom handler functions for complex logic:
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
let delayedResponse (delayMs: int) =
fun (ctx: HttpContext) ->
async {
do! Async.Sleep delayMs
return! OK "Delayed response" ctx
}
let app =
choose [
path "/fast" >=> OK "Instant response"
path "/slow" >=> delayedResponse 1000
]
Handling Errors
Return appropriate HTTP status codes:
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
let app =
choose [
path "/ok" >=> OK "200 OK"
path "/created" >=> Successful.CREATED "Resource created"
path "/bad" >=> RequestErrors.BAD_REQUEST "Invalid request"
path "/not-found" >=> RequestErrors.NOT_FOUND "Not found"
path "/forbidden" >=> RequestErrors.FORBIDDEN "Access denied"
path "/error" >=> ServerErrors.INTERNAL_ERROR "Server error"
]