Skip to content
Tommi Reiman edited this page Feb 2, 2017 · 12 revisions

Compojure-api uses Compojure for routing.

The big difference is, that all compojure-api route functions & macros return compojure.api.routes/Route-records which can both act as normal ring handlers (e.g. can be called with request to produce an response) and they satisfy the compojure.api.routes/Routing protocol for collecting the route information.

(def inc-route
  (GET "/inc" []
    :query-params [x :- s/Int]
    :return {:result s/Int}
    (ok {:result (inc x)})))

inc-route    
; #Route{:path "/inc"
;        :method :get
;        :info {:parameters {:query {Keyword Any, :x Int}}
;               :responses {200 {:schema {:result Int}, :description ""}}}}

(inc-route {:request-method :get, :uri "/inc" :query-params {}})
; CompilerException clojure.lang.ExceptionInfo: Request validation failed: {:x missing-required-key}

(inc-route {:request-method :get, :uri "/inc" :query-params {:x 1}})
; {:status 200, :headers {}, :body {:result 2}, :compojure.api.meta/serializable? true}

At api creation time, the route-tree is walked and the reverse-route tree is generated - to be used both for swagger-docs & for bi-directional routing. To resolve the route-tree manually, you can call get-routes.

(compojure.api.routes/get-routes
  (context "/api" []
    inc-route
    (POST "/mortem" []
      :summary "oh, noes"
      (ok {:rest "in piece"}))))
; [["/api/inc" :get {:parameters {:query {Keyword Any, :x Int}}, :responses {200 {:schema {:result Int}, :description ""}}}]
;  ["/api/mortem" :post {:summary "oh, well"}]]

Bi-directional routing

Routes can have a (preferably qualified) keyword :name. When an api is created, a reverse route tree is created from it's subroutes and the routing table is injected into the request. One can get a full string path to a route by caling compojure.api.routes/path-for* with the request, route name and optionally path-parameters as a map. There is also a helper macro compojure.api.routes/path-for which reads the request from lexical scope bindings. It is only available in the http endpoint macros (GET, POST etc.). Path parameters are written into Strings using standard JSON decoding.

(require '[compojure.api.routes :as routes])

(def app
  (api

    ;; a named route
    (GET "/pong/:id" []
      :path-params [id :- s/Int]
      :name ::pong
      (ok {:id id}))

    ;; reverse routing via a macro
    (GET "/ping1" []
      (temporary-redirect (routes/path-for ::pong {:id 1})))

    ;; reverse routing via function
    (GET "/ping2" request
      (temporary-redirect (routes/path-for* ::pong request {:id 2})))))

(app {:request-method :get, :uri "/ping1"})
; {:status 307, :headers {"Location" "/pong/1"}, :body ""}

(app {:request-method :get, :uri "/ping2"})
; {:status 307, :headers {"Location" "/pong/2"}, :body ""}

Undocumented routes

If the api routes contain routes, which do not satisfy the Routing protocol (e.g. normal ring/compojure functions), an callback-function [:api :invalid-routes-fn] is called. By default, an warning is logged. At runtime, everything works as expected.

Mixing compojure-api with vanilla ring handlers

By default, a WARN is logged.

(api
  (GET "/ping" []
    (ok {:message "I satisfy the Routing protocol!"}))
  (compojure.core/GET "/pong" []
    (ok {:message "I dont."})))
; WARN Not all child routes satisfy compojure.api.routing/Routing. {:path nil, :method nil}, invalid child routes: [#function[compojure.core/if-method/fn--19598]]

Marking routes undocumented will stop the route collector to entering those routes (still works at runtime thou).

(api
  (GET "/ping" []
    (ok {:message "I satisfy the Routing protocol!"}))
  (undocumented
    (compojure.core/GET "/pong" []
      (ok {:message "I dont."}))))

You can also mark compojure-api routes as undocumented - here, the whole api is undocumented

(api
  (undocumented
    (GET "/ping" []
      (ok {:message "I satisfy the Routing protocol!"}))
    (compojure.core/GET "/pong" []
      (ok {:message "I dont."}))))

One can also change how the api handles non-compojure-api routes. Here, we break at compile-time:

(api
  {:api {:invalid-routes-fn compojure.api.routes/fail-on-invalid-child-routes}}
  (GET "/ping" []
    (ok {:message "I satisfy the Routing protocol!"}))
  (compojure.core/GET "/pong" []
    (ok {:message "I dont."})))
; CompilerException clojure.lang.ExceptionInfo: Not all child routes satisfy compojure.api.routing/Routing. {:path nil, :method nil, :invalid [#function[compojure.core/if-method/fn--19598]]}

... or just ignore the bad routes

(api
  {:api {:invalid-routes-fn nil}}
  (GET "/ping" []
    (ok {:message "I satisfy the Routing protocol!"}))
  (compojure.core/GET "/pong" []
    (ok {:message "I dont."})))

Not found (at runtime)?

To set up a "didn't match anything" handler within an api, just do like you would do with ring/compojure.

(api
  inc-route
  (undocumented
    (compojure.route/not-found (ok {:not "found"}))))