Skip to content

Serving static resources

Tommi Reiman edited this page Jun 20, 2016 · 2 revisions

Compojure-api stands on top of Ring and Compojure, so you can use use their standard resource-functions to serve static resources. There are two main ways to do this:

Separate routes

Wrap your api into a routes that also serves the static resources.

(require '[compojure.api.sweet :refer :all])
(require '[compojure.route :as route])

(defn my-api [system]
  (api
   {:swagger
    {:ui "/api-docs"
     :spec "/swagger.json"}}}
​
     (context "/api" []
       :tags ["api"]
​
       ...)))
​
(defn app [system]
  (routes
    (my-api system)
    (route/resources "/")
    (route/not-found "404 Not Found")))

Embedded routes

Resources can also be served from inside the api. Note: non-api routes should be wrapped into undocumented route function to avoid WARNINGs to be logged as the resource-routes don't satisfy compojure.api.routes/Routing protocol.

(require '[compojure.api.sweet :refer :all])
(require '[compojure.route :as route])

(defn my-api [system]
  (api
   {:swagger
    {:ui "/api-docs"
     :spec "/swagger.json"}}}
​
     (context "/api" []
       :tags ["api"]
​
       ...)

     (undocumented
       (route/resources "/")
       (route/not-found "404 Not Found")))

Caveats

api mounts ring-middleware-format middleware by default. It consumes the request body and binds the parsed result into the request under :body-params, which is visible within the api. If there are other routes next to the api, thanks to Clojure Immutability and Mutable Java Streams, they only the original request where the request body is already consumed and thus can't be re-parsed. If you need to (re-)consume the body in different routes, you need to mount the ring-middleware-format on top of all the routes.

This doesn't work

(def app
  (routes
    ;; api eagerly consumes the :body InputStream 
    (api
      ;; parsed body available
      (POST "/echo" []
        :body [data {:name s/Str}]
        (ok data)))
    ;; api sees already consumed :body InputStream
    (api
      ;; body is nil here :(
      (POST "/echo2" []
        :body [data {:name s/Str}]
        (ok data)))))

This works

(def app
  (middleware [[ring.middleware.format-params/wrap-restful-params request-options]
               [ring.middleware.format-response/wrap-restful-response response-options]]
    ;; parsed body available
    (api
      (POST "/echo" []
        :body [data {:name String}]
        (ok data)))
    ;; here too, cool.
    (api
      (POST "/echo2" []
        :body [data {:name String}]
        (ok data)))))