Explore Compojure, a routing library for Clojure, and learn how to define routes, handlers, and build RESTful endpoints with concise syntax.
In this section, we delve into Compojure, a powerful routing library for Clojure, built on top of the Ring library. Compojure provides a concise and expressive syntax for defining routes and building RESTful endpoints, making it an excellent choice for web development in Clojure. As experienced Java developers, you’ll find Compojure’s approach to routing both familiar and refreshingly different from Java’s traditional servlet-based frameworks.
Compojure is a routing library that simplifies the process of defining routes and handling HTTP requests in Clojure web applications. It is built on top of Ring, a Clojure web application library that provides a simple and flexible way to handle HTTP requests and responses.
Before we dive into routing with Compojure, let’s set up a basic Clojure web application with Compojure and Ring.
Create a New Clojure Project: Use Leiningen to create a new Clojure project.
lein new compojure-example
Add Dependencies: Update your project.clj
file to include Compojure and Ring.
(defproject compojure-example "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.3"]
[compojure "1.6.2"]
[ring/ring-defaults "0.3.2"]])
Create a Basic Server: Set up a basic Ring server in src/compojure_example/core.clj
.
(ns compojure-example.core
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.adapter.jetty :refer [run-jetty]]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(defroutes app-routes
(GET "/" [] "Hello, World!")
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
(defn -main []
(run-jetty app {:port 3000 :join? false}))
Run the Server: Start your server using Leiningen.
lein run
Visit http://localhost:3000
in your browser, and you should see “Hello, World!” displayed.
Compojure allows you to define routes using a DSL (Domain-Specific Language) that is both powerful and easy to read. Let’s explore how to define routes and handle parameters.
Routes in Compojure are defined using the GET
, POST
, PUT
, DELETE
, and other HTTP method macros. Here’s a simple example:
(defroutes app-routes
(GET "/" [] "Welcome to Compojure!")
(GET "/hello/:name" [name] (str "Hello, " name "!"))
(POST "/submit" {params :params} (str "Submitted: " params)))
:name
. The parameter is extracted and used in the response.Compojure makes it easy to handle both path and query parameters. Let’s look at an example:
(GET "/search" [query] (str "Searching for: " query))
In this route, query
is a query parameter that can be accessed directly in the route handler.
Compojure supports destructuring, allowing you to extract parameters from the request map. Here’s an example:
(POST "/login" {params :params}
(let [{:keys [username password]} params]
(if (and (= username "admin") (= password "secret"))
"Login successful!"
"Invalid credentials.")))
In this example, we destructure the params
map to extract username
and password
.
Middleware in Ring is a way to wrap handlers with additional functionality, such as logging, authentication, or session management. Compojure routes can be easily integrated with Ring middleware.
To apply middleware to your Compojure routes, use the wrap-defaults
function from ring.middleware.defaults
. Here’s how you can add session and security middleware:
(def app
(-> app-routes
(wrap-defaults site-defaults)))
The site-defaults
middleware includes common middleware for web applications, such as session handling and security headers.
You can also create custom middleware to add specific functionality to your application. Here’s an example of a simple logging middleware:
(defn wrap-logging [handler]
(fn [request]
(println "Request received:" request)
(handler request)))
(def app
(-> app-routes
(wrap-logging)
(wrap-defaults site-defaults)))
Compojure is well-suited for building RESTful APIs. Let’s create a simple API for managing a list of items.
Here’s an example of a basic RESTful API with Compojure:
(def items (atom []))
(defroutes api-routes
(GET "/items" [] @items)
(POST "/items" {params :params}
(let [item (get params "item")]
(swap! items conj item)
(str "Added item: " item)))
(DELETE "/items/:id" [id]
(let [id (Integer. id)]
(swap! items #(vec (remove #(= id (first %)) %)))
(str "Deleted item with id: " id))))
In modern web applications, JSON is a common data format for APIs. Let’s modify our API to handle JSON data using the ring-json
middleware.
Add the Dependency: Update your project.clj
to include ring-json
.
[ring/ring-json "0.5.0"]
Apply JSON Middleware: Use wrap-json-body
and wrap-json-response
to handle JSON requests and responses.
(ns compojure-example.core
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.adapter.jetty :refer [run-jetty]]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[ring.middleware.json :refer [wrap-json-body wrap-json-response]]))
(def app
(-> app-routes
(wrap-json-body)
(wrap-json-response)
(wrap-defaults site-defaults)))
Update the API: Modify the API to work with JSON data.
(POST "/items" {body :body}
(let [item (:item body)]
(swap! items conj item)
{:status 201 :body {:message "Item added" :item item}}))
For Java developers, the transition to Compojure from traditional servlet-based frameworks can be enlightening. Let’s compare a simple route in Compojure with a Java servlet.
(GET "/hello/:name" [name] (str "Hello, " name "!"))
@WebServlet("/hello/*")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getPathInfo().substring(1);
response.getWriter().write("Hello, " + name + "!");
}
}
Comparison:
Now that we’ve covered the basics of routing with Compojure, try modifying the code examples to deepen your understanding:
By leveraging Compojure, you can build robust and maintainable web applications in Clojure. As you continue your journey, explore more advanced features and integrations to fully harness the power of Clojure for web development.