{"id":13393111,"url":"https://github.com/juxt/bidi","last_synced_at":"2025-04-12T01:54:19.140Z","repository":{"id":12675176,"uuid":"15347209","full_name":"juxt/bidi","owner":"juxt","description":"Bidirectional URI routing","archived":false,"fork":false,"pushed_at":"2023-03-15T10:46:57.000Z","size":730,"stargazers_count":998,"open_issues_count":60,"forks_count":90,"subscribers_count":28,"default_branch":"master","last_synced_at":"2025-03-19T11:46:34.014Z","etag":null,"topics":["clojure","clojurescript","router","routing","uri","url"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juxt.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2013-12-20T20:10:30.000Z","updated_at":"2025-03-11T19:44:37.000Z","dependencies_parsed_at":"2022-07-19T18:04:19.781Z","dependency_job_id":"3718f6e5-454d-485c-b3c7-0de2d1aa495a","html_url":"https://github.com/juxt/bidi","commit_stats":{"total_commits":485,"total_committers":55,"mean_commits":8.818181818181818,"dds":"0.20618556701030932","last_synced_commit":"d94316235c4e59d7c44fca9fcc489b386f526bcc"},"previous_names":[],"tags_count":98,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juxt%2Fbidi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juxt%2Fbidi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juxt%2Fbidi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juxt%2Fbidi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juxt","download_url":"https://codeload.github.com/juxt/bidi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245945318,"owners_count":20698208,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clojurescript","router","routing","uri","url"],"created_at":"2024-07-30T17:00:43.442Z","updated_at":"2025-03-28T22:09:14.360Z","avatar_url":"https://github.com/juxt.png","language":"Clojure","funding_links":[],"categories":["Clojure","Features","Awesome ClojureScript"],"sub_categories":["Routing"],"readme":"# bidi\n\n[![Join the chat at https://gitter.im/juxt/bidi](https://badges.gitter.im/juxt/bidi.svg)](https://gitter.im/juxt/bidi?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\n\u003e \"bidi bidi bidi\" -- Twiki, in probably every episode of\n  [Buck Rogers in the 25th Century](http://en.wikipedia.org/wiki/Buck_Rogers_in_the_25th_Century_%28TV_series%29)\n\nIn the grand tradition of Clojure libraries we begin with an irrelevant\nquote.\n\nBi-directional URI dispatch. Like\n[Compojure](https://github.com/weavejester/compojure), but when you want\nto go both ways. If you are serving REST resources, you should be\n[providing links](http://en.wikipedia.org/wiki/HATEOAS) to other\nresources, and without full support for forming URIs from handlers\nyour code will become coupled with your routing. In short, hard-coded\nURIs will eventually break.\n\nIn bidi, routes are *data structures*, there are no macros here. Generally\nspeaking, data structures are to be preferred over code structures. When\nroutes are defined in a data structure there are numerous\nadvantages - they can be read in from a configuration file, generated,\ncomputed, transformed by functions and introspected - all things which\nmacro-based DSLs make harder.\n\nFor example, suppose you wanted to use the same set of routes in your\napplication and in your production [Nginx](http://wiki.nginx.org/Main)\nor [HAProxy](http://haproxy.1wt.eu/) configuration. Having your routes\ndefined in a single data structure means you can programmatically\ngenerate your configuration, making your environments easier to manage\nand reducing the chance of discrepancies.\n\nbidi also avoids 'terse' forms for the route definitions- reducing the\nnumber of parsing rules for the data structure is valued over\nconvenience for the programmer. Convenience can always be added later\nwith macros.\n\nFinally, the logic for matching routes is separated from the\nresponsibility for handling requests. This is an important\n[architectural principle](http://www.infoq.com/presentations/Simple-Made-Easy). So\nyou can match on things that aren't necessarily handlers, like keywords\nwhich you can use to lookup your handlers, or whatever you want to\ndo. Separation of concerns and all that.\n\n## Comparison with other routing libraries\n\nThere are numerous Clojure(Script) routing libraries. Here's a table to help you compare.\n\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eLibrary\u003c/th\u003e\n\u003cth\u003eclj\u003c/th\u003e\n\u003cth\u003ecljs\u003c/th\u003e\n\u003cth\u003eSyntax\u003c/th\u003e\n\u003cth\u003eIsomorphic?\u003c/th\u003e\n\u003cth\u003eSelf-contained?\u003c/th\u003e\n\u003cth\u003eExtensible?\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ca href=\"https://github.com/weavejester/compojure\"\u003eCompojure\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003eMacros\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ca href=\"https://github.com/cgrand/moustache\"\u003eMoustache\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003eMacros\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ca href=\"https://github.com/clojurewerkz/route-one\"\u003eRouteOne\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003eMacros\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ca href=\"http://pedestal.io/\"\u003ePedestal\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003eData\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ca href=\"https://github.com/thatismatt/gudu\"\u003egudu\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003eData\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ca href=\"https://github.com/gf3/secretary\"\u003esecretary\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003eMacros\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ca href=\"https://github.com/DomKM/silk\"\u003esilk\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003eData\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ca href=\"https://github.com/Prismatic/fnhouse\"\u003efnhouse\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003eMacros\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ebidi\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003eData\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ca href=\"https://github.com/metosin/reitit\"\u003ereitit\u003c/a\u003e\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003eData\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003ctd\u003e✔\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\nbidi is written to do\n['one thing well'](http://en.wikipedia.org/wiki/Unix_philosophy) (URI\ndispatch and formation) and is intended for use with Ring middleware,\nHTTP servers (including Jetty, [http-kit](http://http-kit.org/) and\n[aleph](https://github.com/ztellman/aleph)) and is fully compatible with\n[Liberator](http://clojure-liberator.github.io/liberator/).\n\nIf you're using with Liberator, see\nhttps://github.com/juxt/bidi/issues/95 for some more details on how to\nuse them together.\n\n## Installation\n\nAdd the following dependency to your `project.clj` file\n\n[![Clojars Project](https://clojars.org/bidi/latest-version.svg)](http://clojars.org/bidi)\n[![Build Status](https://circleci.com/gh/juxt/bidi.svg?style=svg)](https://circleci.com/gh/juxt/bidi)\n\nAs bidi uses Clojure's reader conditionals, bidi is dependent on both Clojure 1.7 and Leiningen 2.5.3 or later.\n\n## Version 2.x\n\nVersion 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.\n\nVersion 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.\n\n## Take 5 minutes to learn bidi (using the REPL)\n\nLet's create a route that matches `/index.html`. A route is simply a\npair, containing a pattern and a result.\n\n```clojure\nuser\u003e (def route [\"/index.html\" :index])\n#'user/route\n```\n\nLet's try to match that route to a path.\n\n```clojure\nuser\u003e (use 'bidi.bidi)\nnil\nuser\u003e (match-route route \"/index.html\")\n{:handler :index}\n```\n\nWe have a match! A map is returned with a single entry with a `:handler`\nkey and `:index` as the value. We could use this result, for example, to\nlook up a Ring handler in a map mapping keywords to Ring handlers.\n\nWhat happens if we try a different path?\n\n```clojure\nuser\u003e (match-route route \"/another.html\")\nnil\n```\n\nWe get a `nil`. Nil means 'no route matched'.\n\nNow, let's go in the other direction.\n\n```clojure\nuser\u003e (path-for route :index)\n\"/index.html\"\n```\n\nWe ask bidi to use the same route definition to tell us the path that\nwould match the `:index` handler. In this case, it tells us\n`/index.html`. So if you were forming a link to this handler from\nanother page, you could use this function in your view logic to create\nthe link instead of hardcoding in the view template (This gives your\ncode more resilience to changes in the organisation of routes during\ndevelopment).\n\n### Multiple routes\n\nNow let's suppose we have 2 routes. We match partially on their common\nprefix, which in this case is `\"/\"` but we could use `\"\"` if there were\nno common prefix. The patterns for the remaining path can be specified\nin a map (or vector of pairs, if order is important).\n\n```clojure\nuser\u003e (def my-routes [\"/\" {\"index.html\" :index\n                           \"article.html\" :article}])\n#'user/my-routes\n```\n\nSince each entry in the map is itself a route, you can nest these\nrecursively.\n\n```clojure\nuser\u003e (def my-routes [\"/\" {\"index.html\" :index\n                           \"articles/\" {\"index.html\" :article-index\n                                        \"article.html\" :article}}])\n#'user/my-routes\n```\n\nWe can match these routes as before :-\n\n```clojure\nuser\u003e (match-route my-routes \"/index.html\")\n{:handler :index}\nuser\u003e (match-route my-routes \"/articles/article.html\")\n{:handler :article}\n```\n\nand in reverse too :-\n\n```clojure\nuser\u003e (path-for my-routes :article-index)\n\"/articles/index.html\"\n```\n\n### Route patterns\n\nIt's common to want to match on a pattern or template, extracting some\nvariable from the URI. Rather than including special characters in\nstrings, we construct the pattern in segments using a Clojure vector\n`[:id \"/article.html\"]`. This vector replaces the string we had in the\nleft hand side of the route pair.\n\n```clojure\nuser\u003e (def my-routes [\"/\" {\"index.html\" :index\n                           \"articles/\" {\"index.html\" :article-index\n                                        [:id \"/article.html\"] :article}}])\n#'user/my-routes\n```\n\nNow, when we match on an article path, the keyword values are extracted into a map.\n\n```clojure\nuser\u003e (match-route my-routes \"/articles/123/article.html\")\n{:handler :article, :route-params {:id \"123\"}}\nuser\u003e (match-route my-routes \"/articles/999/article.html\")\n{:handler :article, :route-params {:id \"999\"}}\n```\n\nTo form the path we need to supply the value of `:id` as extra\narguments to the `path-for` function.\n\n```clojure\nuser\u003e (path-for my-routes :article :id 123)\n\"/articles/123/article.html\"\nuser\u003e (path-for my-routes :article :id 999)\n\"/articles/999/article.html\"\n```\n\nIf you don't specify a required parameter an exception is thrown.\n\nApart from a few extra bells and whistles documented in the rest of this\nREADME, that's basically it. Your five minutes are up!\n\n### Verbose syntax\n\nbidi also supports a verbose syntax which \"compiles\" to the more terse\ndefault syntax. For example:\n\n```clojure\n(require '[bidi.verbose :refer [branch param leaf]])\n\n(branch\n \"http://localhost:8080\"\n (branch \"/users/\" (param :user-id)\n         (branch \"/topics\"\n                 (leaf \"\" :topics)\n                 (leaf \"/bulk\" :topic-bulk)))\n (branch \"/topics/\" (param :topic)\n         (leaf \"\" :private-topic))\n (leaf \"/schemas\" :schemas)\n (branch \"/orgs/\" (param :org-id)\n         (leaf \"/topics\" :org-topics)))\n```\n\nWill produce the following routes:\n\n```clojure\n[\"http://localhost:8080\"\n [[[\"/users/\" :user-id]\n   [[\"/topics\" [[\"\" :topics] [\"/bulk\" :topic-bulk]]]]]\n  [[\"/topics/\" :topic] [[\"\" :private-topic]]]\n  [\"/schemas\" :schemas]\n  [[\"/orgs/\" :org-id] [[\"/topics\" :org-topics]]]]]\n```\n\n## Going further\n\nHere are some extra topics you'll need to know to use bidi in a project.\n\n### Wrapping as a Ring handler\n\nMatch results can be any value, but are typically functions (either\nin-line or via a symbol reference). You can easily wrap your routes to\nform a Ring handler (similar to what Compojure's `routes` and\n`defroutes` does) with the `make-handler` function.\n\n```clojure\n(ns my.handler\n  (:require [bidi.ring :refer (make-handler)]\n            [ring.util.response :as res]))\n\n(defn index-handler\n  [request]\n  (res/response \"Homepage\"))\n\n(defn article-handler\n  [{:keys [route-params]}]\n  (res/response (str \"You are viewing article: \" (:id route-params))))\n\n(def handler\n  (make-handler [\"/\" {\"index.html\" index-handler\n                      [\"articles/\" :id \"/article.html\"] article-handler}]))\n```\n\nTo chain this with middleware is simple.\n\n```clojure\n(ns my.app\n  (:require [my.handler :refer [handler]]\n            [ring.middleware.session :refer [wrap-session]\n            [ring.middleware.flash :refer [wrap-flash]))\n(def app\n  (-\u003e handler\n      wrap-session\n      wrap-flash))\n```\n\n### Regular Expressions\n\nWe've already seen how keywords can be used to extract segments from a path. By default, keywords only capture numbers and simple identifiers. This is on purpose, in a defence against injection attacks. Often you'll want to specify exactly what you're trying to capture using a regular expression.\n\nIf we want `:id` to match a number only, we can substitute the keyword with a pair, containing a regular expression followed by the keyword. For example, instead of this :-\n\n```clojure\n    [ [ \"foo/\" :id \"/bar\" ] :handler ]\n```\n\nwe write this :-\n\n```clojure\n    [ [ \"foo/\" [ #\"\\d+\" :id ] \"/bar\" ] :handler ]\n```\n\nwhich would match the string `foo/123/bar` but not `foo/abc/bar`.\n\n## Advanced topics\n\nThese features are optional, you don't need to know about them to use\nbidi, but they may come in useful.\n\n### Guards\n\nBy default, routes ignore the request method, behaving like Compojure's\n`ANY` routes. That's fine if your handlers deal with the request methods\nthemselves, as\n[Liberator](http://clojure-liberator.github.io/liberator/)'s\ndo. However, if you want to limit a route to a request method, you can\nwrap the route in a pair (or map entry), using a keyword for the\npattern. The keyword denotes the request method (`:get`, `:put`, etc.)\n\n```clojure\n[\"/\" {\"blog\" {:get {\"/index\" (fn [req] {:status 200 :body \"Index\"})}}}]\n```\n\nYou can also restrict routes by any other request criteria. Guards are\nspecified by maps. Map entries can specify a single value, a set of\npossible values or even a predicate to test a value.\n\nIn this example, the `/zip` route is only matched if the server name in\nthe request is `juxt.pro`. You can use this feature to restrict routes\nto virtual hosts or HTTP schemes.\n\n```clojure\n[\"/\" {\"blog\" {:get\n              {\"/index\" (fn [req] {:status 200 :body \"Index\"})}}\n      {:request-method :post :server-name \"juxt.pro\"}\n      {\"/zip\" (fn [req] {:status 201 :body \"Created\"})}}]\n```\n\nValues in the guard map can be values, sets of acceptable values, or\neven predicate functions to give fine-grained control over the dispatch\ncriteria.\n\n### Keywords\n\nSometimes you want segments of the URI to be extracted as keywords rather than strings, and in the reverse direction, to use keywords as values to be encoded into URIs.\n\nYou can construct a pattern similarly to how you specify regular expressions but instead of the regex you use specify `keyword` core function.\n\n```clojure\n   [ \"foo/\" [ keyword :db/ident ] \"/bar\" ]\n```\n\nWhen matching the path `foo/bidi/bar`, the `:route-params` of the result would be `{:db/ident :bidi}`. To construct the path, you would use `(path-for my-routes handler :db/ident :bidi)`, which results in `foo/bidi/bar` (the colon of the stringified keyword is omitted).\n\nNamespaced keywords are also supported. Note that in the URI the `/` that separates the keyword's namespace from its name is URL encoded to %2F, rather than `/`.\n\n### Catch-All Routes\n\nNote that you can use the pattern `true` to match anything. This is\nuseful for writing catch-all routes.\n\nFor example, if we'd like to match a certain set of routes and return\n`404 Not Found` for everything else, we can do the following:\n\n```clojure\n(def my-routes [\"/\" [[\"index.html\" :index]\n                  [true         :not-found]]])\n```\n\nWe used vectors rather than maps to define the routes because the\norder of the definitions is significant (i.e. `true` will completely\nsubsume the other routes if we let it).\n\nNow let's try to match on that:\n\n```clojure\nuser\u003e (match-route my-routes \"/index.html\")\n{:handler :index}\nuser\u003e (match-route my-routes \"/other.html\")\n{:handler :not-found}\n```\n\nNote that `:not-found` doesn't have any special significance here--we\nstill need to provide a hander function that implements the desired\n`404` behavior.\n\n## Route definitions\n\nA route is formed as a pair: [ *\u0026lt;pattern\u0026gt;* *\u0026lt;matched\u0026gt;* ]\n\nThe left-hand-side of a pair is the pattern. It can match a path, either\nfully or partially. The simplest pattern is a string, but other types of\npatterns are also possible, including segmented paths, regular\nexpressions, records, in various combinations.\n\nThe right-hand-side indicates the result of the match (in the case that\nthe pattern is matched fully) or a route sub-structure that attempts to\nmatch on the remainder of the path (in the case that the pattern is\nmatched partially). The route structure is a recursive structure.\n\nThis [BNF](http://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form)\ngrammar formally defines the basic route structure, although it is\npossible extend these definitions by adding types that satisfy the\nprotocols used in bidi (more on this later).\n\n```\nRouteStructure := RoutePair\n\nRoutePair ::= [ Pattern Matched ]\n\nPattern ::= Path | [ PatternSegment+ ] | MethodGuard | GeneralGuard | true | false\n\nMethodGuard ::= :get :post :put :delete :head :options\n\nGeneralGuard ::= [ GuardKey GuardValue ]* (a map)\n\nGuardKey ::= Keyword\n\nGuardValue ::= Value | Set | Function\n\nPath ::= String\n\nPatternSegment ::= String | Regex | Keyword | [ (String | Regex) Keyword ]\n\nMatched ::= Function | Symbol | Keyword | [ RoutePair+ ] { RoutePair+ }\n```\n\nIn case of confusion, refer to bidi examples found in this README and in\nthe test suite.\n\nA [schema](https://github.com/Prismatic/schema) is available as `bidi.schema/RoutePair`. You can use this to check or validate a bidi route structure in your code.\n\n```clojure\n(require '[schema.core :as s] bidi.schema)\n\n(def route [\"/index.html\" :index])\n\n;; Check that the route is properly structured - returns nil if valid;\n;; otherwise, returns a value with 'bad' parts of the route.\n(s/check bidi.schema/RoutePair route)\n\n;; Throw an exception if the route is badly structured\n(s/validate bidi.schema/RoutePair route)\n```\n\n## Virtual Hosts\n\nIf you are serving multiple virtual hosts with the same server, you may want to create a super-structure that allows routing across virtual host boundaries.\n\nHere's a virtual-host structure:\n\n```clojure\n[\"https://example.org:8443\"\n [\"/index.html\" :index]\n [\"/login\" :login]\n [\"/posts\" […]]\n```\n\nIt's just like the vector-of-vectors syntax we've seen before in bidi, but this time the first element is a virtual-host declaration. This is usually a string but can also be a `java.net.URI` or `java.net.URL`, or a map like `{:scheme :https :host \"example.org:8443\"}`.\n\nA virtual-hosts super-structure is created with the `bidi.vhosts/vhosts.model` variadic function, each argument is a virtual-host structure.\n\n```clojure\n(require '[bidi.vhosts :refer [vhosts-model]])\n\n(def my-vhosts-model\n  (vhosts-model [\"https://example.org:8443\"\n                 [\"/index.html\" :index]\n                 [\"/login\" :login]]\n\n                [\"https://blog.example.org\"\n                 [\"/posts.html\" […]]]))\n\n```\n\n### uri-info\n\nWhen using virtual hosts, use the `bidi.vhosts/uri-info` to generate a map of URIs.\n\nFor example:\n\n```\n(uri-info my-vhosts-model :index {:query-params {\"q\" \"juxt\"}})\n```\n\nwould return\n\n```\n{:uri \"https://example.org:8443/index.html?q=juxt\"\n :path \"/index.html\"\n :host \"example.org:8443\"\n :scheme :https\n :href \"https://example.org:8443/index.html?q=juxt\"}\n```\n\nA partially applied uri-info function is available in bidi's matching context and returns a map of the following elements. This partial applies the vhosts-model which can help with dependency cycles in your code (where your bidi router requires knowledge of resources, which have views that require knowledge of the bidi router's routes).\n\nWhen called via bidi's match-context, the `:href` entry in the result may not contain the scheme, host and port, if these are redundant, whereas the `:uri` entry always contains an absolute URI. If you are creating HTML content for a browser, `:href` is safe to use. If, for example, you are creating an API returning a JSON-formatted response body, prefer `:uri`.\n\n### Synonymous virtual-hosts\n\nThe virtual-host declaration can itself be a vector, if you need to match multiple possibilities. Here's another example, which matches two hosts:\n\n```clojure\n[[\"https://example.org:8443\" \"http://example.org:8000\"]\n [\"/index.html\" :index]\n [\"/login\" :login]]\n```\n\nThe rules for `uri-info` are that the first virtual-host in the vector is used. When the request is known to bidi (i.e. in the partially applied uri-info function in the match-context) the algorithm chooses the first virtual host that matches the request URI's scheme.\n\n### Wildcards\n\nAn virtual host can be specified as a wildcard `:*`, which means it matches any scheme/host. Calls to `uri-info` will assume the scheme/host are that of the incoming request.\n\n```clojure\n[:*\n [\"/index.html\" :index]\n [\"/login\" :login]]\n```\n\nWildcards can be mixed with other vhost forms.\n\n## Composability\n\nAs they are simply nested data structures (strings, vectors, maps),\nroute structures are highly composable. They are consistent and easy to\ngenerate. A future version of bidi may contain macros to reduce the\nnumber of brackets needed to create route structures by hand.\n\n## Extensibility\n\nThe implementation is based on Clojure protocols which allows the route\nsyntax to be extended outside of this library.\n\nBuilt-in records are available but you can also create your own. Below\nis a description of the built-in ones and should give you an idea what\nis possible. If you add your own types, please consider contributing\nthem to the project. Make sure you test that your types in both\ndirections (for URI matching and formation).\n\n### Redirect\n\nThe `Redirect` record is included which satisfies the `Matched` protocol.\n\nConsider the following route definition.\n\n```clojure\n(defn my-handler [req] {:status 200 :body \"Hello World!\"})\n\n[\"/articles\" {\"/new\" my-handler\n              \"/old\" (-\u003eRedirect 307 my-handler)}]\n```\n\nAny requests to `/articles/old` yield\n[*307 Temporary Redirect*](http://en.wikipedia.org/wiki/HTTP_307#3xx_Redirection)\nresponses with a *Location* header of `/articles/new`. This is a robust\nway of forming redirects in your code, since it guarantees that the\n*Location URI* matches an existing handler, both reducing the chance of\nbroken links and encouraging the practise of retaining old URIs (linking\nto new ones) after refactoring. You can also use it for the common\npractice of adding a *welcome page* suffix, for example, adding\n`index.html` to a URI ending in `/`.\n\n### Resources and ResourcesMaybe\n\nThe `Resources` and `ResourcesMaybe` record can be used on the\nright-hand side of a route. It serves resources from the\nclasspath. After the pattern is matched, the remaining part of the path\nis added to the given prefix.\n\n```clojure\n[\"/resources\" (-\u003eResourcesMaybe {:prefix \"public/\"})\n```\n\nThere is an important difference between `Resources` and `ResourcesMaybe`. `Resources` will return a 404 response if the resource cannot be found, while `ResourcesMaybe` will return nil, allowing subsequent routes to be tried.\n\n### Files\n\nSimilar to `Resources`, `Files` will serve files from a file-system.\n\n```clojure\n[\"pics/\" (-\u003eFiles {:dir \"/tmp/pics\"})]\n```\n\n### WrapMiddleware\n\nYou can wrap the target handler in Ring middleware as usual. But\nsometimes you need to specify that the handlers from certain patterns\nare wrapped in particular middleware.\n\nFor example :-\n\n```clojure\n(match-route [\"/index.html\" (-\u003eWrapMiddleware handler wrap-params)]\n             \"/index.html\")\n```\n\nUse this with caution. If you are using this _you are probably doing it wrong_.\n\nBidi separates URI routing from request handling. Ring middleware is\nsomething that should apply to handlers, not routes. If you have a set\nof middleware common to a group of handlers, you should apply the\nmiddleware to each handler in turn, rather than use\n`-\u003eWrapMiddleware`. Better to map a middleware applying function over\nyour handlers rather than use this feature.\n\n### Alternates\n\nSometimes you want to specify a list of potential candidate patterns,\nwhich each match the handler. The first in the list is considered the\ncanonical pattern for the purposes of URI formation.\n\n```clojure\n[#{\"/index.html\" \"/index\"} :index]\n```\n\nAny pattern can be used in the list. This allows quite sophisticated\nmatching. For example, if you want to match on requests that are either\nHEAD or GET but not anything else.\n\n```clojure\n[#{:head :get} :index]\n```\n\nOr match if the server name is `juxt.pro` or `localhost`.\n\n```clojure\n[#{{:server-name \"juxt.pro\"}{:server-name \"localhost\"}}\n {\"/index.html\" :index}]\n```\n\n### Tagged Match\n\nSometimes you need to apply a tag to a route, so you can use the tag\n(rather than the handler) in a `path-for` function. This is very\nconvenient when forming routes, because you don't need to have a\nreference to the handler itself.\n\nYou can use the `tag` function to construct these records.\n\n```clojure\n(tag my-handler :my-tag)\n```\n\nIt's common to use the single threaded macro, so wrapping handlers in\ntags is just like wrapping them in Ring middleware. For example :-\n\n```clojure\n[\"/\" [[\"foo\" (-\u003e foo-handler (tag :foo)]\n      [[\"bar/\" :id] (-\u003e bar-handler (tag :bar)]]]\n```\n\nPaths can now be created like this :-\n\n```clojure\n(path-for my-routes :foo)\n(path-for my-routes :bar :id \"123\")\n\n```\n\n### Route sequences\n\nIt's possible to extract all possible routes from a route structure with `route-seq`.\n\nCall `route-seq` on a route structure returns a sequence of all the possible routes contained in the route structure. This is useful to generating a site map. Each route is a map containing a path and a handler entry.\n\nIf you use keywords to extract route parameters, they will be contained in the path. If you wish to control the expansion, use a custom record that satisfies both `bidi/Pattern` and `bidi/Matches`.\n\n\n## Contributing\n\nWe welcome pull requests. If possible, please run the tests and make\nsure they pass before you submit one.\n\n```\n$ lein test\n\nlein test bidi.bidi-test\n\nlein test bidi.perf-test\nTime for 1000 matches using Compojure routes\n\"Elapsed time: 17.645077 msecs\"\nTime for 1000 matches using uncompiled bidi routes\n\"Elapsed time: 66.449164 msecs\"\nTime for 1000 matches using compiled bidi routes\n\"Elapsed time: 21.269446 msecs\"\n\nRan 9 tests containing 47 assertions.\n0 failures, 0 errors.\n```\n\nA big thank you to everyone involved in bidi so far, including\n\n* Alexander Kiel\n* Bobby Calderwood\n* Cameron Desautels\n* Chris Price\n* David Thomas Hume\n* Dene Simpson\n* Dominic Monroe\n* Elben Shira\n* James Henderson\n* Jeff Rose\n* John Cowie\n* Julian Birch\n* Malcolm Sparks\n* Martin Trojer\n* Matt Mitchell\n* Michael Sappler\n* Nate Smith\n* Neale Swinnerton\n* Nicolas Ha\n* Oliver Hine\n* Philipp Meier\n* Rob Mather\n* Sebastian Bensusan\n* Thomas Crowley\n* Thomas Mulvaney\n* Tom Crayford\n* Andrew Phillips\n\n## Copyright \u0026 License\n\nThe MIT License (MIT)\n\nCopyright © 2014-2015 JUXT LTD.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuxt%2Fbidi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuxt%2Fbidi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuxt%2Fbidi/lists"}