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"
  ]