All Projects → darkleaf → router

darkleaf / router

Licence: EPL-1.0 license
Bidirectional Ring router. REST oriented. Rails inspired.

Programming Languages

clojure
4091 projects

Projects that are alternatives of or similar to router

STCRouter
基于标准URL的iOS路由系统,可实现业务模块组件化,控制器之间零耦合,可实现黑白名单控制,可进行native降级到hybrid。
Stars: ✭ 19 (-75.64%)
Mutual labels:  router, routes, routing
Next Routes
Universal dynamic routes for Next.js
Stars: ✭ 2,354 (+2917.95%)
Mutual labels:  router, routes, routing
gatsby-plugin-dynamic-routes
Creating dynamic routes based on your environment and/or renaming existing routes
Stars: ✭ 14 (-82.05%)
Mutual labels:  router, routes, routing
routex.js
🔼 Alternative library to manage dynamic routes in Next.js
Stars: ✭ 38 (-51.28%)
Mutual labels:  router, routes, routing
Universal Router
A simple middleware-style router for isomorphic JavaScript web apps
Stars: ✭ 1,598 (+1948.72%)
Mutual labels:  router, routes, routing
Falco
A functional-first toolkit for building brilliant ASP.NET Core applications using F#.
Stars: ✭ 214 (+174.36%)
Mutual labels:  router, routing
Klein.php
A fast & flexible router
Stars: ✭ 2,622 (+3261.54%)
Mutual labels:  router, routing
Swiftuirouter
Routing in SwiftUI
Stars: ✭ 242 (+210.26%)
Mutual labels:  router, routing
Clevergo
👅 CleverGo is a lightweight, feature rich and high performance HTTP router for Go.
Stars: ✭ 246 (+215.38%)
Mutual labels:  router, routing
vue-error-page
[NO LONGER MAINTAINED] Provides a wrapper for router-view that allows you to show error pages without changing the URL.
Stars: ✭ 52 (-33.33%)
Mutual labels:  routes, routing
ertuo
Ertuo: quick routing for PHP
Stars: ✭ 29 (-62.82%)
Mutual labels:  routes, routing
Ui Router
The de-facto solution to flexible routing with nested views in AngularJS
Stars: ✭ 13,738 (+17512.82%)
Mutual labels:  router, routing
es6-router
🌐 Simple client side router built in ES6
Stars: ✭ 16 (-79.49%)
Mutual labels:  router, routing
journey
A conductor routing helper library
Stars: ✭ 35 (-55.13%)
Mutual labels:  router, routing
Router
Router implementation for fasthttp
Stars: ✭ 234 (+200%)
Mutual labels:  router, routing
Routerify
A lightweight, idiomatic, composable and modular router implementation with middleware support for the Rust HTTP library hyper.rs
Stars: ✭ 173 (+121.79%)
Mutual labels:  router, routing
Flow builder
Flutter Flows made easy! A Flutter package which simplifies flows with a flexible, declarative API.
Stars: ✭ 169 (+116.67%)
Mutual labels:  router, routing
RouteNow
RouteNow is a small fast library ⚡ that will help you in developing a SinglePage Application without any dependencies like jQuery, AngularJs, vue.js or any of those bulky frameworks.
Stars: ✭ 17 (-78.21%)
Mutual labels:  router, routing
Router
⚡️ A lightning fast HTTP router
Stars: ✭ 158 (+102.56%)
Mutual labels:  router, routing
Rayo.js
Micro framework for Node.js
Stars: ✭ 170 (+117.95%)
Mutual labels:  router, routing

Router

Build Status Clojars Project

Bidirectional RESTfull Ring router for clojure and clojurescript.

Comparation

library clj cljs dsl named routes mountable apps abstraction export format extensibility
compojure macros url
secretary macros url protocols
bidi data/functions url route description data protocols
darkleaf/router functions resource explain data protocols

Usage

(ns app.some-ns
  (:require [darkleaf.router :as r]
            [ring.util.response :refer [response]]))

(r/defcontroller controller
  (index [req]
    (let [request-for (::r/request-for req)]
      (response (str (request-for :index [:pages] {}))))))

(def routing (r/resources :pages :page controller))

(def handler (r/make-handler routing))
(def request-for (r/make-request-for routing))

(handler {:uri "/pages", :request-method :get}) ;; call index action from controller
(request-for :index [:pages] {}) ;; returns {:uri "/pages", :request-method :get}

Single routing namespace:

(ns app.routing
  (:require
   [darkleaf.router :as r]
   [app.controllers.main :as main]
   [app.controllers.session :as session]
   [app.controllers.account.invites :as account.invites]
   [app.controllers.users :as users]
   [app.controllers.users.statistics :as users.statistics]
   [app.controllers.users.pm-bonus :as users.pm-bonus]
   [app.controllers.projects :as projects]
   [app.controllers.projects.status :as projects.status]
   [app.controllers.projects.completion :as projects.completion]
   [app.controllers.tasks :as tasks]
   [app.controllers.tasks.status :as tasks.status]
   [app.controllers.tasks.comments :as tasks.comments]))
   
(def routes
  (r/group
    (r/resource :main main/controller :segment false)
    (r/resource :session session/controller)
    (r/section :account
      (r/resources :invites :invite account.invites/controller)) 
    (r/resources :users :user users/controller
      (r/resource :statistics users.statistics/controller)
      (r/resource :pm-bonus users.pm-bonus/controller))
    (r/resources :projects :project projects/controller
      (r/resource :status projects.status/controller)
      (r/resource :completion projects.completion/controller))
    (r/resources :tasks :task tasks/controller
      (r/resource :status tasks.status/controller)
      (r/resources :comments tasks.comments/controller))))

Multiple routing namespaces:

(ns app.routes.main
  (:require
   [darkleaf.router :as r]))

(r/defcontroller controller
  (show [req] ...))

(def routes (r/resource :main main-controller :segment false))

(ns app.routes
  (:require
   [darkleaf.router :as r]
   [app.routes.main :as main]
   [app.routes.session :as session]
   [app.routes.account :as account]
   [app.routes.users :as users]
   [app.routes.projects :as projects]
   [app.routes.tasks :as tasks]))

(def routes
  (r/group
    main/routes
    session/routes
    account/routes
    users/routes
    projects/routes
    tasks/routes))

Use cases

Rationale

Routing libraries work similarly on all programming languages: they only map uri with a handler using templates. For example compojure, sinatra, express.js, cowboy.

There are some downsides of this approach.

  1. No reverse or named routing. Url is set in templates as a string.
  2. Absence of structure. Libraries do not offer any ways of code structure, that results of chaos in url and unclean code.
  3. Inability to mount an external application. Inability to create html links related with mount point.
  4. Inability to serialize routing and use it in other external applications for request forming.

Most of these problems are solved in Ruby on Rails.

  1. If you know the action, controller name and parameters, you can get url, for example: edit_admin_post_path(@post.id).
  2. You can use rest resources to describe routing. Actions of controllers match to handlers. However, framework allows to add non-standart actions into controller, that makes your code unlean later.
  3. There is an engine support. For example, you can mount a forum engine into your project or decompose your application into several engines.
  4. There is an API for routes traversing, which uses rake routes command. The library js-routes brings url helpers in js.

Solution my library suggests.

  1. Knowing action, scope and params, we can get the request, which invokes the handler of this route: (request-for :edit [:admin :post] {:post "1"}).
  2. The main abstraction is the rest resource. Controller contains only standard actions. You can see resource composition how to deal with it.
  3. Ability to mount an external application. See example for details.
  4. The library interface is identical in clojure and clojurecript, that allows to share the code between server and client using .cljc files. You can also export routing description with cross-platform templates as a simple data structure. See example for details.

Resources

Action name Scope Params Http method Url Type Used for
index [:pages] {} Get /pages collection display a list of pages
show [:page] {:page 1} Get /pages/1 member display a specific page
new [:page] {} Get /pages/new collection display a form for creating new page
create [:page] {} Post /pages collection create a new page
edit [:page] {:page 1} Get /pages/1/edit member display a form for updating page
update [:page] {:page 1} Patch /pages/1 member update a specific page
put [:page] {:page 1} Put /pages/1 member upsert a specific page, may be combined with edit action
destroy [:page] {:page 1} Delete /pages/1 member delete a specific page
(ns app.some-ns
  (:require [darkleaf.router :as r]
            [ring.util.response :refer [response]]))

;; all items are optional
(r/defcontroller pages-controller
  (middleware [h]
    (fn [req] (h req)))
  (collection-middleware [h]
    (fn [req] (h req)))
  (member-middleware [h]
    (fn [req] (h req)))
  (index [req]
    (response "index resp"))
  (show [req]
    (response "show resp"))
  (new [req]
    (response "new resp"))
  (create [req]
    (response "create resp"))
  (edit [req]
    (response "edit resp"))
  (update [req]
    (response "update resp"))
  (put [req]
    (response "put resp"))
  (destroy [req]
    (response "destroy resp")))

;; :index [:pages] {} -> /pages
;; :show [:page] {:page 1} -> /pages/1
(r/resources :pages :page pages-controller)

;; :index [:people] {} -> /menschen
;; :show [:person] {:person 1} -> /menschen/1
(r/resources :people :person people-controller :segment "menschen")

;; :index [:people] {} -> /
;; :show [:person] {:person 1} -> /1
(r/resources :people :person people-controller :segment false)

;; :put [:page :star] {:page 1} -> PUT /pages/1/star
(r/resources :pages :page pages-controller
  (r/resource :star star-controller)

There are 3 types of middlewares:

  • middleware applied to all action handlers including nested.
  • collection-middleware applied only to index, new and create actions.
  • member-middleware applied to show, edit, update, put, delete and all nested handlers, look here for details.

Please see test for all examples.

Resource

Action name Scope Params Http method Url Used for
show [:star] {} Get /star display a specific star
new [:star] {} Get /star/new display a form for creating new star
create [:star] {} Post /star create a new star
edit [:star] {} Get /star/edit display a form for updating star
update [:star] {} Patch /star update a specific star
put [:star] {} Put /star upsert a specific star, may be combined with edit action
destroy [:star] {} Delete /star delete a specific star
;; all items are optional
(r/defcontroller star-controller
  ;; will be applied to nested routes too
  (middleware [h]
    (fn [req] (h req)))
  (show [req]
    (response "show resp"))
  (new [req]
    (response "new resp"))
  (create [req]
    (response "create resp"))
  (edit [req]
    (response "edit resp"))
  (update [req]
    (response "update resp"))
  (put [req]
    (response "put resp"))
  (destroy [req]
    (response "destroy resp")))

;; :show [:star] {} -> /star
(r/resource :star star-controller)

;; :show [:star] {} -> /estrella
(r/resource :star star-controller :segment "estrella")

;; :show [:star] {} -> /
(r/resource :star star-controller :segment false)

;; :index [:star :comments] {} -> /star/comments
(r/resource :star star-controller
  (r/resources :comments :comment comments-controller)

Please see test for exhaustive examples.

Group

This function combines multiple routes into one and applies optional middleware.

(r/defcontroller posts-controller
  (show [req] (response "show post resp")))
(r/defcontroller news-controller
  (show [req] (response "show news resp")))

;; :show [:post] {:post 1} -> /posts/1
;; :show [:news] {:news 1} -> /news/1
(r/group
  (r/resources :posts :post posts-controller)
  (r/resources :news :news news-controller)))

(r/group :middleware (fn [h] (fn [req] (h req)))
  (r/resources :posts :post posts-controller)
  (r/resources :news :news news-controller))

Please see test for exhaustive examples.

Section

;; :index [:admin :pages] {} -> /admin/pages
(r/section :admin
  (r/resources :pages :page pages-controller))

;; :index [:admin :pages] {} -> /private/pages
(r/section :admin, :segment "private"
  (r/resources :pages :page pages-controller))

(r/section :admin, :middleware (fn [h] (fn [req] (h req)))
  (r/resources :pages :page pages-controller))

Please see test for exhaustive examples.

Guard

;; :index [:locale :pages] {:locale "ru"} -> /ru/pages
;; :index [:locale :pages] {:locale "wrong"} -> not found
(r/guard :locale #{"ru" "en"}
  (r/resources :pages :page pages-controller))

(r/guard :locale #(= "en" %)
  (r/resources :pages :page pages-controller))

(r/guard :locale #{"ru" "en"} :middleware (fn [h] (fn [req] (h req)))
  (r/resources :pages :page pages-controller))

Please see test for exhaustive examples.

Mount

This function allows to mount isolated applications. request-for inside request map works regarding the mount point.

(def dashboard-app (r/resource :dashboard/main dashboard-controller :segment false))

;; show [:admin :dashboard/main] {} -> /admin/dashboard
(r/section :admin
  (r/mount dashboard-app :segment "dashboard"))

;; show [:admin :dashboard/main] {} -> /admin
(r/section :admin
  (r/mount dashboard-app :segment false))

;; show [:admin :dashboard/main] {} -> /admin
(r/section :admin
  (r/mount dashboard-app))

(r/section :admin
  (r/mount dashboard-app :segment "dashboard", :middleware (fn [h] (fn [req] (h req)))))

Please see test for exhaustive examples.

Pass

Passes any request in the current scope to a specified handler. Inner segments are available as (-> req ::r/params :segments). Action name is provided by request-method. It can be used for creating custom 404 page for current scope.

(defn handler (fn [req] (response "dashboard")))

;; :get [:admin :dashboard] {} -> /admin/dashboard
;; :post [:admin :dashboard] {:segments ["private" "users"]} -> POST /admin/dashboard/private/users
(r/section :admin
  (r/pass :dashboard handler))

;; :get [:admin :dashboard] {} -> /admin/monitoring
;; :post [:admin :dashboard] {:segments ["private" "users"]} -> POST /admin/monitoring/private/users
(r/section :admin
  (r/pass :dashboard handler :segment "monitoring"))

;; :get [:not-found] {} -> /
;; :post [:not-found] {:segments ["foo" "bar"]} -> POST /foo/bar
(r/pass :not-found handler :segment false)

Please see test for exhaustive examples.

Additional request keys

Handler adds keys for request map:

  • :darkleaf.router/action
  • :darkleaf.router/scope
  • :darkleaf.router/params
  • :darkleaf.router/request-for

Please see test for exhaustive examples.

Async

Asynchronous ring handlers support. It also can be used in macchiato-framework.

(r/defcontroller pages-controller
  (index [req resp raise]
    (future (resp response))))

(def pages (r/resources :pages :page pages-controller))
(def handler (r/make-handler pages))

(defn respond [val]) ;; from web server
(defn error [e]) ;; from web server

(handler {:request-method :get, :uri "/pages"} respond error)

Please see clj test and cljs test for exhaustive examples.

Explain

(r/defcontroller people-controller
  (index [req] (response "index"))
  (show [req] (response "show")))

(def routes (r/resources :people :person people-controller))
(pprint (r/explain routes))
[{:action :index,
  :scope [:people],
  :params-kmap {},
  :req {:uri "/people", :request-method :get}}
 {:action :show,
  :scope [:person],
  :params-kmap {:person "%3Aperson"},
  :req {:uri "/people{/%3Aperson}", :request-method :get}}]

It useful for:

  • inspection routing structure
  • mistakes detection
  • cross-platform routes serialization
  • documentation generation

URI Template uses for templating. Url encode is applied for ability to use keywords as a template variable because of the fact that clojure keywords contains forbidden symbols. Template parameters and :params mapping is set with :params-kmap.

HTML

HTML doesn’t support HTTP methods except GET и POST. You need to add the hidden field _method with put, patch or delete value to send PUT, PATCH or DELETE request. It is also necessary to wrap a handler with darkleaf.router.html.method-override/wrap-method-override. Use it with ring.middleware.params/wrap-params and ring.middleware.keyword-params/wrap-keyword-params.

Please see examples.

In future releases I'm going to add js code for arbitrary request sending using html links.

Questions

You can create github issue with your question.

TODO

  • docs
  • pre, assert

License

Copyright © 2016 Mikhail Kuzmin

Distributed under the Eclipse Public License version 1.0.

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].