Core Concepts & Behavior
Understanding how Fist processes requests will help you design better APIs and avoid common pitfalls.
The Trie Structure
Fist uses a Radix Trie (Prefix Tree) internally.
- Performance: Routing is O(n) relative to the path length, meaning it stays fast regardless of having 10 or 10,000 routes.
- Structure: Paths are split into segments. Each segment is a node in the tree.
Path Normalization
Fist automatically handles common URL inconsistencies so you don’t have to write logic for them:
- Trailing Slashes:
/usersand/users/are treated as the same route. - Double Slashes:
//api///v1is normalized to/api/v1. - Case Sensitivity: Fist is Case Sensitive.
/Usersis distinct from/users.
Precedence & Priority
When a request matches multiple possibilities (e.g., a static route and a wildcard), Fist follows this strict priority order:
- Exact Static Match
- Example:
/posts/newtakes priority over/posts/:id.
- Example:
- Dynamic Match
- Example:
/posts/:idmatches if no static route matches.
- Example:
Constraints
The “Same Level” Constraint
Because of the Trie structure, you cannot register two different dynamic parameter names at the exact same position in the tree. The last one defined will overwrite the previous one.
❌ Incorrect (Conflict):
fist.new()
|> fist.get("/api/:user_id", handler_a)
|> fist.get("/api/:product_id", handler_b)
// Error: Any request to /api/123 will be routed to handler_b,
// and the parameter will be named "product_id".
✅ Correct (Namespacing):
fist.new()
|> fist.get("/users/:user_id", handler_a)
|> fist.get("/products/:product_id", handler_b)