Clojure Guide — Compojure: Declarative Web Routing
In this tutorial, you will learn about Clojure Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Compojure is a small routing library for Ring-based web applications that provides a concise DSL for defining HTTP routes, route parameters, and middleware composition using macros.
What You'll Learn
- Defining routes with GET, POST, PUT, DELETE macros
- Route parameters and destructuring
- Compojure's routing order and specificity
- Combining routes with routes macro
- Using Compojure with Ring middleware
Why It Matters
Compojure makes routing in Clojure clean and readable. Instead of manual URI matching, you declare routes declaratively. Doda Browser uses Compojure-style routing in its internal extension API.
Real-World Use
Many Clojure Web Services use Compojure for routing, often combined with Ring middleware for authentication, logging, and sessions.
flowchart LR
A["Compojure"] --> B["Route Macros"]
B --> C["Route Parameters"]
C --> D["Middleware"]
D --> E["API Design"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Basic Routing
(require '[compojure.core :refer [defroutes GET POST PUT DELETE ANY]])
(defroutes app
(GET "/" [] "Home page")
(GET "/hello" [] "Hello World")
(GET "/about" [] "About us"))
;; Each route matches the HTTP method and URI
Route Parameters
;; Path parameters
(defroutes app
(GET "/user/:id" [id]
(str "User ID: " id))
(GET "/post/:year/:month/:slug" [year month slug]
(str "Post from " year "/" month " titled " slug)))
;; Query parameters
(defroutes app
(GET "/search" [q page]
(str "Search: " q " page: " page)))
Destructuring Parameters
(defroutes app
;; Destructure with let-like syntax
(GET "/user/:id" [id :as request]
(str "User " id " from " (:remote-addr request)))
;; Default values
(GET "/items" [page per-page]
(let [page (or (Integer/parseInt page) 1)
per-page (or (Integer/parseInt per-page) 20)]
(str "Items " page " to " (* page per-page)))))
Composing Routes
;; Combine multiple route groups
(defroutes user-routes
(GET "/users" [] "List users")
(GET "/user/:id" [id] (str "User " id))
(POST "/user" {body :body} (str "Create user: " (slurp body))))
(defroutes admin-routes
(GET "/admin" [] "Admin panel")
(GET "/admin/users" [] "Admin user list"))
;; Combine into one app
(defroutes app
user-routes
admin-routes
(ANY "*" [] {:status 404 :body "Not Found"}))
Middleware with Compojure
(defroutes handler
(GET "/" [] "Hello")
(GET "/protected" [] "Secret area"))
;; Wrap entire app
(def app
(-> handler
wrap-logging
wrap-session
(wrap-file "public")))
;; Route-specific middleware
(defroutes app
(GET "/public" [] "Public")
(GET "/private" request
(if (authenticated? request)
{:status 200 :body "Secret"}
{:status 401 :body "Unauthorized"})))
Context Routes
(require '[compojure.route :as route])
;; Group routes under a prefix
(defroutes api-routes
(context "/api/v1" []
(GET "/users" [] "API users")
(GET "/posts" [] "API posts")))
;; Static resources
(defroutes app
api-routes
(route/resources "/") ; Serve from classpath
(route/not-found "404")) ; Catch-all
Compojure with Ring
(require '[ring.adapter.jetty :as jetty])
(require '[ring.middleware.params :refer [wrap-params]])
(require '[ring.middleware.keyword-params :refer [wrap-keyword-params]])
(defroutes handler
(GET "/hello" [name]
(str "Hello " (or name "World"))))
(def app
(-> handler
wrap-keyword-params
wrap-params))
(jetty/run-jetty app {:port 8080 :join? false})
REST API Example
(defroutes api
(GET "/api/items" [] (list-items))
(GET "/api/item/:id" [id] (get-item id))
(POST "/api/item" {body :body} (create-item (json/parse-string (slurp body) true)))
(PUT "/api/item/:id" [id body] (update-item id body))
(DELETE "/api/item/:id" [id] (delete-item id)))
Common Mistakes
1. Route ordering matters
More specific routes must be defined before wildcard routes. Compojure matches in order.
2. Missing wrap-params
Query parameters and form data are not automatically parsed. Use wrap-params and wrap-keyword-params.
3. Assuming route params are parsed
Route params are strings. Parse them to integers or other types explicitly.
4. Not handling OPTIONS for CORS
Browsers send preflight OPTIONS requests. Handle them explicitly or use CORS middleware.
5. Nesting too many context levels
Deep context nesting makes routes hard to read. Flatten route definitions where possible.
Practice Questions
1. How does Compojure match routes? Compojure matches routes in the order they are defined, first matching the HTTP method (GET, POST, etc.), then the URI pattern.
2. What is the difference between route parameters and query parameters?
Route parameters are part of the URI path like /user/:id. Query parameters are in the URL query string like ?name=Alice.
3. How do you combine multiple route groups?
Use the routes macro or define multiple defroutes and compose them in a single defroutes form.
Challenge: Build a REST API for a todo application using Compojure with CRUD routes and proper middleware.
FAQ
{{< faq question="Is Compojure still maintained?" >}} Compojure is stable and receives maintenance updates. Newer alternatives include Reitit (more features) and Pedestal (different paradigm), but Compojure remains popular. {{< /faq >}}
{{< faq question="How does Compojure compare to Reitit?" >}} Reitit is newer with built-in coercion, data-driven routing, and better performance. Compojure is simpler and uses macros. Both are excellent choices. {{< /faq >}}
{{< faq question="Can Compojure handle WebSocket routes?" >}} Not natively. Use a Websocket library like Sente or http-kit alongside Compojure for HTTP routes. {{< /faq >}}
{{< faq question="Does Compojure support middleware on specific routes?" >}}
Yes. Wrap individual routes or route groups with -> before combining them with other routes.
{{< /faq >}}
{{< faq question="How do I serve static files with Compojure?" >}}
Use compojure.route/resources or compojure.route/files to serve static assets from the classpath or filesystem.
{{< /faq >}}
Mini Project
Build a URL shortener API with Compojure:
(def urls (atom {}))
(defroutes url-shortener
(POST "/shorten" [url]
(let [id (-> (java.util.UUID/randomUUID) str (subs 0 8))]
(swap! urls assoc id url)
{:status 201 :body (str "Short URL: /r/" id)}))
(GET "/r/:id" [id]
(if-let [url (@urls id)]
{:status 302 :headers {"Location" url}}
{:status 404 :body "Not found"})))
(def app
(-> url-shortener wrap-params))
(jetty/run-jetty app {:port 8080 :join? false})
What's Next
Now that you understand Compojure, learn Hiccup for generating HTML in Clojure.
| Topic | Description | Link |
|---|---|---|
| Clojure Hiccup | HTML generation | {{< ref "23-hiccup" >}} |
| Clojure Advanced Testing | Testing web apps | {{< ref "24-testing-advanced" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro