Skip to content
Tommi Reiman edited this page Sep 6, 2016 · 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 ""}}},
;        :childs nil,
;        :handler #function[compojure.core/wrap-routes/fn--19752]}

(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.

(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

(Reverse-)routing information is injected into the request by the api and thus available for all api-routes at runtime. Handlers can use this for the reverse routing using the path-for macro (or the underlaying path-for* function). Both methods take an extra parameters map, which will be used to populate the possible path-parameters.

(def app
  (api
    (GET "/pong" []
      :name ::pong
      (ok))
    (GET "/ping" []
      (temporary-redirect (path-for ::pong)))))

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

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"}))))