Router
Routes are just data and to do routing, we need a router instance satisfying the reitit.core/Router protocol. Routers are created with reitit.core/router function, taking the raw routes and optionally an options map.
The Router protocol:
(defprotocol Router
  (router-name [this])
  (routes [this])
  (options [this])
  (route-names [this])
  (match-by-path [this path])
  (match-by-name [this name] [this name params]))
Creating a router:
(require '[reitit.core :as r])
(def router
  (r/router
    ["/api"
     ["/ping" ::ping]
     ["/user/:id" ::user]]))
Name of the created router:
(r/router-name router)
; :mixed-router
The flattened route tree:
(r/routes router)
; [["/api/ping" {:name :user/ping}]
;  ["/api/user/:id" {:name :user/user}]]
With a router instance, we can do Path-based routing or Name-based (Reverse) routing.
More details
Router options:
(r/options router)
{:lookup #object[...]
 :expand #object[...]
 :coerce #object[...]
 :compile #object[...]
 :conflicts #object[...]}
Route names:
(r/route-names router)
; [:user/ping :user/user]
Composing
As routes are defined as plain data, it's easy to merge multiple route trees into a single router
(def user-routes
  [["/users" ::users]
   ["/users/:id" ::user]]) 
(def admin-routes
  ["/admin"
   ["/ping" ::ping]
   ["/db" ::db]])
(def router
  (r/router
    [admin-routes
     user-routes]))
Merged route tree:
(r/routes router)
; [["/admin/ping" {:name :user/ping}]
;  ["/admin/db" {:name :user/db}]
;  ["/users" {:name :user/users}]
;  ["/users/:id" {:name :user/user}]]
More details on composing routers.
Behind the scenes
When router is created, the following steps are done:
- route tree is flattened
 - route arguments are expanded (via 
:expandoption) - routes are coerced (via 
:coerceoptions) - route tree is compiled (via 
:compileoptions) - route conflicts are resolved (via 
:conflictsoptions) - optionally, route data is validated (via 
:validateoptions) - router implementation is automatically selected (or forced via 
:routeroptions) and created