AtlatestRepositorysigil-web
1
# Routing3
> Request routing, middleware, cookies, and static files.5
```scheme6
(import (sigil web)7
(sigil http))8
```10
The `(sigil web)` module re-exports all routing, middleware, cookie, and static file functionality. Import sub-modules directly for selective access:12
| Module | Purpose |13
|--------|---------|14
| `(sigil web routes)` | Path-based routing |15
| `(sigil web middleware)` | Middleware composition |16
| `(sigil web cookies)` | Cookie parsing and setting |17
| `(sigil web static)` | Static file serving |19
## Routes21
Define routes with an HTTP method, URL pattern, and handler. Handlers receive a22
request and return a response (or `#f` for no match). `method:` takes an23
HTTP-method **symbol** and defaults to `POST` when omitted (a `"post"` string is24
accepted too).26
```scheme27
(define app28
(router29
(route method: GET pattern: "/" handler: home-handler)30
(route method: GET pattern: "/about" handler: about-handler)31
(route method: POST pattern: "/api/login" handler: login-handler)))32
```34
Available method symbols: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS`,35
`HEAD`, `ANY`. `ANY` matches all HTTP methods.37
## Pattern Matching39
Route patterns support three segment types:41
| Pattern | Matches | Example |42
|---------|---------|---------|43
| `/users` | Literal path | `/users` only |44
| `/users/:id` | Named parameter | `/users/42`, `/users/alice` |45
| `/static/*path` | Wildcard (rest of path) | `/static/css/main.css` |47
```scheme48
(router49
(route method: GET pattern: "/users/:id" handler: user-handler)50
(route method: GET pattern: "/files/*path" handler: file-handler))51
```53
### Path Parameters55
Extract parameters from matched routes using `path-param`:57
```scheme58
(define (user-handler request)59
(let ((id (path-param request "id")))60
(http-response/html 20061
(string-append "<h1>User " id "</h1>"))))63
;; Get all params as an alist64
(define (handler request)65
(let ((params (path-params request)))66
...))67
```69
## Route Combination71
`routes` combines multiple handlers into one. The first handler to return a72
non-`#f` response wins.74
```scheme75
(define api-routes76
(router77
(route method: GET pattern: "/api/users" handler: list-users)78
(route method: POST pattern: "/api/users" handler: create-user)))80
(define page-routes81
(router82
(route method: GET pattern: "/" handler: home-page)83
(route method: GET pattern: "/about" handler: about-page)))85
(define app86
(routes api-routes page-routes))87
```89
### ui-routes91
`ui-routes` is a `routes` analog for hypermedia handlers: it combines handlers92
the same way, but a handler that returns a value which **isn't** an HTTP response93
is taken as "handled, nothing extra for the acting user" and becomes an empty94
`(ui-response)`. So an action handler can end with just its mutation, no trailing95
`(ui-response)`:97
```scheme98
(define (toggle-handler req)99
(live-update! todos (item-id req)100
(lambda (i) (dict-set i done?: (not (dict-ref i done?:)))))) ; no ui-response needed102
(define app103
(-> (routes104
(ui-routes (router …hypermedia…)) ; non-response returns -> empty ack105
(router …json-api…)) ; plain routes never coerce106
(with-sigil-ui)107
(with-logging)108
(with-not-found)))109
```111
`#f` still falls through (a missing-id mutation → 404 via `with-not-found`), and112
explicit responses pass through untouched. Keep JSON APIs and webhooks under113
plain `routes`/`router`, which never coerce. `ui-routes` is exported from114
`(sigil web ui)`.116
## Middleware118
Middleware wraps a handler to add cross-cutting concerns. Two styles are supported.120
### Chain Style122
Chain-style wrappers take a handler and return a wrapped handler, ideal for use with the `->` threading macro:124
```scheme125
(define app126
(-> (routes api-routes page-routes)127
(with-logging)128
(with-cors)129
(with-content-type)130
(with-not-found)))131
```133
| Wrapper | Keywords | Description |134
|---------|----------|-------------|135
| `with-logging` | `output:` | Log requests with method, path, status, duration |136
| `with-cors` | `allow-origin:`, `allow-methods:`, `allow-headers:` | Add CORS headers (default: allow all origins) |137
| `with-content-type` | `default:` | Set default Content-Type (default: `"text/html"`) |138
| `with-not-found` | `body:` | Return 404 if handler returns `#f` |139
| `with-sigil-ui` | `path:` | Serve the client script at `/js/sigil-web-ui.js` (from `(sigil web ui)`) |141
```scheme142
;; Custom CORS settings143
(-> handler144
(with-cors allow-origin: "https://example.com"145
allow-methods: "GET, POST"))147
;; Custom 404 page148
(-> handler149
(with-not-found body: "<h1>Page not found</h1>"))150
```152
### Factory Style154
Factory-style middleware returns a `handler -> handler` function, useful for `wrap-middleware`:156
```scheme157
(define app158
(wrap-middleware handler159
(logger-middleware)160
(cors-middleware '("*"))161
(not-found-middleware)))162
```164
## Cookies166
Parse cookies from requests and set cookies on responses.168
```scheme169
;; Get a specific cookie170
(cookie-ref request "session_id")171
; => "abc123" or #f173
;; Get all cookies as a dict174
(cookies request)175
; => #{ "session_id": "abc123" "theme": "dark" }177
;; Set a cookie on a response178
(set-cookie response "session_id" "abc123"179
'((path . "/")180
(http-only . #t)181
(max-age . 3600)182
(same-site . "Strict")))184
;; Delete a cookie (sets max-age=0)185
(delete-cookie response "session_id")186
```188
Cookie options:190
| Option | Description |191
|--------|-------------|192
| `path` | Cookie path (e.g., `"/"`) |193
| `domain` | Cookie domain |194
| `max-age` | Seconds until expiration |195
| `expires` | Expiration date string |196
| `secure` | Only send over HTTPS (`#t`/`#f`) |197
| `http-only` | Not accessible via JavaScript (`#t`/`#f`) |198
| `same-site` | `"Strict"`, `"Lax"`, or `"None"` |200
## Static Files202
Serve files from a directory with automatic MIME type detection.204
```scheme205
(define static-handler206
(make-static-handler "public/"))208
;; Use in a router with a wildcard pattern209
(define app210
(-> (routes211
(router (route method: GET pattern: "/" handler: home-handler))212
(make-static-handler "public/"213
'((prefix . "/static"))))214
(with-not-found)))215
```217
Options for `make-static-handler`:219
| Option | Default | Description |220
|--------|---------|-------------|221
| `index` | `"index.html"` | Index file for directory requests |222
| `prefix` | `""` | URL prefix to strip before file lookup |224
`serve-file` serves a single file by path, returning `#f` if it doesn't exist:226
```scheme227
(serve-file "public/favicon.ico")228
```230
Path traversal attacks (`..`) are blocked by `safe-path?`.232
## Common Patterns234
### Full Application Setup236
```scheme237
(import (sigil web)238
(sigil http))240
(define (home-handler request)241
(http-response/html 200 "<h1>Home</h1>"))243
(define (user-handler request)244
(let ((id (path-param request "id")))245
(http-response/json 200 #{ id: id }))) ; a dict is encoded for you247
(define app248
(-> (routes249
(router250
(route method: GET pattern: "/" handler: home-handler)251
(route method: GET pattern: "/users/:id" handler: user-handler))252
(make-static-handler "public/"))253
(with-logging)254
(with-cors)255
(with-not-found)))257
(http-serve app port: 8080)258
```