Treo (Irish for route) is a simple Ring routing library for Clojure. What makes it different from the many, many, many other routing libraries out there? A combination of the following:
- Has a simple set of functionality for generating routes from namespaces
- Regular expression route matching
- Interleaved middleware that is run AFTER route matching but BEFORE handler triggered
- Works within the Ring system, does not try to replace it.
- No unnecessary DSL or macros
- Good test coverage
Import into your project dependencies:
[treo "0.2.1"]
Create a namespace with specifically named functions:
(ns com.example.test.api.v1.note
(:require [ring.util.response :as ring])
(:import [java.net HttpURLConnection]))
(defn show
"Returns the list of notes. Corresponds to a GET"
[request]
(ring/status (ring/response [{"key" 1 "value" "note1"}])
HttpURLConnection/HTTP_OK))
Now generate your Ring handler using the namespace symbol. Here's an example
that can be run using Leiningen's main
hook:
(ns com.example.test.api.bootstrap
(:require [treo.dispatcher :as treo]
[ring.middleware.format :as ring-format]
[ring.adapter.jetty :as jetty]))
(defn -main
(let [handler-generator (treo/namespace-route-generator "/api/v1")]
(jetty/run-jetty (handler-generator ["note"]
'com.example.test.api.v1.note
ring-format/wrap-restful-format)
{:port 3000})))
Once run, this should now respond to http://localhost:3000/api/v1/note
The core function is treo.dispatcher/namespace-route-generator
.
It creates a function which takes two required arguments:
route-parts
- a sequence of strings representing the REST resource and any argumentshandler-ns
- a namespace symbol defining the implemented HTTP methods
It also takes a variable number of optional Ring middleware functions, which are applied, in the order given, before the underlying namespace is triggered but after the point where the route is matched. This is extremely useful for authentication checks etc.
The output of this function is a standard Ring handler and treated as such.
For convenience, the treo.dispatcher/routes
function is
available and supports combining Ring handlers together into one handler.
namespace-route-generator
takes one optional argument prefix
, which can be
used to set the prefix for all endpoints generated by the response. This is
useful if you want to set a fixed version for all your endpoints.
Each route is made up of a sequence of strings. These strings are part of the composed URL and each URL is a regular expression for more flexible matching. For example, with a prefix of "/api/v1", here are the resulting regexes for the given arguments:
- [""] -> "^/api/v1/?$"
- ["note"] -> "^/api/v1/note/?$"
- ["note" "\s+"] -> "^/api/v1/note/\s+/?$"
- ["note" "(\s+)" "(?\d+)"] -> "^/api/v1/note/(\s)/(?\d+)/?$"
- "note" -> "^/api/v1/note/?$"
If a string is supplied instead of a sequence of strings, it treats it as a sequence of length 1.
Any regex groups included in the URL are injected into the Ring request map. For example, with the regex "^/api/v1/note/(\s)/(?\d+)/?$" and the request URL /api/v1/note/what/342, the Ring request will now include the following data:
{:treo/route {:groups ["what" "342"]
:named-groups {:id "342"}}}
Your target namespaces can define one of a set of fixed functions, each
corresponding to a specific HTTP method. Any functions not defined will
result in a 405 Not Allowed response for that HTTP method. The precise set of
function names is defined at treo.dispatcher/ns-method-fns
.
The defaults (below) can be overridden by rebinding this variable.
HTTP Method | Function Name |
---|---|
GET | show |
POST | create |
PUT | change |
PATCH | change |
DELETE | delete |
If it's necessary to access the specific request method function from the
namespace handler, method-handler-fn
enables this. This is useful if you
wish to access metadata on a function from a middleware, for example.
For added flexibility, you can use the
treo.dispatcher/create-request-method-handler
function directly. This is
what the namespace introspection wraps - a function that returns a Ring
handler which invokes one of a set of functions based on the incoming
request method.
A simple example run from the REPL:
user> (require '[ring.mock.request :as mock])
user> (require '[treo.dispatcher :as dispatcher])
user> (def handler (dispatcher/create-request-method-handler {:get (fn [{:keys [uri]}] uri)}))
user> (handler (mock/request :get "/api/v1/test"))
/api/v1/test
user> (handler (mock/request :post "/api/v1/test"))
{:status 405, :headers {}, :body nil}
Copyright © 2019 Shane Breatnach
Distributed under the Eclipse Public License version 2.0.