What is Ring in Clojure?

Ring is an easy-to-use library for writing web applications in Clojure, inspired by Python's Web Server Gateway Interface (WSGI) and Ruby's Rack. Ring enables web applications to be built of modular components that can be shared among various applications, web servers, and web frameworks by abstracting the specifics of HTTP into a straightforward, unified application programming interface (API).

Ring architecture in Clojure
Ring architecture in Clojure

Here is the complete request/response process in Ring as shown in the diagram.

  1. The client requests a webpage, and a query is sent to the server.

  2. The HTTP request moves toward Ring before actually moving toward the server.

  3. Ring checks and modifies the request (converts it into a Clojure hash map) and then passes it to the middleware.

  4. Middleware sends the request to the handler.

  5. The handler checks the request map, performs all the necessary functions, and returns the response map with the desired data.

  6. The response, as a map, moves from the handler to the middleware and Ring.

  7. Ring transforms the response map to the HTTP response and sends it back to the user.

Server

In the context of Ring, the server refers to the component that coordinates handling incoming HTTP requests, applying middleware transformations, and dispatching the requests to appropriate handler functions.

Here's a brief explanation of the main components of a Ring server.

Ring

Ring framework works as a middleman between the client and the server, parsing the incoming HTTP request into Clojure hash maps. It coordinates the flow of requests through the middleware stack and eventually to the designated handler function. The server is often started on a specific port and host, allowing it to listen for incoming connections. The app handler also transforms the response from the server into a Clojure hash map. The transformation of requests and responses into hash maps enables us to work with HTTP messages efficiently and seamlessly. Hash maps use the technique of hashing, which converts an object into an integer value. The integer value helps in indexing and faster searches.

Handler

Handler is the function that takes the request map and returns the response with its status and body.
Here are some examples of handler functions:

(defn hello-world-handler
[_]
{:status 200
:headers {}
:body "Hello World"})

Lines 1–5 (hello-world-handler): A function, hello-world-handler, which takes an unused argument, is defined. It returns a response map with the status 200, empty headers, and "Hello World" as the body.

(defn bad-request-handler
[_]
{:status 400
:headers {}
:body "Bad Request: The request could not be understood by the server."})

Lines 1–5 (bad-request-handler): These lines define a function bad-request-handler that takes an unused argument and returns a response map indicating the status 400 (Bad Request), with empty headers, and a body message explaining the issue.

Just like the infamous Error 404: Not found, all the requests with status codes of 400 and onward are considered bad requests that can’t be returned with the data.

(defn internal-server-error-handler
[_]
{:status 500
:headers {}
:body "Internal Server Error: The server encountered an unexpected condition."})

Lines 1–5 (internal-server-error-handler): A function called internal-server-error-handler is defined, which takes an unused argument. It returns a response map containing the status of 500 (Internal Server Error), empty headers, and a body message describing an unexpected server condition.

Sometimes, the server gets down due to unexpected behavior. In that case, the handler will respond with the status code 500.

Middleware

Middleware is also a function between the server and the handler that can modify the request (transform it into the Clojure hash map ) before it reaches the server. This modification allows the communication to be faster and more efficient. In the same way, middleware also modifies the response it receives from the server before it’s passed on to the client through the app.

Here is a middleware function in Clojure:

(defn middleware-add-header
[handler-fn]
(fn [req]
(let [response (handler-fn req)]
(assoc-in response [:headers "X-Custom-Header"] "Middleware Example"))))
(defn main-handler
[req]
{:status 200
:headers {}
:body "Hello from the main handler!"})
(defn combined-handler [req]
(-> main-handler
(middleware-add-header)))
;; Usage
(def req {:method :get
:uri "/example"})
(def response (combined-handler req))
  • Lines 1–2 (middleware-add-header): These lines define a function middleware-add-header that takes handler-fn as an argument and returns a new function. The function returned takes req as an argument.

  • Lines 3–5 (middleware-add-header): The middleware-add-header function invokes handler-fn with the provided req to get a response. It uses the assoc-in function to add a custom header X-Custom-Header with the value Middleware Example to the response. Then, it returns the modified response with the added header.

  • Lines 7–11 (main-handler): The function main-handler is defined that takes req as an argument and returns a map representing a response with the following keys:

    • :status: Set to 200 (OK status code).

    • :headers: An empty map indicating no additional headers initially.

    • :body: Contains the string, “Hello from the main handler!”.

  • Lines 13–15 (combined-handler): They define a function combined-handler using the threading macro (->) that threads the result of main-handler through the middleware-add-header function. It composes the middleware and main handler to create a modified handler.

  • Lines 18–21 (Usage): In this part, a sample request map named req is defined. Finally, combined-handler with the req to get a modified response is invoked. The response is stored in the response variable. The sample request map has the following keys:

    • :method: Set to :get, indicating a GET request.

    • :uri: Set to "/example", representing the requested Uniform Resource Identifier (URI).

This structure allows the easy creation of a series of middleware functions that can process requests and responses as they pass through the pipeline, demonstrating the ability to manipulate the data between the main handler and the final response.

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved