Defining Endpoints¶
The io.aviso.rook/gen-routes
function is provided with namespaces; the actual
endpoints are functions within those namespaces.
Rook identifies functions with the metadata :rook-route. Functions with this metadata will be added as endpoints.
Here’s an example namespace with just a single endpoint:
(ns org.example.widgets
(:require [ring.util.response :as r])
(defn list-widgets
{:rook-route [:get ""]}
[]
(r/response {:widgets []})
Endpoint functions take some number of parameters (more on this shortly) and return a Ring response map.
This is a placeholder: it returns fixed and largely empty Ring response. In a real application, the function could be provided with a database connection or some sort and could perform a query and return the results of that query.
Note
Pedestal route matching is very specific: the list-widgets
endpoint above is mapped to /widgets
,
and a client that requests /widgets/
will get a 404 NOT FOUND response.
Rook Routes¶
The route meta is either two or three terms, in a vector:
- The verb to use, such as :get, :post, :head, ... or :all.
- The path to match.
- (optional) A map of path variable constraints.
For example, we might define some additional endpoints to flesh out a CRUD API:
(defn get-widget
{:rook-route [:get "/:id" {:id #"\d{6}"}]}
[^:path-param id]
(r/not-found "WIDGET NOT FOUND"))
(defn update-widget
{:rook-route [:post "/:id" {:id #"\d{6}"}]}
[^:path-param id ^:request body]
(r/response "OK"))
The URI for the get-widget
endpoint is /widgets/:id
, where the :id
path parameter
must match the regular expression (six numeric digits).
This is because the namespace’s URI is /widgets
and the endpoint’s path is appended to that.
Likewise, the update-widget
endpoint is also mapped to the URI /widgets/:id
, but with the POST verb.
Because of how Pedestal routing is designed, a URI where the :id
variable doesn’t match the regular expression
will be ignored (it might match some other endpoint, but will most likely match nothing and result in a
404 NOT FOUND response).
This example also illustrates another major feature of Rook: endpoints can have any number of parameters, but use metadata on the parameters to identify what is to be supplied.
- :path-param is used to mark function parameters that should match against a path parameter.
- :request is used to mark function parameters that should match a key stored in the Ring request map.
Namespace Metadata¶
That repetition about the :id
path parameter constraint is bothersome.
Fortunately, Rook merges metadata from the namespace with metadata from the endpoint, allowing
such things to be just defined once:
(ns org.example.widgets
{:constraints {:id #"\d{6}"}}
(:require [ring.util.response :as r])
(defn list-widgets
{:rook-route [:get ""]}
[]
(r/response {:widgets []})
(defn get-widget
{:rook-route [:get "/:id"]}
[^:path-param id]
(r/not-found "WIDGET NOT FOUND"))
(defn update-widget
{:rook-route [:post "/:id"]}
[^:path-param id ^:request body]
(r/response "OK"))
Here, each endpoint inherits the :id
constraint from the namespace.