User Guide
fist is a declarative, type-safe, tree-based HTTP router for Gleam. It is independent of any specific web server and operates directly on standard gleam/http types.
1. Creating a Router
import fist
import gleam/http/response
pub fn create_router() {
fist.new()
|> fist.get("/", to: fn(_req, _ctx, _params) {
response.new(200) |> response.set_body("Hello, World!")
})
}
2. Route Handlers & Methods
Every handler receives three arguments:
req: The HTTPRequest(req_body).ctx: A custom application context (e.g. database, config, state).params: ADict(String, String)containing extracted URL parameters.
Supported Methods
fist provides first-class helpers for standard HTTP methods:
fist.get(router, path, to: handler)fist.post(router, path, to: handler)fist.put(router, path, to: handler)fist.delete(router, path, to: handler)fist.patch(router, path, to: handler)fist.head(router, path, to: handler)fist.options(router, path, to: handler)fist.route(router, method: http.Other("..."), path, handler)(Custom methods)
3. Dynamic Parameters
Prefix a segment with : to capture its value. Percent-encoded values are automatically decoded (e.g., João from Jo%C3%A3o).
import fist
import gleam/dict
import gleam/http/response
import gleam/result
fn show_user(_req, _ctx, params) {
let id = dict.get(params, "id") |> result.unwrap("unknown")
response.new(200) |> response.set_body("User: " <> id)
}
let router =
fist.new()
|> fist.get("/users/:id", to: show_user)
|> fist.get("/users/:id/posts/:post_id", to: show_post)
4. Route Groups & Middlewares
Groups
Organize related routes under a common path prefix and apply shared middlewares:
let router =
fist.new()
|> fist.group(at: "/api/v1", with: [auth_middleware], defining: fn(v1) {
v1
|> fist.get("/users", to: list_users)
|> fist.post("/users", to: create_user)
})
Middlewares (wrap)
Middlewares are wrapper functions (Handler) -> Handler. They are applied at definition time (Static Wrapping), introducing zero Trie lookup overhead at runtime.
Middlewares execute in the exact order declared in the list (outer to inner):
fn log_middleware(next) {
fn(req, ctx, params) {
// Before handler logic
let res = next(req, ctx, params)
// After handler logic
res
}
}
let router =
fist.new()
|> fist.get("/public", to: public_handler)
|> fist.wrap(log_middleware)
5. Metadata & Documentation (describe, inspect)
You can document endpoints immediately after registering them:
let router =
fist.new()
|> fist.get("/users", to: list_users)
|> fist.describe("List all active users")
|> fist.post("/users", to: create_user)
|> fist.describe("Create a new user")
// Extract all routes and metadata programmatically:
let routes = fist.inspect(router)
6. Execution & HTTP Status Handling
Dispatch requests using fist.handle:
let response =
fist.handle(
router,
request: req,
context: ctx,
not_found: fn() {
response.new(404) |> response.set_body("Not Found")
},
)
Handling 405 Method Not Allowed & CORS Preflight
Use fist.allowed_methods(router, path) to inspect registered methods for a given route:
import fist
import gleam/http.{Get, Options, Post}
import gleam/http/response
import gleam/list
import gleam/string
pub fn dispatch(router, req, ctx) {
case req.method {
// 1. Automatic CORS Preflight
Options -> {
case fist.allowed_methods(router, req.path) {
[] -> response.new(404) |> response.set_body("Not Found")
methods -> {
let allow =
list.map(methods, string.inspect)
|> string.join(", ")
response.new(204)
|> response.set_header("access-control-allow-methods", allow)
|> response.set_header("access-control-allow-origin", "*")
|> response.set_body("")
}
}
}
// 2. Standard Request Routing
_ -> {
fist.handle(router, req, ctx, not_found: fn() {
case fist.allowed_methods(router, req.path) {
// Route exists for other methods -> 405
[_, ..] as methods -> {
let allow =
list.map(methods, string.inspect)
|> string.join(", ")
response.new(405)
|> response.set_header("allow", allow)
|> response.set_body("Method Not Allowed")
}
// Route does not exist at all -> 404
[] -> response.new(404) |> response.set_body("Not Found")
}
})
}
}
}