sigildocs

Routing

Request routing, middleware, cookies, and static files.
(import (sigil web)
        (sigil http))

The (sigil web) module re-exports all routing, middleware, cookie, and static file functionality. Import sub-modules directly for selective access:

ModulePurpose
(sigil web routes)Path-based routing
(sigil web middleware)Middleware composition
(sigil web cookies)Cookie parsing and setting
(sigil web static)Static file serving

Routes

Define routes with an HTTP method, URL pattern, and handler. Handlers receive a request and return a response (or #f for no match). method: takes an HTTP-method symbol and defaults to POST when omitted (a "post" string is accepted too).

(define app
  (router
    (route method: GET  pattern: "/"          handler: home-handler)
    (route method: GET  pattern: "/about"     handler: about-handler)
    (route method: POST pattern: "/api/login" handler: login-handler)))

Available method symbols: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD, ANY. ANY matches all HTTP methods.

Pattern Matching

Route patterns support three segment types:

PatternMatchesExample
/usersLiteral path/users only
/users/:idNamed parameter/users/42, /users/alice
/static/*pathWildcard (rest of path)/static/css/main.css
(router
  (route method: GET pattern: "/users/:id" handler: user-handler)
  (route method: GET pattern: "/files/*path" handler: file-handler))

Path Parameters

Extract parameters from matched routes using path-param:

(define (user-handler request)
  (let ((id (path-param request "id")))
    (http-response/html 200
      (string-append "<h1>User " id "</h1>"))))

;; Get all params as an alist
(define (handler request)
  (let ((params (path-params request)))
    ...))

Route Combination

routes combines multiple handlers into one. The first handler to return a non-#f response wins.

(define api-routes
  (router
    (route method: GET  pattern: "/api/users" handler: list-users)
    (route method: POST pattern: "/api/users" handler: create-user)))

(define page-routes
  (router
    (route method: GET pattern: "/"      handler: home-page)
    (route method: GET pattern: "/about" handler: about-page)))

(define app
  (routes api-routes page-routes))

ui-routes

ui-routes is a routes analog for hypermedia handlers: it combines handlers the same way, but a handler that returns a value which isn't an HTTP response is taken as "handled, nothing extra for the acting user" and becomes an empty (ui-response). So an action handler can end with just its mutation, no trailing (ui-response):

(define (toggle-handler req)
  (live-update! todos (item-id req)
    (lambda (i) (dict-set i done?: (not (dict-ref i done?:))))))   ; no ui-response needed

(define app
  (-> (routes
        (ui-routes (router …hypermedia…))    ; non-response returns -> empty ack
        (router …json-api…))                 ; plain routes never coerce
      (with-sigil-ui)
      (with-logging)
      (with-not-found)))

#f still falls through (a missing-id mutation → 404 via with-not-found), and explicit responses pass through untouched. Keep JSON APIs and webhooks under plain routes/router, which never coerce. ui-routes is exported from (sigil web ui).

Middleware

Middleware wraps a handler to add cross-cutting concerns. Two styles are supported.

Chain Style

Chain-style wrappers take a handler and return a wrapped handler, ideal for use with the -> threading macro:

(define app
  (-> (routes api-routes page-routes)
      (with-logging)
      (with-cors)
      (with-content-type)
      (with-not-found)))
WrapperKeywordsDescription
with-loggingoutput:Log requests with method, path, status, duration
with-corsallow-origin:, allow-methods:, allow-headers:Add CORS headers (default: allow all origins)
with-content-typedefault:Set default Content-Type (default: "text/html")
with-not-foundbody:Return 404 if handler returns #f
with-sigil-uipath:Serve the client script at /js/sigil-web-ui.js (from (sigil web ui))
;; Custom CORS settings
(-> handler
    (with-cors allow-origin: "https://example.com"
               allow-methods: "GET, POST"))

;; Custom 404 page
(-> handler
    (with-not-found body: "<h1>Page not found</h1>"))

Factory Style

Factory-style middleware returns a handler -> handler function, useful for wrap-middleware:

(define app
  (wrap-middleware handler
    (logger-middleware)
    (cors-middleware '("*"))
    (not-found-middleware)))

Cookies

Parse cookies from requests and set cookies on responses.

;; Get a specific cookie
(cookie-ref request "session_id")
; => "abc123" or #f

;; Get all cookies as a dict
(cookies request)
; => #{ "session_id": "abc123" "theme": "dark" }

;; Set a cookie on a response
(set-cookie response "session_id" "abc123"
  '((path . "/")
    (http-only . #t)
    (max-age . 3600)
    (same-site . "Strict")))

;; Delete a cookie (sets max-age=0)
(delete-cookie response "session_id")

Cookie options:

OptionDescription
pathCookie path (e.g., "/")
domainCookie domain
max-ageSeconds until expiration
expiresExpiration date string
secureOnly send over HTTPS (#t/#f)
http-onlyNot accessible via JavaScript (#t/#f)
same-site"Strict", "Lax", or "None"

Static Files

Serve files from a directory with automatic MIME type detection.

(define static-handler
  (make-static-handler "public/"))

;; Use in a router with a wildcard pattern
(define app
  (-> (routes
        (router (route method: GET pattern: "/" handler: home-handler))
        (make-static-handler "public/"
          '((prefix . "/static"))))
      (with-not-found)))

Options for make-static-handler:

OptionDefaultDescription
index"index.html"Index file for directory requests
prefix""URL prefix to strip before file lookup

serve-file serves a single file by path, returning #f if it doesn't exist:

(serve-file "public/favicon.ico")

Path traversal attacks (..) are blocked by safe-path?.

Common Patterns

Full Application Setup

(import (sigil web)
        (sigil http))

(define (home-handler request)
  (http-response/html 200 "<h1>Home</h1>"))

(define (user-handler request)
  (let ((id (path-param request "id")))
    (http-response/json 200 #{ id: id })))     ; a dict is encoded for you

(define app
  (-> (routes
        (router
          (route method: GET pattern: "/"          handler: home-handler)
          (route method: GET pattern: "/users/:id" handler: user-handler))
        (make-static-handler "public/"))
      (with-logging)
      (with-cors)
      (with-not-found)))

(http-serve app port: 8080)