SkillAgentSearch skills...

Bidi

Bidirectional URI routing

Install / Use

/learn @juxt/Bidi
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

bidi

Join the chat at https://gitter.im/juxt/bidi

"bidi bidi bidi" -- Twiki, in probably every episode of Buck Rogers in the 25th Century

In the grand tradition of Clojure libraries we begin with an irrelevant quote.

Bi-directional URI dispatch. Like Compojure, but when you want to go both ways. If you are serving REST resources, you should be providing links to other resources, and without full support for forming URIs from handlers your code will become coupled with your routing. In short, hard-coded URIs will eventually break.

In bidi, routes are data structures, there are no macros here. Generally speaking, data structures are to be preferred over code structures. When routes are defined in a data structure there are numerous advantages - they can be read in from a configuration file, generated, computed, transformed by functions and introspected - all things which macro-based DSLs make harder.

For example, suppose you wanted to use the same set of routes in your application and in your production Nginx or HAProxy configuration. Having your routes defined in a single data structure means you can programmatically generate your configuration, making your environments easier to manage and reducing the chance of discrepancies.

bidi also avoids 'terse' forms for the route definitions- reducing the number of parsing rules for the data structure is valued over convenience for the programmer. Convenience can always be added later with macros.

Finally, the logic for matching routes is separated from the responsibility for handling requests. This is an important architectural principle. So you can match on things that aren't necessarily handlers, like keywords which you can use to lookup your handlers, or whatever you want to do. Separation of concerns and all that.

Comparison with other routing libraries

There are numerous Clojure(Script) routing libraries. Here's a table to help you compare.

<table> <thead> <tr> <th>Library</th> <th>clj</th> <th>cljs</th> <th>Syntax</th> <th>Isomorphic?</th> <th>Self-contained?</th> <th>Extensible?</th> </tr> </thead> <tbody> <tr> <td><a href="https://github.com/weavejester/compojure">Compojure</a></td> <td>✔</td> <td></td> <td>Macros</td> <td></td> <td>✔</td> <td></td> </tr> <tr> <td><a href="https://github.com/cgrand/moustache">Moustache</a></td> <td>✔</td> <td></td> <td>Macros</td> <td></td> <td>✔</td> <td></td> </tr> <tr> <td><a href="https://github.com/clojurewerkz/route-one">RouteOne</a></td> <td>✔</td> <td></td> <td>Macros</td> <td>✔</td> <td>✔</td> <td></td> </tr> <tr> <td><a href="http://pedestal.io/">Pedestal</a></td> <td>✔</td> <td></td> <td>Data</td> <td>✔</td> <td>✔</td> <td>✔</td> </tr> <tr> <td><a href="https://github.com/thatismatt/gudu">gudu</a></td> <td>✔</td> <td></td> <td>Data</td> <td>✔</td> <td>✔</td> <td></td> </tr> <tr> <td><a href="https://github.com/gf3/secretary">secretary</a></td> <td></td> <td>✔</td> <td>Macros</td> <td>✔</td> <td>✔</td> <td>✔</td> </tr> <tr> <td><a href="https://github.com/DomKM/silk">silk</a></td> <td>✔</td> <td>✔</td> <td>Data</td> <td>✔</td> <td>✔</td> <td>✔</td> </tr> <tr> <td><a href="https://github.com/Prismatic/fnhouse">fnhouse</a></td> <td>✔</td> <td></td> <td>Macros</td> <td></td> <td></td> <td>✔</td> </tr> <tr> <td>bidi</td> <td>✔</td> <td>✔</td> <td>Data</td> <td>✔</td> <td>✔</td> <td>✔</td> </tr> <tr> <td><a href="https://github.com/metosin/reitit">reitit</a></td> <td>✔</td> <td>✔</td> <td>Data</td> <td>✔</td> <td>✔</td> <td>✔</td> </tr> </tbody> </table>

bidi is written to do 'one thing well' (URI dispatch and formation) and is intended for use with Ring middleware, HTTP servers (including Jetty, http-kit and aleph) and is fully compatible with Liberator.

If you're using with Liberator, see https://github.com/juxt/bidi/issues/95 for some more details on how to use them together.

Installation

Add the following dependency to your project.clj file

Clojars Project Build Status

As bidi uses Clojure's reader conditionals, bidi is dependent on both Clojure 1.7 and Leiningen 2.5.3 or later.

Version 2.x

Version 2.x builds on 1.x by providing a mechanism to envelope multiple virtual hosts with a single route map. The idea is to eventually create a route map which defines routes across multiple services and helps with the construction of URIs to other services, a process which is traditionally error-prone.

Version 2.x is backward compatible and forward compatible with version 1.x. If you are upgrading from 1.x to 2.x you will not need to change your existing route definitions.

Take 5 minutes to learn bidi (using the REPL)

Let's create a route that matches /index.html. A route is simply a pair, containing a pattern and a result.

user> (def route ["/index.html" :index])
#'user/route

Let's try to match that route to a path.

user> (use 'bidi.bidi)
nil
user> (match-route route "/index.html")
{:handler :index}

We have a match! A map is returned with a single entry with a :handler key and :index as the value. We could use this result, for example, to look up a Ring handler in a map mapping keywords to Ring handlers.

What happens if we try a different path?

user> (match-route route "/another.html")
nil

We get a nil. Nil means 'no route matched'.

Now, let's go in the other direction.

user> (path-for route :index)
"/index.html"

We ask bidi to use the same route definition to tell us the path that would match the :index handler. In this case, it tells us /index.html. So if you were forming a link to this handler from another page, you could use this function in your view logic to create the link instead of hardcoding in the view template (This gives your code more resilience to changes in the organisation of routes during development).

Multiple routes

Now let's suppose we have 2 routes. We match partially on their common prefix, which in this case is "/" but we could use "" if there were no common prefix. The patterns for the remaining path can be specified in a map (or vector of pairs, if order is important).

user> (def my-routes ["/" {"index.html" :index
                           "article.html" :article}])
#'user/my-routes

Since each entry in the map is itself a route, you can nest these recursively.

user> (def my-routes ["/" {"index.html" :index
                           "articles/" {"index.html" :article-index
                                        "article.html" :article}}])
#'user/my-routes

We can match these routes as before :-

user> (match-route my-routes "/index.html")
{:handler :index}
user> (match-route my-routes "/articles/article.html")
{:handler :article}

and in reverse too :-

user> (path-for my-routes :article-index)
"/articles/index.html"

Route patterns

It's common to want to match on a pattern or template, extracting some variable from the URI. Rather than including special characters in strings, we construct the pattern in segments using a Clojure vector [:id "/article.html"]. This vector replaces the string we had in the left hand side of the route pair.

user> (def my-routes ["/" {"index.html" :index
                           "articles/" {"index.html" :article-index
                                        [:id "/article.html"] :article}}])
#'user/my-routes

Now, when we match on an article path, the keyword values are extracted into a map.

user> (match-route my-routes "/articles/123/article.html")
{:handler :article, :route-params {:id "123"}}
user> (match-route my-routes "/articles/999/article.html")
{:handler :article, :route-params {:id "999"}}

To form the path we need to supply the value of :id as extra arguments to the path-for function.

user> (path-for my-routes :article :id 123)
"/articles/123/article.html"
user> (path-for my-routes :article :id 999)
"/articles/999/article.html"

If you don't specify a required parameter an exception is thrown.

Apart from a few extra bells and whistles documented in the rest of this README, that's basically it. Your five minutes are up!

Verbose syntax

bidi also supports a verbose syntax which "compiles" to the more terse default syntax. For example:

(require '[bidi.verbose :refer [branch param leaf]])

(branch
 "http://localhost:8080"
 (branch "/users/" (param :user-id)
         (branch "/topics"
                 (leaf "" :topics)
                 (leaf "/bulk" :topic-bulk)))
 (branch "/topics/" (param :topic)
         (leaf "" :private-topic))
 (leaf "/schemas" :schemas)
 (branch "/orgs/" (param :org-id)
         (leaf "/topics" :org-topics)))

Will produce the following routes:

["http://localhost:8080"
 [[["/users/" :user-id]
   [["/topics" [["" :topics] ["/bulk" :topic-bulk]]]]]
  [["/topics/" :topic] [["" :private-topic]]]
  ["/schemas" :schemas]
  [["/orgs/" :org-id] [["/topics" :org-topics]]]]]

Going further

Here are some extra topics you'll need to know to use bidi in a project.

Wrapping as a Ring handler

Match results can be any value, but are typically functions (either in-line or via a symbol reference). You can easily wrap your ro

View on GitHub
GitHub Stars1.0k
CategoryDevelopment
Updated19d ago
Forks88

Languages

Clojure

Security Score

100/100

Audited on Mar 8, 2026

No findings