All Projects → domkm → Silk

domkm / Silk

Routing for Clojure & ClojureScript

Programming Languages

clojure
4091 projects
clojurescript
191 projects

Projects that are alternatives of or similar to Silk

Awesome Nextjs
📔 📚 A curated list of awesome resources : books, videos, articles about using Next.js (A minimalistic framework for universal server-rendered React applications)
Stars: ✭ 6,812 (+3039.17%)
Mutual labels:  isomorphic, routing
Buttermilk
beautifully simple isomorphic routing for React projects
Stars: ✭ 108 (-50.23%)
Mutual labels:  isomorphic, routing
Nextjs Dynamic Routes
[Deprecated] Super simple way to create dynamic routes with Next.js
Stars: ✭ 145 (-33.18%)
Mutual labels:  isomorphic, routing
Ataraxy
A data-driven Ring routing and destructuring library
Stars: ✭ 187 (-13.82%)
Mutual labels:  routing
Netflux
JavaScript client and server side transport API based on WebRTC & WebSocket
Stars: ✭ 188 (-13.36%)
Mutual labels:  isomorphic
Ui Router
The de-facto solution to flexible routing with nested views in AngularJS
Stars: ✭ 13,738 (+6230.88%)
Mutual labels:  routing
Hapi Universal Redux
DEPRECATED: Create an universal React and Redux app in less than 5 minutes!
Stars: ✭ 215 (-0.92%)
Mutual labels:  isomorphic
Fault tolerant router
A daemon, running in background on a Linux router or firewall, monitoring the state of multiple internet uplinks/providers and changing the routing accordingly. LAN/DMZ internet traffic is load balanced between the uplinks.
Stars: ✭ 182 (-16.13%)
Mutual labels:  routing
Pushy
Clojurescript library for quick and easy HTML5 pushState
Stars: ✭ 212 (-2.3%)
Mutual labels:  routing
Next Routes
Universal dynamic routes for Next.js
Stars: ✭ 2,354 (+984.79%)
Mutual labels:  routing
Route
Simple isomorphic router
Stars: ✭ 199 (-8.29%)
Mutual labels:  isomorphic
Peering Manager
Peering sessions management tool
Stars: ✭ 189 (-12.9%)
Mutual labels:  routing
React Isomorphic Starterkit
Create an isomorphic React app in less than 5 minutes
Stars: ✭ 2,326 (+971.89%)
Mutual labels:  isomorphic
Elefant
Elefant, the refreshingly simple PHP CMS and web framework.
Stars: ✭ 188 (-13.36%)
Mutual labels:  routing
Falco
A functional-first toolkit for building brilliant ASP.NET Core applications using F#.
Stars: ✭ 214 (-1.38%)
Mutual labels:  routing
Openrouteservice App
🚙 The open source route planner app with plenty of features.
Stars: ✭ 187 (-13.82%)
Mutual labels:  routing
React Imvc
An Isomorphic MVC Framework
Stars: ✭ 211 (-2.76%)
Mutual labels:  isomorphic
Osrm Frontend
Modular rewrite of the OSRM frontend using LRM
Stars: ✭ 197 (-9.22%)
Mutual labels:  routing
Atlasr
Atlasr is a truly open-source and free map browser.
Stars: ✭ 196 (-9.68%)
Mutual labels:  routing
Lime Packages
OpenWrt packages composing LibreMesh meta-firmware for wireless mesh networking
Stars: ✭ 204 (-5.99%)
Mutual labels:  routing

Silk Current Version

Isomorphic Clojure[Script] Routing

Design Goals and Solutions

Compatible with Clojure and ClojureScript

The 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. Server-specific code for use with Ring is in domkm.silk.serve. There is currently no browser-specific code, though there probably will be in the future.

Extensible Routes

An isomorphic routing library must be extensible for environment-specific constraints. For 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.

This is easy to do with Silk's core protocol, domkm.silk/Pattern, and is shown below.

Bidirectional Routes

Routes 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. Your code and routes quickly become coupled and brittle without support for URL formation. Unidirectional routes (like Compojure) will, eventually, break; bidirectional routes will not.

Silk routes are named and a specific route can be retrieved from a collection of routes by its name.

Decoupled Matching and Handling

This architectural principle is especially important in isomorphic applications. Matching will probably be similar on the server-side and browser-side (except for extensions mentioned above) but handling will likely be completely different.

Names are not restricted to strings or keywords, so a name could be a handler function. Or you could store handler functions elsewhere and look them up by route name. Silk does not impose restrictions.

Data, Not Functions/Macros

Data structures can be generated, transformed, and analyzed at runtime and, perhaps more importantly, can be inspected and printed in meaningful ways. Macro DSLs and function composition make these things very difficult.

Silk routes are data structures. They are not nested functions and are not created with complex macros. They are easy to create, manipulate, and inspect from Clojure and ClojureScript.

Reasonably Convenient

Silk has a few special rules to make route definition and use fairly terse. However, 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.

Reasonably Performant

This goal is not yet met. Well, it may be, but there are no benchmarks yet.

Use

Take a few minutes to learn Silk

Patterns can be matched and unmatched with domkm.silk/match and domkm.silk/unmatch respectively.

Strings only match themselves.

(silk/match "foo" "foo")
;=> {}
(silk/unmatch "foo" {})
;=> "foo"

Keywords are wildcards.

(silk/match :foo "bar")
;=> {:foo "bar"}
(silk/unmatch :foo {:foo "bar"})
;=> "bar"

There are also built in patterns for common use cases.

(silk/match (silk/int :answer) "42")
;=> {:answer 42}
(silk/unmatch (silk/int :answer) {:answer 42})
;=> "42"

(silk/match (silk/uuid :id) "c11902f0-21b6-4645-a218-9fa40ef69333")
;=> {:id #uuid "c11902f0-21b6-4645-a218-9fa40ef69333"}
(silk/unmatch (silk/uuid :id) {:id #uuid "c11902f0-21b6-4645-a218-9fa40ef69333"})
;=> "c11902f0-21b6-4645-a218-9fa40ef69333"

(silk/match (silk/cat "user-" (silk/int :id)) "user-42")
;=> {:id 42}
(silk/unmatch (silk/cat "user-" (silk/int :id)) {:id 42})
;=> "user-42"

(silk/match (silk/? :this {:this "that"}) "foo")
;=> {:this "foo"}
(silk/match (silk/? :this {:this "that"}) nil)
;=> {:this "that"}

Patterns can be data structures.

(silk/match ["users" (silk/int :id)] ["users" "42"])
;=> {:id 42}

A 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. If the second element is a vector, the first and second elements are associated into the third element under :path and :query keys respectively. If the second element is a map, it is left unchanged.

(silk/url-pattern [["users" "list"] {"filter" :filter "limit" :limit} {:scheme "https"}])
;=> {:path ["users" "list"], :query {"filter" :filter, "limit" :limit}, :scheme "https"}

(silk/url-pattern {:path ["users" "list"] :query {"filter" :filter "limit" :limit} :scheme "https"})
;=> {:path ["users" "list"], :query {"filter" :filter, "limit" :limit}, :scheme "https"}

(silk/route [:route-name [["users" "list"] {"filter" :filter "limit" :limit} {:scheme "https"}]])
;=> #<Route [email protected]>

Routes are patterns.

(silk/match (silk/route [:route-name [["users" :username]]]) {:path ["users" "domkm"]})
;=> {:username "domkm", :domkm.silk/name :route-name, :domkm.silk/pattern {:path ["users" :username]}}
(silk/unmatch (silk/route [:route-name [["users" :username]]]) {:username "domkm"})
;=> #domkm.silk.URL{:scheme nil, :user nil, :host nil, :port nil, :path ["users" "domkm"], :query nil, :fragment nil}

None of that is particularly useful unless you can match and unmatch route collections. Fortunately, a collection of routes is also a pattern.

(def user-routes
  (silk/routes [[:users-index [["users"]]]
                [:users-show [["users" (silk/int :id)]]]]))

(silk/match user-routes {:path ["users" "42"]})
;=> {:id 42, :domkm.silk/name :users-show, :domkm.silk/routes #<Routes [email protected]>, ...}
(silk/unmatch user-routes {:id 42 :domkm.silk/name :users-show})
;=> #domkm.silk.URL{:scheme nil, :user nil, :host nil, :port nil, :path ["users" "42"], :query nil, :fragment nil}

If you don't care about the match order, you can create routes with a map.

(def page-routes
  (silk/routes {:home-page [[]] ; match "/"
                :other-page [["pages" :title]]}))

Routes can be constrained by request methods.

(def api-routes
  (silk/routes {:api-data [["api"] {"limit" (silk/? (silk/int :limit) {:limit 100})
                                    "offset" (silk/? (silk/int :offset) {:offset 0})} (serve/POST)]}))


(silk/match api-routes {:path ["api"]})
;=> nil
(silk/match api-routes {:path ["api"] :request-method :post})
;=> {:limit 100, :offset 0, :domkm.silk/name :api-data, ...}

Routes can be combined.

(def all-routes
  (silk/routes [user-routes
                page-routes
                [:comments [["comments"] {"id" (silk/uuid :id)}]]
                api-routes]))

All matching and unmatching is pure and bidirectional.

Matching and unmatching patterns is powerful and pure but quite verbose. Silk provides a higher-level interface via domkm.silk/arrive and domkm.silk/depart

(silk/arrive all-routes "/pages/about")
;=> {:title "about", :domkm.silk/name :other-page, ...}

You can also provide a handler function.

(silk/arrive all-routes "/pages/about" :title)
;=> "about"

Unmatching is almost as easy.

(silk/depart all-routes :other-page {:title "about"})
;=> "/pages/about"

As with domkm.silk/arrive, you can provide a handler function.

(silk/depart all-routes :other-page {:title "about"} clojure.string/upper-case)
;=> "/PAGES/ABOUT"

Go forth and route!

Status

Silk is very much a work-in-progress. Everything is subject to change.

If you have suggestions, please do share them.

License

Copyright © 2014 Dom Kiva-Meyer

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].