Router Module
The Router module provides efficient HTTP routing with path parameter extraction and flexible route matching patterns.
Basic Router Setup
Create a simple router with basic routes:
open Suave
open Suave.Successful
open Suave.Router
let app =
router {
get "/" (OK "Home page")
get "/about" (OK "About page")
post "/submit" (OK "Form submitted")
delete "/items/:id" (OK "Item deleted")
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Path Parameters
Extract values from URL paths using parameters:
open Suave
open Suave.Successful
open Suave.Router
open Suave.Operators
let getUser ctx =
match routeParam "id" ctx with
| Some id -> OK (sprintf "User %s" id) ctx
| None -> RequestErrors.BAD_REQUEST "Missing user ID" ctx
let app =
router {
get "/users/:id" getUser
get "/posts/:postId/comments/:commentId" (fun ctx ->
match (routeParam "postId" ctx, routeParam "commentId" ctx) with
| (Some postId, Some commentId) ->
OK (sprintf "Post %s, Comment %s" postId commentId) ctx
| _ -> RequestErrors.BAD_REQUEST "Missing parameters" ctx
)
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
HTTP Methods
Use different HTTP methods for different route handlers:
open Suave
open Suave.Successful
open Suave.Router
open Suave.Operators
let app =
router {
get "/api/users" (OK "List all users")
post "/api/users" (OK "Create new user")
get "/api/users/:id" (OK "Get user by ID")
put "/api/users/:id" (OK "Update user")
delete "/api/users/:id" (OK "Delete user")
patch "/api/users/:id" (OK "Patch user")
head "/api/health" (OK "")
options "/api/users" (OK "")
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Route Scoping
Group routes with a common prefix. scope builds a Router; turn it into a WebPart with tryRoute (or put the same paths in a top-level router):
open Suave
open Suave.Successful
open Suave.Router
open Suave.Operators
let runRouter (r : Router) : WebPart =
fun ctx -> tryRoute r ctx
let apiScope =
scope "/api" {
get "/users" (OK "List users")
get "/users/:id" (OK "Get user")
post "/users" (OK "Create user")
delete "/users/:id" (OK "Delete user")
}
let v2Scope =
scope "/v2" {
get "/users" (OK "List users (v2)")
get "/posts" (OK "List posts (v2)")
}
let app =
choose [
runRouter apiScope
runRouter v2Scope
router {
get "/" (OK "Home")
}
]
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Wildcard Routes
Match remaining path segments with wildcards:
open Suave
open Suave.Successful
open Suave.Router
open Suave.Operators
let serveStatic ctx =
match routeParam "filepath" ctx with
| Some filepath ->
OK (sprintf "Serving file: %s" filepath) ctx
| None -> RequestErrors.NOT_FOUND "File not found" ctx
let app =
router {
get "/files/*filepath" serveStatic
get "/docs/*filepath" (fun ctx ->
match routeParam "filepath" ctx with
| Some docPath ->
OK (sprintf "Documentation: %s" docPath) ctx
| None -> RequestErrors.NOT_FOUND "Doc not found" ctx
)
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Complex Route Patterns
Combine multiple parameters and dynamic segments:
open Suave
open Suave.Successful
open Suave.Router
open Suave.Operators
let getCommentHandler ctx =
match (routeParam "userId" ctx, routeParam "postId" ctx, routeParam "commentId" ctx) with
| (Some userId, Some postId, Some commentId) ->
let response =
sprintf "User %s, Post %s, Comment %s"
userId postId commentId
OK response ctx
| _ ->
RequestErrors.BAD_REQUEST "Missing required parameters" ctx
let app =
router {
get "/users/:userId/posts/:postId/comments/:commentId" getCommentHandler
get "/repos/:owner/:repo/branches/:branch" (fun ctx ->
match (routeParam "owner" ctx, routeParam "repo" ctx, routeParam "branch" ctx) with
| (Some owner, Some repo, Some branch) ->
OK (sprintf "%s/%s - Branch: %s" owner repo branch) ctx
| _ -> RequestErrors.BAD_REQUEST "Missing repository parameters" ctx
)
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Route with Middleware
Apply middleware to router handlers:
open Suave
open Suave.Successful
open Suave.Router
open Suave.Operators
open Suave.Filters
let requireJson =
Writers.setHeader "Content-Type" "application/json"
let logRequest : WebPart =
fun ctx ->
printfn "Method: %s, Path: %s"
ctx.request.rawMethod
ctx.request.path
async { return Some ctx }
let app =
router {
get "/" (logRequest >=> OK "Home")
get "/api/data" (logRequest >=> requireJson >=> OK """{"message":"Hello"}""")
post "/api/submit" (logRequest >=> OK "Submitted")
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
RESTful API Example
Build a complete RESTful API with the Router module:
open Suave
open Suave.Router
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
let users = System.Collections.Generic.Dictionary<string, string>()
let getAllUsers = OK (System.String.Join(",", users.Keys))
let getUser ctx =
match routeParam "id" ctx with
| Some id when users.ContainsKey(id) ->
OK (sprintf "User: %s" users.[id]) ctx
| Some _ -> NOT_FOUND "User not found" ctx
| None -> BAD_REQUEST "Missing user ID" ctx
let createUser = OK "User created"
let updateUser ctx = async {
match routeParam "id" ctx with
| Some id ->
users.[id] <- "Updated"
return! OK "User updated" ctx
| None -> return! BAD_REQUEST "Missing user ID" ctx
}
let deleteUser ctx = async {
match routeParam "id" ctx with
| Some id when users.ContainsKey(id) ->
users.Remove(id) |> ignore
return! OK "User deleted" ctx
| Some _ -> return! NOT_FOUND "User not found" ctx
| None -> return! BAD_REQUEST "Missing user ID" ctx
}
let app =
router {
get "/api/users" getAllUsers
post "/api/users" createUser
get "/api/users/:id" getUser
put "/api/users/:id" updateUser
delete "/api/users/:id" deleteUser
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Nested Scopes
Prefer one router (or one scope) with the full path prefix — nested scope builders are not supported:
open Suave
open Suave.Successful
open Suave.Router
open Suave.Operators
let app =
router {
get "/api/v1/users" (OK "V1 Users")
get "/api/v1/posts" (OK "V1 Posts")
get "/api/v2/users" (OK "V2 Users")
get "/api/v2/posts" (OK "V2 Posts")
get "/admin/dashboard" (OK "Admin Dashboard")
get "/admin/users" (OK "Admin Users")
delete "/admin/users/:id" (OK "User deleted")
get "/" (OK "Home")
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Route Parameters in Complex Handlers
Use route parameters in sophisticated request handling:
open Suave
open Suave.Router
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
let handleRequest ctx =
async {
// Extract multiple parameters
let orgId = routeParam "orgId" ctx
let projectId = routeParam "projectId" ctx
let resourceId = routeParam "resourceId" ctx
match (orgId, projectId, resourceId) with
| (Some org, Some proj, Some res) ->
let response =
sprintf
"Organization: %s\nProject: %s\nResource: %s"
org proj res
return! OK response ctx
| _ ->
return! BAD_REQUEST "Missing required parameters" ctx
}
let app =
router {
get "/orgs/:orgId/projects/:projectId/resources/:resourceId" handleRequest
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Custom Route Builder
Build routes programmatically with the route helper:
open Suave
open Suave.Successful
open Suave.Router
open Suave.Operators
let buildApiRoute path handler =
router {
// Custom `route` takes one argument: (methods * path * handler)
route ([ HttpMethod.GET; HttpMethod.POST ], path, handler)
}
let multiMethodHandler ctx =
match ctx.request.method with
| HttpMethod.GET -> OK "GET request" ctx
| HttpMethod.POST -> OK "POST request" ctx
| _ -> OK "Other method" ctx
let app =
choose [
buildApiRoute "/data" multiMethodHandler
router {
get "/" (OK "Home")
}
]
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Route Matching Performance
The Router module optimizes performance with exact and pattern matching:
open Suave
open Suave.Successful
open Suave.Router
open Suave.Operators
// Routes with exact paths are O(1) lookups
// Routes with parameters are checked in order
let app =
router {
// Exact matches (fast - O(1))
get "/" (OK "Home")
get "/about" (OK "About")
get "/contact" (OK "Contact")
// Parameterized routes (slower - checked in order)
get "/users/:id" (fun ctx ->
match routeParam "id" ctx with
| Some id -> OK (sprintf "User %s" id) ctx
| None -> OK "No user" ctx
)
// Wildcard routes (checked last)
get "/static/*" (fun ctx ->
match routeParam "path" ctx with
| Some path -> OK (sprintf "Static: %s" path) ctx
| None -> OK "Static file" ctx
)
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Error Handling in Routes
Handle errors and edge cases in router handlers:
open Suave
open Suave.Router
open Suave.Operators
open Suave.Successful
open Suave.RequestErrors
let safeGetUser ctx =
async {
try
match routeParam "id" ctx with
| Some id ->
let userId = System.Int32.Parse(id)
if userId > 0 then
return! OK (sprintf "User %d" userId) ctx
else
return! BAD_REQUEST "Invalid user ID" ctx
| None ->
return! BAD_REQUEST "Missing user ID" ctx
with
| _ -> return! BAD_REQUEST "Invalid format" ctx
}
let app =
router {
get "/users/:id" safeGetUser
}
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0