A fast data-driven routing library for Clojure/Script
Find a file
2017-08-13 14:40:46 +03:00
perf-test/clj/reitit Cleanup & fix perf test 2017-08-12 17:55:58 +03:00
scripts Initial commit 2017-08-07 14:15:45 +03:00
src/reitit Default compile get the :handler from meta 2017-08-13 14:40:46 +03:00
test Default compile get the :handler from meta 2017-08-13 14:40:46 +03:00
.gitignore Initial commit 2017-08-07 14:15:45 +03:00
.travis.yml Initial commit 2017-08-07 14:15:45 +03:00
CHANGELOG.md Initial commit 2017-08-07 14:15:45 +03:00
CONTRIBUTING.md Initial commit 2017-08-07 14:15:45 +03:00
LICENSE Initial commit 2017-08-07 14:15:45 +03:00
project.clj Add stuff 2017-08-08 15:31:00 +03:00
README.md Support route compilation (fixes #14) 2017-08-12 17:50:21 +03:00

reitit Build Status Dependencies Status

Snappy data-driven router for Clojure(Script).

  • Simple data-driven route syntax
  • First-class route meta-data
  • Generic, not tied to HTTP
  • Extendable
  • Fast

Latest version

Clojars Project

Route Syntax

Routes are defined as vectors, which String path as the first element, then optional meta-data (non-vector) and optional child routes. Routes can be wrapped in vectors.

Simple route:

["/ping"]

Two routes:

[["/ping]
 ["/pong]]

Routes with meta-data:

[["/ping ::ping]
 ["/pong {:name ::pong}]]

Nested routes with meta-data:

["/api"
 ["/admin" {:middleware [::admin]}
  ["/user" ::user]
  ["/db" ::db]
 ["/ping" ::ping]]

Previous example flattened:

[["/api/admin/user" {:middleware [::admin], :name ::user}
 ["/api/admin/db" {:middleware [::admin], :name ::db}
 ["/api/ping" ::ping]]

Routers

For actual routing, we need to create a Router. Reitit ships with 2 different router implementations: LinearRouter and LookupRouter, both based on the awesome Pedestal implementation.

Router is created with reitit.core/router, which takes routes and optionally an options map as arguments. The route-tree gets expanded, optionally coerced and compiled to support both fast path- and name-based lookups.

Create a router:

(require '[reitit.core :as reitit])

(def router
  (reitit/router
    [["/api"
      ["/ping" ::ping]
      ["/user/:id" ::user]]))

(class router)
; reitit.core.LinearRouter

Get the expanded routes:

(reitit/routes router)
; [["/api/ping" {:name :user/ping}]
;  ["/api/user/:id" {:name :user/user}]]

Path-based routing:

(reitit/match-by-path router "/hello")
; nil

(reitit/match-by-path router "/api/user/1")
; #Match{:template "/api/user/:id"
;        :meta {:name :user/user}
;        :path "/api/user/1"
;        :handler nil
;        :params {:id "1"}}

Name-based (reverse) routing:

(reitit/match-by-name router ::user)
; ExceptionInfo missing path-params for route '/api/user/:id': #{:id}

Oh, that didn't work, retry:

(reitit/match-by-name router ::user {:id "1"})
; #Match{:template "/api/user/:id"
;        :meta {:name :user/user}
;        :path "/api/user/1"
;        :handler nil
;        :params {:id "1"}}

Route meta-data

Routes can have arbitrary meta-data. For nested routes, the meta-data is accumulated from root towards leafs using meta-merge.

A router based on nested route tree:

(def ring-router
  (reitit/router
    ["/api" {:middleware [:api-mw]}
     ["/ping" ::ping]
     ["/public/*path" ::resources]
     ["/user/:id" {:name ::get-user
                   :parameters {:id String}}
      ["/orders" ::user-orders]]
     ["/admin" {:middleware [:admin-mw]
                :roles #{:admin}}
      ["/root" {:name ::root
                :roles ^:replace #{:root}}]
      ["/db" {:name ::db
              :middleware [:db-mw]}]]]))

Expanded and merged route tree:

(reitit/routes ring-router)
; [["/api/ping" {:name :user/ping
;                :middleware [:api-mw]}]
;  ["/api/public/*path" {:name :user/resources
;                        :middleware [:api-mw]}]
;  ["/api/user/:id/orders" {:name :user/user-orders
;                           :middleware [:api-mw]
;                           :parameters {:id String}}]
;  ["/api/admin/root" {:name :user/root
;                      :middleware [:api-mw :admin-mw]
;                      :roles #{:root}}]
;  ["/api/admin/db" {:name :user/db
;                    :middleware [:api-mw :admin-mw :db-mw]
;                    :roles #{:admin}}]]

Path-based routing:

(reitit/match-by-path ring-router "/api/admin/root")
; #Match{:template "/api/admin/root"
;        :meta {:name :user/root
;               :middleware [:api-mw :admin-mw]
;               :roles #{:root}}
;        :path "/api/admin/root"
;        :handler nil
;        :params {}}

Route meta-data is just data and the actual interpretation is left to the application. Custom coercion and route compilation can be defined via router options enabling things like clojure.spec validation for route-meta data and pre-compiled route handlers (Ring-handlers or Pedestal-style interceptors).

TODO: examples / implementations of different kind of routers. See Open issues.

Special thanks

To all Clojure(Script) routing libs out there, expecially to Ataraxy, Bide, Bidi, Compojure and Pedestal.

License

Copyright © 2017 Metosin Oy

Distributed under the Eclipse Public License, the same as Clojure.