Asko Nõmm

Ruuter 2.0

Ruuter, my zero-dependency, runtime-agnostic Router for Clojure, ClojureScript and Babashka has a new release out. A pretty hefty one at that:

  • Best-match routing: Routes are now matched by specificity instead of first-match-wins. Literal segments beat parameters, parameters beat optionals, optionals beat wildcards. Route order in the vector no longer matters.
  • Segment trie: Routes are compiled into a trie (prefix tree) data structure for O(path-depth) matching instead of O(N) linear scan. This yields 4-380x performance improvements depending on route count and match type.
  • compile-routes function: New public function for explicit route compilation. Routes are also compiled implicitly and cached via memoization when using route directly.
  • Single wildcard constraint: Wildcard parameters (:name*) must now be the last segment in a path. Multiple wildcards per path are no longer supported.
  • No regex: Route matching no longer uses regular expressions. Matching is done via direct string comparison of path segments against a trie.
  • deps.edn only: Leiningen (project.clj) has been retired. All build, test, and benchmark tasks use deps.edn and bb.edn.

Massive performance improvements come with this release, which is something that’s been lacking a lot so far:

  • JVM (Clojure)
    • Small route sets: 1.6–4.1x faster
    • Medium route sets: 39–139x faster
    • Large route sets: 162–345x faster
    • Peak throughput: ~9.8M ops/sec (literal match)
  • ClojureScript (Node.js)
    • Small route sets: 0.9–6.5x faster (literal-first is within noise; params, wildcards, and misses see large gains)
    • Medium route sets: 14–40x faster
    • Large route sets: 38–167x faster
    • Peak throughput: ~1.3M ops/sec (literal match)
  • Babashka
    • Small route sets: 2.0–6.4x faster
    • Medium route sets: 11–32x faster
    • Large route sets: 32–182x faster
    • Peak throughput: ~1.1M ops/sec (miss/404 — fast trie rejection)