Advanced Patterns
Fist is more than just a router; its generic nature allows for powerful architectural patterns.
Custom Return Types
Handlers are not restricted to returning a gleam/http/response.Response. They can return Algebraic Data Types (ADTs), which you can transform globally later. This keeps your business logic pure and testable.
Example: Domain-Specific Return Type
pub type AppResult {
Html(String)
Json(String)
NotFound
}
// Handler returns pure data, not HTTP concepts
fn my_handler(_, _, _) {
Json("{\"status\": \"ok\"}")
}
pub fn main() {
let router =
fist.new()
|> fist.get("/api", to: my_handler)
|> fist.map(fn(res) {
// Global transformation layer: Convert AppResult -> Response
case res {
Html(body) -> response.new(200) |> response.set_body(body)
Json(json) -> {
response.new(200)
|> response.set_header("content-type", "application/json")
|> response.set_body(json)
}
NotFound -> response.new(404) |> response.set_body("Not Found")
}
})
}
Route Wrapping (Middlewares)
Fist provides the fist.wrap function to apply a middleware to all existing routes in a router. A middleware is a function that takes a handler and returns a new handler.
Authentication Example
// 1. Define a middleware wrapper
fn require_auth(
next: fn(Request, Ctx, Dict) -> Response
) {
fn(req, ctx, params) {
case request.get_header(req, "authorization") {
Ok("secret-token") -> next(req, ctx, params) // Continue
_ -> response.new(401) |> response.set_body("Unauthorized")
}
}
}
// 2. Use it in your router
pub fn main() {
fist.new()
|> fist.get("/public", to: public_handler)
// Wrap all routes defined ABOVE this line with the middleware
|> fist.wrap(require_auth)
// Routes defined BELOW this line will NOT have the middleware
|> fist.get("/login", to: login_handler)
}
Context Polymorphism with mount
One of Fist’s most powerful features is the ability to combine routers with different context requirements. This is achieved through mount and map_context.
Imagine you have an admin router that requires an AdminUser context, but your main router has a Nil context:
// admin.gleam
pub type AdminContext { AdminContext(user: User) }
pub fn admin_router() {
fist.new()
|> fist.get("/dashboard", show_dashboard) // Expects AdminContext
}
// main.gleam
pub fn main_router() {
fist.new()
|> fist.mount(
at: "/admin",
sub: admin.admin_router(),
transform: fn(_nil_ctx) {
// Logic to transform Root Context to AdminContext
AdminContext(user: get_current_user())
}
)
}
This allows for true modularity and isolation of concerns across your application.
Context & State Management
Fist passes a generic Context to every handler, avoiding global state.
pub type AppContext {
AppContext(db_conn: Connection, api_key: String)
}
fn dashboard(req, ctx: AppContext, params) {
// Use ctx.db_conn safely here
}
pub fn main() {
let ctx = AppContext(db_conn: db.connect(), api_key: "secret")
// The context is injected at runtime
fist.handle(router, req, ctx, not_found_handler)
}