Easy clojure routing
Add this to your deps.edn
coast-framework/router {:mvn/version "1.0.1"}
Require it like this
(ns your-project
(:require [router.core :as router]))
Use it like this
(defn index [request]
{:status 200
:body "index"
:headers {"Content-Type" "text/plain"}})
(defn post [request]
{:status 200
:body "post"
:headers {"Content-Type" "text/plain"}})
(defn account [request]
(let [{:keys [params]} request]
{:status 200
:body (str "account with id " (:id params))
:headers {"Content-Type" "text/plain"}}))
(def routes
(router/routes
[:get "/" index :index]
[:post "/posts" post :post]
[:get "/accounts/:id" account :account]))
The router supports per-route ring middleware
(defn mw1 [handler]
(fn [request]
(handler request)))
(defn mw2 [handler]
(fn [request]
(handler request)))
(def routes
(router/routes
[:get "/" index]
(router/middleware mw1 mw2
[:get "/posts" post]
[:get "/accounts/:id" account])))
In some cases it might make more sense to keep separate apps separate and combine them later instead of defining middleware functions across route vectors.
(defn authenticate [handler]
(fn [request]
(if (some? (:session request))
(handler request)
{:status 403 :body "No"})))
(def private-routes
(router/routes
[:get "/accounts" account/index]
[:get "/accounts/:id" account/show]
[:post "/accounts" account/create]
[:put "/accounts/:id" account/update]))
(def public-routes
(router/routes
[:get "/" home/index]))
(def private-app (-> (router/app private-routes)
(authenticate)))
(def public-app (router/app public-routes))
(def app (router/apps public-app private-app))
You can also prefix a set of routes like so
(def api-routes (router/routes
(router/prefix "/api"
[:get "/" routes.api/index :api/index] ; => GET /api
[:post "/" routes.api/post :api/index]))) ; => POST /api
The url-for
helper takes a route name and returns the string for that route
(router/url-for :index) ; => "/"
(router/url-for :hello) ; => "/hello"
(router/url-for :account {:id 1}) ;=> "/accounts/1"
You can also add query string parameters AND anchor tags
(router/url-for :account/show {:id 1 :? {:sort "asc"} :# "anchor"}) ; => "/accounts/1?sort=asc#anchor"
The action-for
helper takes a route name and optional args and returns a map for forms.
The put
, patch
and delete
in forms are all set under the _method
key.
(router/action-for :account/create) ; => {:method :post :action "/accounts"}
(router/action-for :account/update {:id 2}) ; => {:method :post :action "/accounts/2" :_method :put}
The redirect-to
helper takes a route name and optional args and
returns a ring response map with the "Location"
header set
(router/redirect-to :account/show {:id 1}) ; => {:status 302 :body "" :headers {"Location" "/accounts/1"}}
(router/redirect-to :index) ; => ; => {:status 302 :body "" :headers {"Location" "/"}}
A route is a vector that must have three items, and an optional name
(defn index [request])
[:get "/" index] ; this is a a valid route
The first item in a route is the request method, which can be one of the following:
:get
:post
:put
:patch
:delete
:head
:connect
:trace
The next item is the url with any parameters you define:
"/" ; => responds to localhost/ or localhost
"/accounts/:id" ; => responds to localhost/accounts/1 or localhost/accounts/whatever
"/accounts/:id/todos" ; => responds to localhost/accounts/1/todos
The last required item is the function that will respond to the request. This function takes one argument, the ring request map:
(defn home [request]
{:status 200
:body "home"
:headers {"content-type" "text/plain"}})
The final, optional item is a name for url-for
and action-for
helpers
(defn hello [request]
{:status 200
:body (str "hello " (get-in request [:params :name]))
:headers {"content-type" "text/plain"}})
[:get ; request-method
"/hello/:name" ; url
hello ; handler function
:hello-route] ; optional route name
(router/url-for :hello-route {:name "sean"}) ; => "/hello/sean"
cd router && make test
MIT
Any and all issues or pull requests welcome!