Combining a Site and API in Compojure

It’s often useful to have both an API and a website serving HTML in the same project. Compojure is a popular routing library in Clojure built on top of the Ring web server abstraction. In this blog we’ll build a small “hello worldish” site and REST API in Compojure with different middleware. The code is available on github.


Given we’ve created a new leining compojure project using something like lein new compojure compojure-example we can start building our api. The API will simply contain an entry point that return a link to the greeting route that’ll use to greet a person. The links are rendered according to the HAL specification. Below you can see all the code for our API:

(ns compojure-example.api
  (:require [compojure.core :refer :all]
            [compojure.handler :as handler]
            [compojure.route :as route]
            [cheshire.core :as json]
            [ :as io]))

(defn get-base-uri [request]
  "Generate a base uri from a ring request. For example 'http://localhost:5000/api'."
  (let [scheme (name (:scheme request))
        context (:context request)
        hostname (get (:headers request) "host")]
    (str scheme "://" hostname context)))

(defn json-response [data & [status]]
  {:status  (or status 200)
   :headers {"Content-Type" "application/hal+json; charset=utf-8"}
   :body    (json/generate-string data)})

(defroutes api-routes
           (context "/api" []
                    (GET "/" request
                         (let [base-uri (get-base-uri request)
                               hal-links {:_links {:self {:href base-uri} :greet {:href (str base-uri "/greet{?name}") :templated true}}}]
                           (json-response hal-links)))
                    (GET "/greet" [name :as request]
                         (let [base-uri (get-base-uri request)]
                           (json-response {:greeting (str "Greetings " name) :_links {:self {:href (str base-uri "/greet?name=" name)}}})))
                    (ANY "*" []
                         (route/not-found (slurp (io/route "404.html"))))))

(def rest-api
  (handler/api api-routes))

So let’s go through the example. We start off by defining a get-base-uri function that returns the base uri from the request. We’ll use this to construct the links that are returned from our API. Next up is the json-response function that acts as a template for all our API responses. It assumes status code 200 and content-type hal as well as a JSON encoded body. Our Compojure routes are defined in the api-routes. Since we’d like all our routes to be located under “/api” we’ll use a Compojure context to wrap all routes under this path. The entry point (“/”) simply returns a templated link to the greet route as well as a self link (mandatory in HAL) as a JSON response by calling our json-response function. We use the get-base-uri function defined earlier to construct our base uri used in the links. The “/greet” route returns a link to self and the actual greeting message. If no route is found we return an error page (“404.html” located in the resources folder). Note that it’s important to specify the “not-found” route inside the “/api” context when we’re combining the site and the api. At last we define “rest-api” as a handler with middleware suitable for a web API. This allows for parameter deconstruction in Compojure.


Now that we have the our API we can define a simple site that serves an HTML page that uses the API. The code is shown here:

  (:require [compojure.core :refer :all]
            [compojure.handler :as handler]
            [compojure.route :as route]
            [ :as io]))

(defroutes site-routes
           (GET "/" [] (slurp (io/resource "public/index.html")))
           (route/resources "/")
           (route/not-found (slurp (io/resource "404.html"))))

(def site
  (handler/site site-routes))

What we’re doing is essentially to say that when we reach the entry point we should return the “index.html” page located in the resources/public folder. Other than that we should serve our resources (javascript, html, css etc) from the default (resources/public) folder. If we navigate to an unknown route will return the same “404.html” page as we did in the API.

Putting it together

Now we have a site and an API which is great but we need to combine the two. We can do this by creating a new namespace called boot which contains the following:

(ns compojure-example.boot
  (:require [compojure-example.api :refer [rest-api]]
            [ :refer [site]]
            [compojure.core :refer [routes]]))

; Combine the site and rest-api
(def site-and-api (routes rest-api site))

By defining site-and-api in its own namespace neither the API nor site need to be aware of each other (and could be used separately if needed). By calling routes we combine two or more handlers into one. Note that it’s important that we provide the rest-api before site since otherwise the site will return the 404.html page when calling “/api”. This is because the “/api” route is not defined in the routes declaration in site and thus would match route/not-found.

We should also update our project.clj file to use compojure-example.boot/site-and-api as our Ring handler (so that we can start the server using lein ring server).


As we’ve seen it’s easy to create a simple site and API in compojure once you know how to go about. I found the Compojure documentation lacking somewhat if you were completely new to it so hopefully this guide can help someone in the same situation. The code is available on github.

This Post Has 2 Comments

  1. Sascha

    Hi Johan,
    thanks for showing how an API and Website can live together in such a clear way.
    This helped me a lot. — sk

  2. tom lindsey


    Stumbled across this article. I am having an odd problem, which I solved, or should I say, “worked around” but I since you seem to have a good handle on these things I thought I would see if you would be interested in seeing the problem and weighing in on what the cause is and what a good solution would be.



Leave a Reply