reitit/advanced/composing_routers.html

1059 lines
42 KiB
HTML
Raw Normal View History

<!DOCTYPE HTML>
<html lang="" >
<head>
<meta charset="UTF-8">
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Composing Routers · GitBook</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="">
<meta name="generator" content="GitBook 3.2.3">
<link rel="stylesheet" href="../gitbook/style.css">
<link rel="stylesheet" href="../gitbook/gitbook-plugin-highlight/website.css">
<link rel="stylesheet" href="../gitbook/gitbook-plugin-search/search.css">
<link rel="stylesheet" href="../gitbook/gitbook-plugin-fontsettings/website.css">
<meta name="HandheldFriendly" content="true"/>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="../gitbook/images/apple-touch-icon-precomposed-152.png">
<link rel="shortcut icon" href="../gitbook/images/favicon.ico" type="image/x-icon">
<link rel="next" href="different_routers.html" />
<link rel="prev" href="configuring_routers.html" />
</head>
<body>
<div class="book">
<div class="book-summary">
<div id="book-search-input" role="search">
<input type="text" placeholder="Type to search" />
</div>
<nav role="navigation">
<ul class="summary">
<li class="chapter " data-level="1.1" data-path="../">
<a href="../">
Introduction
</a>
</li>
<li class="chapter " data-level="1.2" data-path="../basics/">
<a href="../basics/">
Basics
</a>
<ul class="articles">
<li class="chapter " data-level="1.2.1" data-path="../basics/route_syntax.html">
<a href="../basics/route_syntax.html">
Route Syntax
</a>
</li>
<li class="chapter " data-level="1.2.2" data-path="../basics/router.html">
<a href="../basics/router.html">
Router
</a>
</li>
<li class="chapter " data-level="1.2.3" data-path="../basics/path_based_routing.html">
<a href="../basics/path_based_routing.html">
Path-based Routing
</a>
</li>
<li class="chapter " data-level="1.2.4" data-path="../basics/name_based_routing.html">
<a href="../basics/name_based_routing.html">
Name-based Routing
</a>
</li>
<li class="chapter " data-level="1.2.5" data-path="../basics/route_data.html">
<a href="../basics/route_data.html">
Route Data
</a>
</li>
<li class="chapter " data-level="1.2.6" data-path="../basics/route_data_validation.html">
<a href="../basics/route_data_validation.html">
Route Data Validation
</a>
</li>
<li class="chapter " data-level="1.2.7" data-path="../basics/route_conflicts.html">
<a href="../basics/route_conflicts.html">
Route Conflicts
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.3" data-path="../coercion/">
<a href="../coercion/">
Coercion
</a>
<ul class="articles">
<li class="chapter " data-level="1.3.1" data-path="../coercion/coercion.html">
<a href="../coercion/coercion.html">
Coercion Explained
</a>
</li>
<li class="chapter " data-level="1.3.2" data-path="../coercion/schema_coercion.html">
<a href="../coercion/schema_coercion.html">
Plumatic Schema
</a>
</li>
<li class="chapter " data-level="1.3.3" data-path="../coercion/clojure_spec_coercion.html">
<a href="../coercion/clojure_spec_coercion.html">
Clojure.spec
</a>
</li>
<li class="chapter " data-level="1.3.4" data-path="../coercion/data_spec_coercion.html">
<a href="../coercion/data_spec_coercion.html">
Data-specs
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.4" data-path="./">
<a href="./">
Advanced
</a>
<ul class="articles">
<li class="chapter " data-level="1.4.1" data-path="configuring_routers.html">
<a href="configuring_routers.html">
Configuring Routers
</a>
</li>
<li class="chapter active" data-level="1.4.2" data-path="composing_routers.html">
<a href="composing_routers.html">
Composing Routers
</a>
</li>
<li class="chapter " data-level="1.4.3" data-path="different_routers.html">
<a href="different_routers.html">
Different Routers
</a>
</li>
<li class="chapter " data-level="1.4.4" data-path="route_validation.html">
<a href="route_validation.html">
Route Validation
</a>
</li>
<li class="chapter " data-level="1.4.5" data-path="dev_workflow.html">
<a href="dev_workflow.html">
Dev Workflow
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.5" data-path="../ring/">
<a href="../ring/">
Ring
</a>
<ul class="articles">
<li class="chapter " data-level="1.5.1" data-path="../ring/ring.html">
<a href="../ring/ring.html">
Ring-router
</a>
</li>
<li class="chapter " data-level="1.5.2" data-path="../ring/reverse_routing.html">
<a href="../ring/reverse_routing.html">
Reverse-routing
</a>
</li>
<li class="chapter " data-level="1.5.3" data-path="../ring/default_handler.html">
<a href="../ring/default_handler.html">
Default handler
</a>
</li>
<li class="chapter " data-level="1.5.4" data-path="../ring/static.html">
<a href="../ring/static.html">
Static Resources
</a>
</li>
<li class="chapter " data-level="1.5.5" data-path="../ring/dynamic_extensions.html">
<a href="../ring/dynamic_extensions.html">
Dynamic Extensions
</a>
</li>
<li class="chapter " data-level="1.5.6" data-path="../ring/data_driven_middleware.html">
<a href="../ring/data_driven_middleware.html">
Data-driven Middleware
</a>
</li>
<li class="chapter " data-level="1.5.7" data-path="../ring/coercion.html">
<a href="../ring/coercion.html">
Pluggable Coercion
</a>
</li>
<li class="chapter " data-level="1.5.8" data-path="../ring/route_data_validation.html">
<a href="../ring/route_data_validation.html">
Route Data Validation
</a>
</li>
<li class="chapter " data-level="1.5.9" data-path="../ring/compiling_middleware.html">
<a href="../ring/compiling_middleware.html">
Compiling Middleware
</a>
</li>
<li class="chapter " data-level="1.5.10" data-path="../ring/swagger.html">
<a href="../ring/swagger.html">
Swagger Support
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.6" data-path="../frontend/">
<a href="../frontend/">
Frontend
</a>
<ul class="articles">
<li class="chapter " data-level="1.6.1" data-path="../frontend/basics.html">
<a href="../frontend/basics.html">
Basics
</a>
</li>
<li class="chapter " data-level="1.6.2" data-path="../frontend/browser.html">
<a href="../frontend/browser.html">
Browser integration
</a>
</li>
<li class="chapter " data-level="1.6.3" data-path="../frontend/controllers.html">
<a href="../frontend/controllers.html">
Controllers
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.7" data-path="../performance.html">
<a href="../performance.html">
Performance
</a>
</li>
<li class="chapter " data-level="1.8" data-path="../interceptors.html">
<a href="../interceptors.html">
Interceptors (WIP)
</a>
</li>
<li class="chapter " data-level="1.9" data-path="../development.html">
<a href="../development.html">
Development Instructions
</a>
</li>
<li class="chapter " data-level="1.10" data-path="../faq.html">
<a href="../faq.html">
FAQ
</a>
</li>
<li class="divider"></li>
<li>
<a href="https://www.gitbook.com" target="blank" class="gitbook-link">
Published with GitBook
</a>
</li>
</ul>
</nav>
</div>
<div class="book-body">
<div class="body-inner">
<div class="book-header" role="navigation">
<!-- Title -->
<h1>
<i class="fa fa-circle-o-notch fa-spin"></i>
<a href=".." >Composing Routers</a>
</h1>
</div>
<div class="page-wrapper" tabindex="-1" role="main">
<div class="page-inner">
<div id="book-search-results">
<div class="search-noresults">
<section class="normal markdown-section">
<h1 id="composing-routers">Composing Routers</h1>
<p>Data-driven approach in <code>reitit</code> allows us to compose routes, route data, middleware and interceptors chains. We can compose routers too. This is needed to achieve dynamic routing in <a href="https://github.com/weavejester/compojure" target="_blank">Compojure</a>.</p>
<h2 id="immutatability">Immutatability</h2>
<p>Once a router is created, the routing tree is immutable and cannot be changed. To change the routing, we need to create a new router with changed routes and/or options. For this, the <code>Router</code> protocol exposes it&apos;s resolved routes via <code>r/routes</code> and options via <code>r/options</code>.</p>
<h2 id="adding-routes">Adding routes</h2>
<p>Let&apos;s create a router:</p>
<pre><code class="lang-clj">(<span class="hljs-name">require</span> &apos;[reitit.core <span class="hljs-symbol">:as</span> r])
(<span class="hljs-name"><span class="hljs-builtin-name">def</span></span> router
(<span class="hljs-name">r/router</span>
[[<span class="hljs-string">&quot;/foo&quot;</span> <span class="hljs-symbol">::foo</span>]
[<span class="hljs-string">&quot;/bar/:id&quot;</span> <span class="hljs-symbol">::bar</span>]]))
</code></pre>
<p>We can query it&apos;s resolved routes and options:</p>
<pre><code class="lang-clj">(<span class="hljs-name">r/routes</span> router)
<span class="hljs-comment">;[[&quot;/foo&quot; {:name :user/foo}]</span>
<span class="hljs-comment">; [&quot;/bar/:id&quot; {:name :user/bar}]]</span>
(<span class="hljs-name">r/options</span> router)
<span class="hljs-comment">;{:lookup #object[...]</span>
<span class="hljs-comment">; :expand #object[...]</span>
<span class="hljs-comment">; :coerce #object[...]</span>
<span class="hljs-comment">; :compile #object[...]</span>
<span class="hljs-comment">; :conflicts #object[...]}</span>
</code></pre>
<p>Let&apos;s add a helper function to create a new router with extra routes:</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">defn</span></span> add-routes [router routes]
(<span class="hljs-name">r/router</span>
(<span class="hljs-name"><span class="hljs-builtin-name">into</span></span> (<span class="hljs-name">r/routes</span> router) routes)
(<span class="hljs-name">r/options</span> router)))
</code></pre>
<p>We can now create a new router with an extra routes:</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">def</span></span> router2
(<span class="hljs-name">add-routes</span>
router
[[<span class="hljs-string">&quot;/baz/:id/:subid&quot;</span> <span class="hljs-symbol">::baz</span>]]))
(<span class="hljs-name">r/routes</span> router2)
<span class="hljs-comment">;[[&quot;/foo&quot; {:name :user/foo}]</span>
<span class="hljs-comment">; [&quot;/bar/:id&quot; {:name :user/bar}]</span>
<span class="hljs-comment">; [&quot;/baz/:id/:subid&quot; {:name :user/baz}]]</span>
</code></pre>
<p>The original router was now changed:</p>
<pre><code class="lang-clj">(<span class="hljs-name">r/routes</span> router)
<span class="hljs-comment">;[[&quot;/foo&quot; {:name :user/foo}]</span>
<span class="hljs-comment">; [&quot;/bar/:id&quot; {:name :user/bar}]]</span>
</code></pre>
<p>When a new router is created, all rules are applied, including the conflict resolution:</p>
<pre><code class="lang-clj">(<span class="hljs-name">add-routes</span>
router2
[[<span class="hljs-string">&quot;/:this/should/:fail&quot;</span> <span class="hljs-symbol">::fail</span>]])
<span class="hljs-comment">;CompilerException clojure.lang.ExceptionInfo: Router contains conflicting route paths:</span>
<span class="hljs-comment">;</span>
<span class="hljs-comment">; /baz/:id/:subid</span>
<span class="hljs-comment">;-&gt; /:this/should/:fail</span>
</code></pre>
<h2 id="merging-routers">Merging routers</h2>
<p>Let&apos;s create a helper function to merge routers:</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">defn</span></span> merge-routers [&amp; routers]
(<span class="hljs-name">r/router</span>
(<span class="hljs-name"><span class="hljs-builtin-name">apply</span></span> merge (<span class="hljs-name"><span class="hljs-builtin-name">map</span></span> r/routes routers))
(<span class="hljs-name"><span class="hljs-builtin-name">apply</span></span> merge (<span class="hljs-name"><span class="hljs-builtin-name">map</span></span> r/options routers))))
</code></pre>
<p>We can now merge multiple routers into one:</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">def</span></span> router
(<span class="hljs-name">merge-routers</span>
(<span class="hljs-name">r/router</span> [<span class="hljs-string">&quot;/route1&quot;</span> <span class="hljs-symbol">::route1</span>])
(<span class="hljs-name">r/router</span> [<span class="hljs-string">&quot;/route2&quot;</span> <span class="hljs-symbol">::route2</span>])
(<span class="hljs-name">r/router</span> [<span class="hljs-string">&quot;/route3&quot;</span> <span class="hljs-symbol">::route3</span>])))
(<span class="hljs-name">r/routes</span> router)
<span class="hljs-comment">;[[&quot;/route1&quot; {:name :user/route1}]</span>
<span class="hljs-comment">; [&quot;/route2&quot; {:name :user/route2}]</span>
<span class="hljs-comment">; [&quot;/route3&quot; {:name :user/route3}]]</span>
</code></pre>
<h2 id="nesting-routers">Nesting routers</h2>
<p>Routers can be nested using the catch-all parameter.</p>
<p>Here&apos;s a router with nested routers under a custom <code>:router</code> key in the route data:</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">def</span></span> router
(<span class="hljs-name">r/router</span>
[[<span class="hljs-string">&quot;/ping&quot;</span> <span class="hljs-symbol">:ping</span>]
[<span class="hljs-string">&quot;/olipa/*&quot;</span> {<span class="hljs-symbol">:name</span> <span class="hljs-symbol">:olipa</span>
<span class="hljs-symbol">:router</span> (<span class="hljs-name">r/router</span>
[[<span class="hljs-string">&quot;/olut&quot;</span> <span class="hljs-symbol">:olut</span>]
[<span class="hljs-string">&quot;/makkara&quot;</span> <span class="hljs-symbol">:makkara</span>]
[<span class="hljs-string">&quot;/kerran/*&quot;</span> {<span class="hljs-symbol">:name</span> <span class="hljs-symbol">:kerran</span>
<span class="hljs-symbol">:router</span> (<span class="hljs-name">r/router</span>
[[<span class="hljs-string">&quot;/avaruus&quot;</span> <span class="hljs-symbol">:avaruus</span>]
[<span class="hljs-string">&quot;/ihminen&quot;</span> <span class="hljs-symbol">:ihminen</span>]])}]])}]]))
</code></pre>
<p>Matching by path:</p>
<pre><code class="lang-clj">(<span class="hljs-name">r/match-by-path</span> router <span class="hljs-string">&quot;/olipa/kerran/iso/kala&quot;</span>)
<span class="hljs-comment">;#Match{:template &quot;/olipa/*&quot;</span>
<span class="hljs-comment">; :data {:name :olipa</span>
<span class="hljs-comment">; :router #object[reitit.core$mixed_router]}</span>
<span class="hljs-comment">; :result nil</span>
<span class="hljs-comment">; :path-params {: &quot;kerran/iso/kala&quot;}</span>
<span class="hljs-comment">; :path &quot;/olipa/iso/kala&quot;}</span>
</code></pre>
<p>That didn&apos;t work as we wanted, as there is no nested route that matches. Thing is that the core routing doesn&apos;t understand anything about our ad-hoc invented <code>:router</code> key, so it only matched against the top-level router, which gave a match for the catch-all path.</p>
<p>As the <code>Match</code> contains all the route data, we can create a new matching function that understands the new <code>:router</code> syntax. Below is a function that does recursive matching using the subrouters. It returns either <code>nil</code> or a vector of mathces.</p>
<pre><code class="lang-clj">(<span class="hljs-name">require</span> &apos;[clojure.string <span class="hljs-symbol">:as</span> str])
(<span class="hljs-name"><span class="hljs-builtin-name">defn</span></span> recursive-match-by-path [router path]
(<span class="hljs-name"><span class="hljs-builtin-name">if-let</span></span> [match (<span class="hljs-name">r/match-by-path</span> router path)]
(<span class="hljs-name"><span class="hljs-builtin-name">if-let</span></span> [subrouter (<span class="hljs-name"><span class="hljs-builtin-name">-&gt;</span></span> match <span class="hljs-symbol">:data</span> <span class="hljs-symbol">:router</span>)]
(<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> [subpath (<span class="hljs-name">subs</span> path (<span class="hljs-name">str/last-index-of</span> (<span class="hljs-symbol">:template</span> match) <span class="hljs-string">&quot;/&quot;</span>))]
(<span class="hljs-name"><span class="hljs-builtin-name">if-let</span></span> [submatch (<span class="hljs-name">recursive-match-by-path</span> subrouter subpath)]
(<span class="hljs-name"><span class="hljs-builtin-name">cons</span></span> match submatch)))
(<span class="hljs-name"><span class="hljs-builtin-name">list</span></span> match))))
</code></pre>
<p>With invalid nested path we get now <code>nil</code> as expected:</p>
<pre><code class="lang-clj">(<span class="hljs-name">recursive-match-by-path</span> router <span class="hljs-string">&quot;/olipa/kerran/iso/kala&quot;</span>)
<span class="hljs-comment">; nil</span>
</code></pre>
<p>With valid path we get all the nested matches:</p>
<pre><code class="lang-clj">(<span class="hljs-name">recursive-match-by-path</span> router <span class="hljs-string">&quot;/olipa/kerran/avaruus&quot;</span>)
<span class="hljs-comment">;[#reitit.core.Match{:template &quot;/olipa/*&quot;</span>
<span class="hljs-comment">; :data {:name :olipa</span>
<span class="hljs-comment">; :router #object[reitit.core$mixed_router]}</span>
<span class="hljs-comment">; :result nil</span>
<span class="hljs-comment">; :path-params {: &quot;kerran/avaruus&quot;}</span>
<span class="hljs-comment">; :path &quot;/olipa/kerran/avaruus&quot;}</span>
<span class="hljs-comment">; #reitit.core.Match{:template &quot;/kerran/*&quot;</span>
<span class="hljs-comment">; :data {:name :kerran</span>
<span class="hljs-comment">; :router #object[reitit.core$lookup_router]}</span>
<span class="hljs-comment">; :result nil</span>
<span class="hljs-comment">; :path-params {: &quot;avaruus&quot;}</span>
<span class="hljs-comment">; :path &quot;/kerran/avaruus&quot;}</span>
<span class="hljs-comment">; #reitit.core.Match{:template &quot;/avaruus&quot; </span>
<span class="hljs-comment">; :data {:name :avaruus} </span>
<span class="hljs-comment">; :result nil </span>
<span class="hljs-comment">; :path-params {} </span>
<span class="hljs-comment">; :path &quot;/avaruus&quot;}]</span>
</code></pre>
<p>Let&apos;s create a helper to get only the route names for matches:</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">defn</span></span> name-path [router path]
(<span class="hljs-name">some-&gt;&gt;</span> (<span class="hljs-name">recursive-match-by-path</span> router path)
(<span class="hljs-name"><span class="hljs-builtin-name">mapv</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">comp</span></span> <span class="hljs-symbol">:name</span> <span class="hljs-symbol">:data</span>))))
(<span class="hljs-name">name-path</span> router <span class="hljs-string">&quot;/olipa/kerran/avaruus&quot;</span>)
<span class="hljs-comment">; [:olipa :kerran :avaruus]</span>
</code></pre>
<p>So, we can nest routers, but why would we do that?</p>
<h2 id="dynamic-routing">Dynamic routing</h2>
<p>In all the examples above, the routers were created ahead of time, making the whole route tree effective static. To have more dynamic routing, we can use router references allowing the immutable routes to be changed over time. We can also create fully dynamic nested routers that allow routes to be generated on request basis. Let&apos;s walk through both cases.</p>
<p>First, we need to modify our matching function to support router references:</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">defn-</span></span> &lt;&lt; [x]
(<span class="hljs-name"><span class="hljs-builtin-name">if</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">instance?</span></span> clojure.lang.IDeref x)
(<span class="hljs-name"><span class="hljs-builtin-name">deref</span></span> x) x))
(<span class="hljs-name"><span class="hljs-builtin-name">defn</span></span> recursive-match-by-path [router path]
(<span class="hljs-name"><span class="hljs-builtin-name">if-let</span></span> [match (<span class="hljs-name">r/match-by-path</span> (<span class="hljs-name">&lt;&lt;</span> router) path)]
(<span class="hljs-name"><span class="hljs-builtin-name">if-let</span></span> [subrouter (<span class="hljs-name"><span class="hljs-builtin-name">-&gt;</span></span> match <span class="hljs-symbol">:data</span> <span class="hljs-symbol">:router</span> &lt;&lt;)]
(<span class="hljs-name"><span class="hljs-builtin-name">let</span></span> [subpath (<span class="hljs-name">subs</span> path (<span class="hljs-name">str/last-index-of</span> (<span class="hljs-symbol">:template</span> match) <span class="hljs-string">&quot;/&quot;</span>))]
(<span class="hljs-name"><span class="hljs-builtin-name">if-let</span></span> [submatch (<span class="hljs-name">recursive-match-by-path</span> subrouter subpath)]
(<span class="hljs-name"><span class="hljs-builtin-name">cons</span></span> match submatch)))
(<span class="hljs-name"><span class="hljs-builtin-name">list</span></span> match))))
</code></pre>
<p>Then, we need some routers.</p>
<p>First, a router that can be updated on demand on background, for example based on a domain event when a new entry in inserted into a database. We&apos;ll wrap the router into a <code>atom</code> to achieve this.</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">def</span></span> beer-router
(<span class="hljs-name"><span class="hljs-builtin-name">atom</span></span>
(<span class="hljs-name">r/router</span>
[[<span class="hljs-string">&quot;/lager&quot;</span> <span class="hljs-symbol">:lager</span>]])))
</code></pre>
<p>Another, fully dynamic router, which is re-created on each routing request.</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">def</span></span> dynamic-router
(<span class="hljs-name"><span class="hljs-builtin-name">reify</span></span> clojure.lang.IDeref
(<span class="hljs-name"><span class="hljs-builtin-name">deref</span></span> [_]
(<span class="hljs-name">r/router</span>
[<span class="hljs-string">&quot;/duo&quot;</span> (<span class="hljs-name"><span class="hljs-builtin-name">keyword</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">str</span></span> <span class="hljs-string">&quot;duo&quot;</span> (<span class="hljs-name"><span class="hljs-builtin-name">rand-int</span></span> <span class="hljs-number">100</span>)))]))))
</code></pre>
<p>We can compose the routers into a system-level static root router.</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">def</span></span> router
(<span class="hljs-name">r/router</span>
[[<span class="hljs-string">&quot;/gin/napue&quot;</span> <span class="hljs-symbol">:napue</span>]
[<span class="hljs-string">&quot;/ciders/*&quot;</span> <span class="hljs-symbol">:ciders</span>]
[<span class="hljs-string">&quot;/beers/*&quot;</span> {<span class="hljs-symbol">:name</span> <span class="hljs-symbol">:beers</span>
<span class="hljs-symbol">:router</span> beer-router}]
[<span class="hljs-string">&quot;/dynamic/*&quot;</span> {<span class="hljs-symbol">:name</span> <span class="hljs-symbol">:dynamic</span>
<span class="hljs-symbol">:router</span> dynamic-router}]]))
</code></pre>
<p>Matching root routes:</p>
<pre><code class="lang-clj">(<span class="hljs-name">name-path</span> <span class="hljs-string">&quot;/vodka/russian&quot;</span>)
<span class="hljs-comment">; nil</span>
(<span class="hljs-name">name-path</span> <span class="hljs-string">&quot;/gin/napue&quot;</span>)
<span class="hljs-comment">; [:napue]</span>
</code></pre>
<p>Matching (nested) beer routes:</p>
<pre><code class="lang-clj">(<span class="hljs-name">name-path</span> <span class="hljs-string">&quot;/beers/lager&quot;</span>)
<span class="hljs-comment">; [:beers :lager]</span>
(<span class="hljs-name">name-path</span> <span class="hljs-string">&quot;/beers/saison&quot;</span>)
<span class="hljs-comment">; nil</span>
</code></pre>
<p>No saison!? Let&apos;s add the route:</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">swap!</span></span> beer-router add-routes [[<span class="hljs-string">&quot;/saison&quot;</span> <span class="hljs-symbol">:saison</span>]])
</code></pre>
<p>There we have it:</p>
<pre><code class="lang-clj">(<span class="hljs-name">name-path</span> <span class="hljs-string">&quot;/beers/saison&quot;</span>)
<span class="hljs-comment">; [:beers :saison]</span>
</code></pre>
<p>We can&apos;t add a conflicting routes:</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">swap!</span></span> beer-router add-routes [[<span class="hljs-string">&quot;/saison&quot;</span> <span class="hljs-symbol">:saison</span>]])
<span class="hljs-comment">;CompilerException clojure.lang.ExceptionInfo: Router contains conflicting route paths:</span>
<span class="hljs-comment">;</span>
<span class="hljs-comment">; /saison</span>
<span class="hljs-comment">;-&gt; /saison</span>
</code></pre>
<p>The dynamic routes are re-created on every request:</p>
<pre><code class="lang-clj">(<span class="hljs-name">name-path</span> <span class="hljs-string">&quot;/dynamic/duo&quot;</span>)
<span class="hljs-comment">; [:dynamic :duo71]</span>
(<span class="hljs-name">name-path</span> <span class="hljs-string">&quot;/dynamic/duo&quot;</span>)
<span class="hljs-comment">; [:dynamic :duo55]</span>
</code></pre>
<h3 id="performance">Performance</h3>
<p>With nested routers, instead of having to do just one route match, matching is recursive, which adds a small cost. All nested routers need to be of type catch-all at top-level, which is order of magnitude slower than fully static routes. Dynamic routes are the slowest ones, at least an order of magnitude slower, as the router needs to be recreated for each request.</p>
<p>Here&apos;s a quick benchmark on the recursive matches.</p>
<table>
<thead>
<tr>
<th>path</th>
<th>time</th>
<th>type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/gin/napue</code></td>
<td>40ns</td>
<td>static</td>
</tr>
<tr>
<td><code>/ciders/weston</code></td>
<td>440ns</td>
<td>catch-all</td>
</tr>
<tr>
<td><code>/beers/saison</code></td>
<td>600ns</td>
<td>catch-all + static</td>
</tr>
<tr>
<td><code>/dynamic/duo</code></td>
<td>12000ns</td>
<td>catch-all + dynamic</td>
</tr>
</tbody>
</table>
<h3 id="when-to-use-nested-routers">When to use nested routers?</h3>
<p>Nesting routers is not trivial and because of that, should be avoided. For dynamic (request-time) route generation, it&apos;s the only choise. For other cases, nested routes are most likely a better option. Let&apos;s re-create the previous example with normal route composition.</p>
<p>A helper to create beer-routes and the root router. We will generate fully qualified beer names so that the route names won&apos;t clash so easily.</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">defn</span></span> beer-routes [beers]
(<span class="hljs-name"><span class="hljs-builtin-name">for</span></span> [beer beers]
[(<span class="hljs-name"><span class="hljs-builtin-name">str</span></span> <span class="hljs-string">&quot;/&quot;</span> beer) (<span class="hljs-name"><span class="hljs-builtin-name">keyword</span></span> (<span class="hljs-name"><span class="hljs-builtin-name">str</span></span> *ns*) beer)]))
(<span class="hljs-name"><span class="hljs-builtin-name">defn</span></span> create-router [beer-routes]
(<span class="hljs-name">r/router</span>
[[<span class="hljs-string">&quot;/gin/napue&quot;</span> <span class="hljs-symbol">:napue</span>]
[<span class="hljs-string">&quot;/ciders/*&quot;</span> <span class="hljs-symbol">:ciders</span>]
[<span class="hljs-string">&quot;/beers&quot;</span> beer-routes]
[<span class="hljs-string">&quot;/dynamic/*&quot;</span> {<span class="hljs-symbol">:name</span> <span class="hljs-symbol">:dynamic</span>
<span class="hljs-symbol">:router</span> dynamic-router}]]))
</code></pre>
<p>New new root router <em>reference</em> and a helper to reset it</p>
<pre><code class="lang-clj">(<span class="hljs-name"><span class="hljs-builtin-name">def</span></span> router
(<span class="hljs-name"><span class="hljs-builtin-name">atom</span></span> (<span class="hljs-name">create-router</span> <span class="hljs-literal">nil</span>)))
(<span class="hljs-name"><span class="hljs-builtin-name">defn</span></span> reset-router! [beers]
(<span class="hljs-name"><span class="hljs-builtin-name">reset!</span></span> router (<span class="hljs-name"><span class="hljs-builtin-name">-&gt;</span></span> beers beer-routes create-router)))
</code></pre>
<p>Let&apos;s reset the router with some beers:</p>
<pre><code class="lang-clj">(<span class="hljs-name">reset-router!</span> [<span class="hljs-string">&quot;lager&quot;</span> <span class="hljs-string">&quot;sahti&quot;</span> <span class="hljs-string">&quot;bock&quot;</span>])
</code></pre>
<p>We can see that the beer routes are now embedded into the core router:</p>
<pre><code>(r/routes @router)
;[[&quot;/gin/napue&quot; {:name :napue}]
; [&quot;/ciders/*&quot; {:name :ciders}]
; [&quot;/beers/lager&quot; {:name :user/lager}]
; [&quot;/beers/sahti&quot; {:name :user/sahti}]
; [&quot;/beers/bock&quot; {:name :user/bock}]
; [&quot;/dynamic/*&quot; {:name :dynamic,
; :router #object[user$reify__24359]}]]
</code></pre><p>And the routing works:</p>
<pre><code class="lang-clj">(<span class="hljs-name">name-path</span> @router <span class="hljs-string">&quot;/beers/sahti&quot;</span>)
<span class="hljs-comment">;[:user/sahti]</span>
</code></pre>
<p>The beer-routes now route in constant time of ~40ns, compared to ~600ns in the previous example.</p>
<h2 id="todo">TODO</h2>
<ul>
<li>add an example how to do dynamic routing with <code>reitit-ring</code></li>
<li>maybe create a <code>recursive-router</code> into a separate ns with all <code>Router</code> functions implemented correctly? maybe not...</li>
<li>add <code>reitit.core/merge-routes</code> to effectively merge routes with route data</li>
</ul>
</section>
</div>
<div class="search-results">
<div class="has-results">
<h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
<ul class="search-results-list"></ul>
</div>
<div class="no-results">
<h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
</div>
</div>
</div>
</div>
</div>
</div>
<a href="configuring_routers.html" class="navigation navigation-prev " aria-label="Previous page: Configuring Routers">
<i class="fa fa-angle-left"></i>
</a>
<a href="different_routers.html" class="navigation navigation-next " aria-label="Next page: Different Routers">
<i class="fa fa-angle-right"></i>
</a>
</div>
<script>
var gitbook = gitbook || [];
gitbook.push(function() {
gitbook.page.hasChanged({"page":{"title":"Composing Routers","level":"1.4.2","depth":2,"next":{"title":"Different Routers","level":"1.4.3","depth":2,"path":"advanced/different_routers.md","ref":"advanced/different_routers.md","articles":[]},"previous":{"title":"Configuring Routers","level":"1.4.1","depth":2,"path":"advanced/configuring_routers.md","ref":"advanced/configuring_routers.md","articles":[]},"dir":"ltr"},"config":{"plugins":["editlink","github","highlight"],"root":"doc","styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"editlink":{"label":"Edit This Page","multilingual":false,"base":"https://github.com/metosin/reitit/tree/master/doc"},"github":{"url":"https://github.com/metosin/reitit"},"highlight":{},"search":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"sharing":{"facebook":true,"twitter":true,"google":false,"weibo":false,"instapaper":false,"vk":false,"all":["facebook","google","twitter","weibo","instapaper"]},"fontsettings":{"theme":"white","family":"sans","size":2},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","pdf":{"pageNumbers":true,"fontSize":12,"fontFamily":"Arial","paperSize":"a4","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56}},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"gitbook":"*"},"file":{"path":"advanced/composing_routers.md","mtime":"2018-07-26T07:40:18.058Z","type":"markdown"},"gitbook":{"version":"3.2.3","time":"2018-07-26T07:40:53.641Z"},"basePath":"..","book":{"language":""}});
});
</script>
</div>
<script src="../gitbook/gitbook.js"></script>
<script src="../gitbook/theme.js"></script>
<script src="../gitbook/gitbook-plugin-editlink/plugin.js"></script>
<script src="../gitbook/gitbook-plugin-github/plugin.js"></script>
<script src="../gitbook/gitbook-plugin-search/search-engine.js"></script>
<script src="../gitbook/gitbook-plugin-search/search.js"></script>
<script src="../gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
<script src="../gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
<script src="../gitbook/gitbook-plugin-sharing/buttons.js"></script>
<script src="../gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
</body>
</html>