How can I list all the routes on a handler function? I'm looking for behavior similar to rails' rake routes. For example:
(defroutes foo-routes
(GET "/foo/:foo-id"
[foo-id]
"bar response")
(GET "/bar/:bar-id"
[bar-id]
"foo response"))
Is it then possible to extract a map from foo-bar-routes containing the following?
{:GET "/foo/:foo-id"
:GET "/bar/:bar-id"}
I don't think it is possible. defroutes is a macro that returns a ring handler. GET is a macro that returns a route. Route is again just a function that calls related handler only if method and path are matching. So in the end your foo-routes is just a clojure function that is composed of other functions defined by your routes and it doesn't maintain such map. If you need to get such map, maybe you can maintain it in your code yourself and generate routes out of this map.
I know this thread is quite old but I had the same question and could resolve it by myself, here's what I've got:
Assuming you defined your API this way:
(def my-api (compojure.api.api/api ...))
Then you can easily list the routes you defined that way:
(->> (.-get-routes my-api {})
(map (juxt second first)))
Related
Let's say I'm doing a web app that exposes API to fetch and mutate data about cats.
I'm implementing my db layer using Korma, so there will be something like that:
(ns kittens.db
(:require [korma.core :refer :all]))
(defn fetch-cats [db]
(select cats-table ...))
(defn fetch-cat-by-id [db id]
(select cats-table
...
(where {:id id})))
(defn create-cat [db data]
(insert cats-table
...))
...
...
After I'm implementing API routes as follows:
(ns kittens.routes
(:require [compojure.core :refer [defroutes GET POST ...]]
[kittens.db :as db]))
(defroutes cats-routes
(GET "/cats" [...] (db/fetch-cats ...))
(GET "/cats/:id" [...] (db/fetch-cat-by-id ...))
(POST "/new-cat" [...] (db/create-cat ...)))
In such implementation routes module talks directly to db module, which seems kinda inflexible to me. Should there be something in between of them?
In my opinion you do not need anything else, this is good.
Such a setup is already plenty flexible.
Seeing as all looks good, here is some general advice as things grow:
Avoid putting logic route bodies.
If the body of a route starts to grow, consider extracting it to a function.
Routes are less fun to test.
Colocate your routes and handler middleware unless you have so many routes they need to be split out into contexts.
Make sure your handler references the routes var #' not the routes directly so that reloading routes will work.
In the clojurescript re-frame todomvc application we find the following snippet in the todomvc.views namespace.
(defn todo-list
[visible-todos]
[:ul.todo-list
(for [todo #visible-todos]
^{:key (:id todo)} [todo-item todo])])
Although I have read the Clojure chapter on metadata I don't quite understand the purpose of:
^{:key
in the snippet above. Please explain.
The :key is what React needs when you have many items, so that they can be unique within the group. But the latest version of React does not need these keys. So if you use the latest versions of reframe / Reagent, just try without the :key metadata.
This metadata is equivalent to placing :key within the component. So for example what you have is equivalent to:
[todo-item {:key (:id todo)} todo]
Using the metadata approach is a convenience, which must in some cases be easier than the 'first key in props passed to the component' approach.
Here's more explanation.
^{:key (:id todo)} [todo-item todo] would be equivalent to (with-meta [todo-item todo] {:key (:id todo)}), see https://clojuredocs.org/clojure.core/with-meta
Reagent uses this to generate the corresponding react component with a key. Keys help React identify which items have changed, are added, or are removed. here is the explanation: https://reactjs.org/docs/lists-and-keys.html
I am getting the body and headers from the request like this:
(POST "/api/item" {body :body headers :headers} (create-item body headers))
The body is wrapped, so I get a keyword map and I can easily take values from the that:
(def app
(-> (handler/api app-routes)
(middleware/wrap-json-body {:keywords? true})
(middleware/wrap-json-response)))
As simple as:
(:item-name body)
How can I achieve the same with the headers, or just simply take a specific header value? Do I have to map the headers into a Clojure data structure first?
If I print headers I get something like this:
({host localhost:3000, user-agent Mozilla/5.0})
The headers are already in a Clojure data structure. If you want a better idea of the data types present, use prn instead of println, and you will see that it is a a hash-map with strings as keys.
(:foo x) is a shortcut for (get x :foo). For a hash-map with string keys you can get a value with eg. (get headers "host"). There is a function in clojure.walk, clojure.walk/keywordize-keys that will turn keys of a data structure into keywords, recursively through a nested structure. IMHO this is a bit silly, and one is better off using get and the string keys in most cases.
I am trying to write a middleware for converting all the string object ids in the request to ObjectId objects.
I am achieving this using the following:
(defn get-object-id
[id]
(when (and (string? id) (re-matches object-id-regex id))
(ObjectId. id)))
(defn maybe-obj->object-id [obj]
(or (get-object-id obj) obj))
(defn- convert-string->object-ids [obj]
(cwalk/postwalk
(partial pcommon/maybe-obj->object-id) obj))
(defn warp-params-string->objectid
"convert strings to object ids"
[handler]
(fn [request]
(handler (update-in request [:params] convert-string->object-ids))))
This is working for all the params coming for json, request params etc. But this is not applying to the route params, e.g. :fst for url "/:fst". I looked at the GET macro and the route params are being injected somewhere inside that macro. However since GET/POST etc are executed last, my middlewares do not have access to these. Any graceful way of achieving this.
Those /:foo/:bar-style parameters get bound as a result of pattern matching on URIs, with the patterns specified in the individual routes' definitions. Outer layers don't even know what the patterns look like. So, not really possible to lift processing of these to middleware.
Instead, you could write a macro, say with-preprocessed-params, to wrap your route handlers' bodies in. If it ends up being useful in many handlers, you can additionally provide your own versions of GET & Co., delegating to Compojure's macros with the body wrapped in your param-processing macro.
That's not really a good solution if you were hoping to use the results of this preprocessing in further layers of middleware. In that case, assuming you're happy to leave matching actual URI path segments to the core handler layer, you can perform your preprocessing of other parameter types in a piece of middleware, then use your GET & Co. variants to preprocess the route parameters only.
I'm developing a web application with Clojure, currently with Ring, Moustache, Sandbar and Hiccup. I have a resource named job, and a route to show a particular step in a multi-step form for a particular job defined like this (other routes omitted for simplicity):
(def web-app
(moustache/app
;; matches things like "/job/32/foo/bar"
:get [["job" id & step]
(fn [req] (web.controllers.job/show-job id step))]))
In the view my controller renders, there are links to other steps within the same job. At the moment, these urls are constructed by hand, e.g. (str "/job/" id step). I don't like that hard-coded "/job/" part of the url, because it repeats what I defined in the moustache route; if I change the route I need to change my controller, which is a tighter coupling than I care for.
I know that Rails' routing system has methods to generate urls from parameters, and I wish I had similar functionality, i.e. I wish I had a function url-for that I could call like this:
(url-for :job 32 "foo" "bar")
; => "/job/32/foo/bar"
Is there a Clojure web framework that makes this easy? If not, what are your thoughts on how this could be implemented?
Noir provides something similar. It's even called url-for.
The example function you have mentioned could be implemented as below. But I am not sure if this is exactly what you are looking for.
(defn url-for [& rest]
(reduce
#(str %1 "/" %2) "" (map #(if (keyword? %1) (name %1) (str %1)) rest)))