Fist 👊

A declarative, type-safe, tree-based HTTP router for Gleam.

fist is a pure router library that operates directly on standard gleam/http types, completely decoupled from any specific web server (Mist, Wisp, Elli, etc.). It compiles with 100% parity to both the BEAM (Erlang) and JavaScript (Node.js, Deno, Bun, browser) targets with zero native runtime dependencies.


Features


Installation

gleam add fist

Quick Example

import fist
import gleam/dict
import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/result

// 1. Define custom application context
pub type AppContext {
  AppContext(api_version: String)
}

// 2. Define your handlers: fn(Request, Context, Params) -> Response
fn get_user(_req: Request(String), ctx: AppContext, params: dict.Dict(String, String)) {
  let user_id = dict.get(params, "user_id") |> result.unwrap("anonymous")

  response.new(200)
  |> response.set_header("x-api-version", ctx.api_version)
  |> response.set_body("User profile: " <> user_id)
}

// 3. Build the router
pub fn router() {
  fist.new()
  |> fist.get("/", to: fn(_, _, _) {
    response.new(200) |> response.set_body("Welcome!")
  })
  |> fist.group(at: "/api/v1", with: [], defining: fn(v1) {
    v1
    |> fist.get("/users/:user_id", to: get_user)
    |> fist.describe("Get user by ID")
  })
  |> fist.get("/static/*filepath", to: fn(_req, _ctx, params) {
    let path = dict.get(params, "filepath") |> result.unwrap("")
    response.new(200) |> response.set_body("Serving: " <> path)
  })
}

// 4. Dispatch requests
pub fn handle_request(req: Request(String), ctx: AppContext) -> Response(String) {
  fist.handle(router(), req, ctx, not_found: fn() {
    response.new(404) |> response.set_body("Route Not Found")
  })
}

Documentation

Full documentation and API reference are published on HexDocs:

(Repository markdown sources are available in the docs/ directory).

✨ Search Document