{"id":13801293,"url":"https://github.com/DomKM/silk","last_synced_at":"2025-05-13T11:30:59.331Z","repository":{"id":18179552,"uuid":"21293746","full_name":"domkm/silk","owner":"domkm","description":"Routing for Clojure \u0026 ClojureScript","archived":false,"fork":false,"pushed_at":"2022-01-04T23:48:51.000Z","size":74,"stargazers_count":224,"open_issues_count":5,"forks_count":13,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-05-06T06:17:58.212Z","etag":null,"topics":["bidirectional","clojure","clojurescript","isomorphic","routing","silk","silk-routes"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/domkm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-06-28T01:37:36.000Z","updated_at":"2025-04-17T05:00:15.000Z","dependencies_parsed_at":"2022-08-25T15:03:16.754Z","dependency_job_id":null,"html_url":"https://github.com/domkm/silk","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domkm%2Fsilk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domkm%2Fsilk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domkm%2Fsilk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domkm%2Fsilk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/domkm","download_url":"https://codeload.github.com/domkm/silk/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253932789,"owners_count":21986448,"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":["bidirectional","clojure","clojurescript","isomorphic","routing","silk","silk-routes"],"created_at":"2024-08-04T00:01:21.287Z","updated_at":"2025-05-13T11:30:57.781Z","avatar_url":"https://github.com/domkm.png","language":"Clojure","funding_links":[],"categories":["Awesome ClojureScript"],"sub_categories":["Routing"],"readme":"# Silk  [![Current Version](https://clojars.org/com.domkm/silk/latest-version.svg)](https://clojars.org/com.domkm/silk)\n\n## Isomorphic Clojure[Script] Routing\n\n### Design Goals and Solutions\n\n#### Compatible with Clojure and ClojureScript\n\nThe core functionality of Silk in `domkm.silk` is fully compatible with both Clojure and ClojureScript and was designed to be so from the ground up.\nServer-specific code for use with Ring is in `domkm.silk.serve`.\nThere is currently no browser-specific code, though there probably will be in the future.\n\n#### Extensible Routes\n\nAn isomorphic routing library must be extensible for environment-specific constraints.\nFor example, on the server-side we may want a route to only respond to `GET` HTTP requests, while on the browser-side we want that same route to work even though there is no concept of incoming HTTP requests.\n\nThis is easy to do with Silk's core protocol, `domkm.silk/Pattern`, and is shown below.\n\n#### Bidirectional Routes\n\nRoutes should be bidirectional (commonly referred to as named). If you are serving resources, the likelihood is that you are also providing links to other resources.\nYour code and routes quickly become coupled and brittle without support for URL formation.\nUnidirectional routes (like Compojure) will, eventually, break; bidirectional routes will not.\n\nSilk routes are named and a specific route can be retrieved from a collection of routes by its name.\n\n#### Decoupled Matching and Handling\n\nThis architectural principle is especially important in isomorphic applications.\nMatching will probably be similar on the server-side and browser-side (except for extensions mentioned above) but handling will likely be completely different.\n\nNames are not restricted to strings or keywords, so a name could *be* a handler function.\nOr you could store handler functions elsewhere and look them up by route name.\nSilk does not impose restrictions.\n\n#### Data, Not Functions/Macros\n\nData structures can be generated, transformed, and analyzed at runtime and, perhaps more importantly, can be inspected and printed in meaningful ways.\nMacro DSLs and function composition make these things very difficult.\n\nSilk routes are data structures.\nThey are not nested functions and are not created with complex macros.\nThey are easy to create, manipulate, and inspect from Clojure and ClojureScript.\n\n#### Reasonably Convenient\n\nSilk has a few special rules to make route definition and use fairly terse.\nHowever, since routes are just data and are therefore easy to create and compose, users can easily define more convenient syntax for their specific use cases.\n\n#### Reasonably Performant\n\nThis goal is not yet met. Well, it may be, but there are no benchmarks yet.\n\n### Use\n\n##### Take a few minutes to learn Silk\n\nPatterns can be matched and unmatched with `domkm.silk/match` and `domkm.silk/unmatch` respectively.\n\nStrings only match themselves.\n\n```clojure\n(silk/match \"foo\" \"foo\")\n;=\u003e {}\n(silk/unmatch \"foo\" {})\n;=\u003e \"foo\"\n```\n\nKeywords are wildcards.\n\n```clojure\n(silk/match :foo \"bar\")\n;=\u003e {:foo \"bar\"}\n(silk/unmatch :foo {:foo \"bar\"})\n;=\u003e \"bar\"\n```\n\nThere are also built in patterns for common use cases.\n\n```clojure\n(silk/match (silk/int :answer) \"42\")\n;=\u003e {:answer 42}\n(silk/unmatch (silk/int :answer) {:answer 42})\n;=\u003e \"42\"\n\n(silk/match (silk/uuid :id) \"c11902f0-21b6-4645-a218-9fa40ef69333\")\n;=\u003e {:id #uuid \"c11902f0-21b6-4645-a218-9fa40ef69333\"}\n(silk/unmatch (silk/uuid :id) {:id #uuid \"c11902f0-21b6-4645-a218-9fa40ef69333\"})\n;=\u003e \"c11902f0-21b6-4645-a218-9fa40ef69333\"\n\n(silk/match (silk/cat \"user-\" (silk/int :id)) \"user-42\")\n;=\u003e {:id 42}\n(silk/unmatch (silk/cat \"user-\" (silk/int :id)) {:id 42})\n;=\u003e \"user-42\"\n\n(silk/match (silk/? :this {:this \"that\"}) \"foo\")\n;=\u003e {:this \"foo\"}\n(silk/match (silk/? :this {:this \"that\"}) nil)\n;=\u003e {:this \"that\"}\n```\n\nPatterns can be data structures.\n\n```clojure\n(silk/match [\"users\" (silk/int :id)] [\"users\" \"42\"])\n;=\u003e {:id 42}\n```\nA route can be created with a 2-tuple. The first element is a route name and the second element is something that can be turned into a URL pattern.\nIf the second element is a vector, the first and second elements are `assoc`iated into the third element under `:path` and `:query` keys respectively.\nIf the second element is a map, it is left unchanged.\n\n```clojure\n(silk/url-pattern [[\"users\" \"list\"] {\"filter\" :filter \"limit\" :limit} {:scheme \"https\"}])\n;=\u003e {:path [\"users\" \"list\"], :query {\"filter\" :filter, \"limit\" :limit}, :scheme \"https\"}\n\n(silk/url-pattern {:path [\"users\" \"list\"] :query {\"filter\" :filter \"limit\" :limit} :scheme \"https\"})\n;=\u003e {:path [\"users\" \"list\"], :query {\"filter\" :filter, \"limit\" :limit}, :scheme \"https\"}\n\n(silk/route [:route-name [[\"users\" \"list\"] {\"filter\" :filter \"limit\" :limit} {:scheme \"https\"}]])\n;=\u003e #\u003cRoute domkm.silk.Route@6ebe4324\u003e\n```\n\nRoutes are patterns.\n\n```clojure\n(silk/match (silk/route [:route-name [[\"users\" :username]]]) {:path [\"users\" \"domkm\"]})\n;=\u003e {:username \"domkm\", :domkm.silk/name :route-name, :domkm.silk/pattern {:path [\"users\" :username]}}\n(silk/unmatch (silk/route [:route-name [[\"users\" :username]]]) {:username \"domkm\"})\n;=\u003e #domkm.silk.URL{:scheme nil, :user nil, :host nil, :port nil, :path [\"users\" \"domkm\"], :query nil, :fragment nil}\n```\n\nNone of that is particularly useful unless you can match and unmatch route collections. Fortunately, a collection of routes is also a pattern.\n\n```clojure\n(def user-routes\n  (silk/routes [[:users-index [[\"users\"]]]\n                [:users-show [[\"users\" (silk/int :id)]]]]))\n\n(silk/match user-routes {:path [\"users\" \"42\"]})\n;=\u003e {:id 42, :domkm.silk/name :users-show, :domkm.silk/routes #\u003cRoutes domkm.silk.Routes@c6f8bbc\u003e, ...}\n(silk/unmatch user-routes {:id 42 :domkm.silk/name :users-show})\n;=\u003e #domkm.silk.URL{:scheme nil, :user nil, :host nil, :port nil, :path [\"users\" \"42\"], :query nil, :fragment nil}\n```\n\nIf you don't care about the match order, you can create routes with a map.\n\n```clojure\n(def page-routes\n  (silk/routes {:home-page [[]] ; match \"/\"\n                :other-page [[\"pages\" :title]]}))\n```\n\nRoutes can be constrained by request methods.\n\n```clojure\n(def api-routes\n  (silk/routes {:api-data [[\"api\"] {\"limit\" (silk/? (silk/int :limit) {:limit 100})\n                                    \"offset\" (silk/? (silk/int :offset) {:offset 0})} (serve/POST)]}))\n\n\n(silk/match api-routes {:path [\"api\"]})\n;=\u003e nil\n(silk/match api-routes {:path [\"api\"] :request-method :post})\n;=\u003e {:limit 100, :offset 0, :domkm.silk/name :api-data, ...}\n```\n\nRoutes can be combined.\n\n```clojure\n(def all-routes\n  (silk/routes [user-routes\n                page-routes\n                [:comments [[\"comments\"] {\"id\" (silk/uuid :id)}]]\n                api-routes]))\n```\n\n__All matching and unmatching is pure and bidirectional.__\n\nMatching and unmatching patterns is powerful and pure but quite verbose.\nSilk provides a higher-level interface via `domkm.silk/arrive` and `domkm.silk/depart`\n\n```clojure\n(silk/arrive all-routes \"/pages/about\")\n;=\u003e {:title \"about\", :domkm.silk/name :other-page, ...}\n```\n\nYou can also provide a handler function.\n\n```clojure\n(silk/arrive all-routes \"/pages/about\" :title)\n;=\u003e \"about\"\n```\n\nUnmatching is almost as easy.\n\n```clojure\n(silk/depart all-routes :other-page {:title \"about\"})\n;=\u003e \"/pages/about\"\n```\n\nAs with `domkm.silk/arrive`, you can provide a handler function.\n\n```clojure\n(silk/depart all-routes :other-page {:title \"about\"} clojure.string/upper-case)\n;=\u003e \"/PAGES/ABOUT\"\n```\n\n__Go forth and route!__\n\n### Status\n\nSilk is very much a work-in-progress. Everything is subject to change.\n\nIf you have suggestions, please do share them.\n\n### License\n\nCopyright \u0026copy; 2014 Dom Kiva-Meyer\n\nDistributed under the Eclipse Public License either version 1.0 or (at your option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDomKM%2Fsilk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDomKM%2Fsilk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDomKM%2Fsilk/lists"}