HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
ring-clojure
ring
Clojure2012–2026
ring-clojure / ring

ring

Clojure HTTP server abstraction

21Distinct people
28Mentions
1Explicit recommenders
3.9KGitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

Clojure HTTP server abstraction

GitHub

Where it is used

No documented product use was verified in the last source check.

What people are saying

Mitchell: First allow me to apologize for the tone of my comment. Re-reading it now, I realize that I glossed over a key point: I used Vagrant every day for a team of 5 people.…

snprbob86 used a recommendation phrase. One indexed comment matches. This is a phrase match, not a verdict.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

Mentioned in the same Hacker News threads. That is not proof they are substitutes.

This week on Hacker News

Nov 8–Nov 15 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/15/2026, 12:45:45 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year28 dated mentions

Observed mentions: 2012-08-20 17:16:56 UTC to 2026-08-12 14:24:30 UTC.

02462012-01-01 00:00:00 UTC to 2013-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments20122013-01-01 00:00:00 UTC to 2014-01-01 00:00:00 UTC (exclusive): 6 mentions; 0 submissions, 6 comments2014-01-01 00:00:00 UTC to 2015-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2015-01-01 00:00:00 UTC to 2016-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2016-01-01 00:00:00 UTC to 2017-01-01 00:00:00 UTC (exclusive): 4 mentions; 0 submissions, 4 comments2017-01-01 00:00:00 UTC to 2018-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2018-01-01 00:00:00 UTC to 2019-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 5 mentions; 0 submissions, 5 comments20192020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 3 mentions; 2 submissions, 1 comments2021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2025-01-01 00:00:00 UTC to 2026-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2026-01-01 00:00:00 UTC to 2027-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments. Incomplete calendar period.2026*

* An incomplete calendar period is marked with an outlined bar. Its count is not directly comparable with a full period.

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20122012-01-01 00:00:00 UTC2013-01-01 00:00:00 UTC101
20132013-01-01 00:00:00 UTC2014-01-01 00:00:00 UTC606
20142014-01-01 00:00:00 UTC2015-01-01 00:00:00 UTC202
20152015-01-01 00:00:00 UTC2016-01-01 00:00:00 UTC202
20162016-01-01 00:00:00 UTC2017-01-01 00:00:00 UTC404
20172017-01-01 00:00:00 UTC2018-01-01 00:00:00 UTC000
20182018-01-01 00:00:00 UTC2019-01-01 00:00:00 UTC202
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC505
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC321
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC000
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC101
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC101
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC000
20252025-01-01 00:00:00 UTC2026-01-01 00:00:00 UTC000
2026 (partial)2026-01-01 00:00:00 UTC2027-01-01 00:00:00 UTC101

From the project README

An excerpt from the repository, fetched Nov 15, 2026.

Read the project’s introduction
Ring Ring is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks. The SPEC.md file at the root of this distribution provides a complete description of the Ring interface. The Wiki contains more in-depth documentation on how to use Ring. Libraries ring/ring - meta-package containing all relevant dependencies ring/ring-core - core functions and middleware for Ring handlers, requests and responses org.ring-clojure/ring-core-protocols - contains only the protocols necessary for building Ring responses org.ring-clojure/ring-websocket-protocols - contains only the protocols necessary for WebSockets ring/ring-devel - functions for developing and debugging Ring applications ring/ring-servlet - construct legacy Java Servlets (≤ 4.0) from Ring handlers org.ring-clojure/ring-jakarta-servlet construct Jakarta Servlets (≥ 5.0) from Ring handlers ring/ring-jetty-adapter - a Ring adapter that uses an embedded Jetty web server Installation To include one of the above libraries, for instance ring-core, add the following dependency to your deps.edn file: ring/ring-core {:mvn/version "1.15.6"} Or to your Leiningen project file: [ring/ring-core "1.15.6"] Documentation Changelog Wiki API docs Contributing Please read CONTRIBUTING.md before submitting a pull request. Thanks This project borrows heavily from Ruby's Rack and Python's WSGI; thanks to those communities for their work. Thanks also go to the many individuals who have contributed to Ring's code and documentation over the years. License Copyright © 2009-2026 Mark McGranaghan, James Reeves & contributors. Released under the MIT license.
Read the full README on GitHub

What Hacker News says

28 sources
The Clojure's approach is to favour libraries over frameworks. We use Clojure on various style of applications (mainly web and API based), hosted on widespread infrastructure technologies of Cloud Providers (Kubernetes, PostgreSQL, Kafka, etc.) and it's a breeze: here are our reasons for choosing Clojure https://defsquare.com/blog/why-we-bet-on-clojure If you want to look at libraries where some people have settled, here are our _opinionated_ takes on libraries that we use on almost every project: - HTTP: Ring is the de facto way to manage HTTP request (see https://github.com/ring-clojure/ring/wiki/Concepts) supplemented with various "Middlewares" for web-related concerns (security, logging, content encoding, etc. see https://github.com/ring-clojure/ring/wiki/Standard-middlewar...). Jetty and Aleph are common web servers (and https://gith
Read the rest
ub.com/clj-commons/aleph) that implement Ring interface. - Routing: Reitit (https://github.com/metosin/reitit) - Single-Page App: shadow-cljs for the build concerns (https://github.com/thheller/shadow-cljs), Reagent with Re-frame for complex/large app (https://reagent-project.github.io and https://github.com/day8/re-frame). Even if we now prefer using HTMX (https://htmx.org) and server-side rendering (Hiccup way of manipulating HTML is just amazing, https://github.com/weavejester/hiccup). - Schema management: Malli (https://github.com/metosin/malli) - Lifecycle management: Mount, Integrant or Component (https://github.com/tolitius/mount https://github.com/weavejester/integrant and https://github.com/stuartsierra/component) - Config management: Aero (https://github.com/juxt/aero) and environ (https://github.com/weavejester/environ) - Log: μ/log (https://github.com/BrunoBonacci/mulog) - Time: https://github.com/juxt/tick - Testing: https://clojure.github.io/clojure/clojure.test-api.html and Kaocha for the runner (https://github.com/lambdaisland/kaocha). - Package and project management: tools.deps is simple and powerful (https://clojure.org/guides/deps_and_cli) For infrastructure stuff (PostgreSQL, Keycloak, Solr, Kafka, etc.), just wrapping the underlying Java library or driver is often enough, and there are lots of wrappers for easing the integration. And we implement Hexagonal architecture style to put all of that in place. Don't be afraid a libraries not moving a lot recently, it's a sign that the library is mature and battle-tested. Clojure is a joy to use every day :)
In A History of Clojure (2020) [pdf]
> In the HTTP handlers it makes sense that you don't have return values, because: What would you do with that value exactly? I think that approach used by clojure's ring shows an elegant way to represent http responses https://github.com/ring-clojure/ring/wiki/Concepts#responses. They are essentially structs with the following fields: status := number headers := map of string->string body := stream | string | seq | inputstream Request handlers are handed a request struct that is similar. The handler is a function that maps a request to a response (it doesn't actually write to streams itself). I like this style for an http library for a couple of reasons: 1. HTTP resources can be viewed as functions whose domain is the request, and range is the response. Having the abstraction match that makes for really nice code. 2. If you model the req
Read the rest
uest/response structs symmetrically and expose your http client as a handler itself, you can write proxies very easily. For an example of this, see http4k (https://www.http4k.org/documentation/) (a kotlin library). I won't argue that the net/http abstraction should change, but I do agree with the author's take on the desired shape of an http library. I'd probably just write my own abstraction on top to fill the gap.
In The problem with Go’s default HTTP handlers
Clojure, for those unfamiliar with the language, unsurprisingly, does in fact allow for both 'for' and 'while' loops, and are part of the core language library. https://clojuredocs.org/clojure.core/while https://clojuredocs.org/clojure.core/for And here are examples picked from a random repository of these constructs being used in the wild: https://github.com/ring-clojure/ring/search?q=while What clojure does do is also provide a number of higher level control constructs, and, by the nature of the syntax of the language, puts them on the same level as 'while' and 'for', which themselves are part of a library, rather than baked into the language itself. This can, of course, be done in other languages, using fluent interfaces. https://en.wikipedia.org/wiki/Fluent_interface But is typically much more awkward and harder to compose. Also, you
Read the rest
typically have to wrap expressions in lambdas.
In Parse, don’t type-check
Some of my favorite Clojure libraries in this space: - instaparse: takes EBNF (and other formats) as a string and gives you a parser instantly (!!!) https://github.com/Engelberg/instaparse - re-frame: React-Redux alternative that IMO is much easier to work with https://github.com/day8/re-frame - reagent: React wrapper https://reagent-project.github.io/ - ring https://github.com/ring-clojure/ring - fulcro https://github.com/fulcrologic/fulcro More than any library, though, what keeps bringing me back to Clojure is its incredibly blissful set of persistent data structures. It's a pernicious accident of history that we are taught to accept references to mutable objects as normal, inevitable, and desirable in most CS curricula. It is much easier to develop, debug, and maintain immutable data structures instead, and I consider my good fortune
Read the rest
every time I get to debug something in Clojure rather than in some reference-laden OOP soup.
In Using Clojure for Web Apps
A proof of correctness is what static typing is fundamentally. The example I provided simply turns this up to 11. However, if you relax the constraint then you're losing benefits of statically checking properties at compile time. If you agree that having a 10 line version that does less checking is better than having a 260 line version that does, then you're agreeing that there is a cost associated with static checking. There are plenty of real world scenarios where static checking gets in the way even if you're not trying to encode complex properties using the type system. One problem is that static typing is at odds with modularization since type declarations are considered globally. For example, Ring HTTP abstraction in Clojure represents requests and responses as maps. Middleware functions [1] can update these maps to inject additiona
Read the rest
l keys, or modify existing keys. These functions often live in separate libraries that know nothing about one another. A static type system precludes this since it would require you to provide a static declaration for every possible request and response map. [1] https://github.com/ring-clojure/ring/wiki/Middleware-Pattern...
In What I Want from a Type System (2016)
It's more about controlling state. I like to think of ring[0] as a really nice example of this that's pretty easy to reason about. It abstracts http handling into a series of key/value data structures. The request that's pulled in looks like this: {:remote-addr "localhost" :headers {"host" "localhost" "content-type" "application/json" "accept" "application/json"} :server-port 80 :content-type "application/json" :uri "/" :server-name "localhost" :query-string nil :body "" :scheme :http :request-method :get} The response like this: {:status 200 :headers {"Content-Type" "text/plain"} :body "Hello World"} Functions manipulate these structures, but their input and outputs are primarily those data structures. (defn handler [request] {:status 200 :headers {"Con
Read the rest
tent-Type" "text/plain"} :body "Hello World"}) Your app is now just a function stack that get's called in this order: (def app (-> handler (wrap-content-type "text/html"))) Request => wrap-content-type => handler (generate response) => wrap-content-type => Returned Want to add middleware? Define a function that handles it, here's an example[1]: (defn middleware [handler] (fn [request] ;; Do something to the request before sending it down the chain. (let [response (handler request)] ;; Do something to the response that's coming back up the chain. response))) Then add the function to your app: (def app (-> handler (wrap-content-type "text/html") (wrap-keyword-params) (wrap-params))) You want to handle session data? Ring provides wrap session[2], add it and then use your own middleware or handler to manipulate the session by just changing the value at :session. Example [3]: (defn handler [{session :session}] (let [count (:count session 0) session (assoc session :count (inc count))] (-> (response (str "You accessed this page " count " times.")) (assoc :session session)))) Much of your world is now reduced down to: * Does this data structure look correct? * How can I fix it? * Do I need some resource like a db connection? Ok how do I add it to my data structure? It all composes, and you end up worrying a lot less. Finally I find that doing things this way ends up creating programs that just have much less explicit state, as opposed to temporary state that's derived from other values and so only changes when those things change. You can program in other languages like this, it's just a bit harder, and you have to hope that no one else you're working with breaks one of the assumptions that a system like this is built on, as opposed to having the language enforce those for you. Sorry if I've been unclear =)... [0] - https://github.com/ring-clojure/ring/wiki (Examples taken from ring). [1] - https://stackoverflow.com/a/19459508 [2] - https://github.com/ring-clojure/ring/blob/95e4ca25d5b98c45f9... [3] - https://github.com/ring-clojure/ring/wiki/Sessions
In The Functional Style (2018)
Except that they're not the same. Static typing restricts you to a set of statements that can be verified by the type checker. This is a subset of all valid statements, otherwise you could just type check code in any language at compile time. Static typing makes many dynamic patterns either difficult or impossible to use. For example, Ring middleware becomes pretty much impossible in a static language https://github.com/ring-clojure/ring/wiki/Middleware-Pattern... The pattern here is that the request and response are expressed as maps. The request map is passed through a set of middleware functions, and each one can modify this map in some way. A dynamic language makes it possible to write middleware libraries that know absolutely nothing about each other, and compose seamlessly. A static language would require you to provide a full descr
Read the rest
iption of every possible permutation of the request map, and every library would have to conform to it. This creates coupling because any time a library needs to create a new key that only it cares about, the global spec needs to be modified to support it. My experience is that immutability plays a far bigger role than types in addressing the problem of maintainability. Immutability as the default makes it natural to structure applications using independent components. This indirectly helps with the problem of tracking types in large applications as well. You don't need to track types across your entire application, and you're able to do local reasoning within the scope of each component. Meanwhile, you make bigger components by composing smaller ones together, and you only need to know the types at the level of composition which is the public API for the components.
In Fearless Concurrency: Clojure, Rust, Pony, Erlang and Dart
There is a cost associated with eliminating this class of errors using static typing. The cost is that you're restricted to a set of statements that the type checker can verify to be correct. Writing code for the benefit of the type checker is often at odds with writing it in a way that conveys the meaning best to the human reader. This is necessarily less expressive than the dynamic approach. Code written in dynamic languages tends to do a better job of expressing its intent because it can be written in a more direct fashion. Here's a concrete real world example of what I'm talking about: >When I first wrote the core.async go macro I based it on the state monad. It seemed like a good idea; keep everything purely functional. However, over time I've realized that this actually introduces a lot of incidental complexity. And let me explain t
Read the rest
hat thought. >What are we concerned about when we use the state monad, we are shunning mutability. Where do the problems surface with mutability? Mostly around backtracking (getting old data or getting back to an old state), and concurrency. >In the go macro transformation, I never need old state, and the transformer isn't concurrent. So what's the point? Recently I did an experiment that ripped out the state monad and replaced it with mutable lists and lots of atoms. The end result was code that was about 1/3rd the size of the original code, and much more readable. >So more and more, I'm trying to see mutability through those eyes: I should reach for immutable data first, but if that makes the code less readable and harder to reason about, why am I using it? https://groups.google.com/forum/#!topic/clojure/wccacRJIXvg Another example of something that's trivial in a dynamic language, but difficult to do in a static one would be Ring middleware: https://github.com/ring-clojure/ring/wiki/Middleware-Pattern... So really what you're doing with static typing is trading one set of problems for another. This is perfectly fine if those are the kinds of problems you prefer to deal with, but it's important to recognize that you are making a trade off as opposed to getting something for free here.
In IBM releases Elm-powered app
Sort of. Java interop is an extra win for Clojure, but I really meant just the Clojure libraries. Every time I need one, it seems much more feature-complete than CL alternative. To take an example: in a project I'm working on, we're using Ring[0] at the backend + bunch of middleware, including Liberator[1]. Take a look at e.g. Liberator docs[2] and tell me, where's a CL library for just that, with so much thought that went into it? Or, in a CL project we had to implement CORS handling ourselves, whereas in Clojure we get it for free. Etc. This kind of story seems to repeat every time I need a library. In Common Lisp, you have to hope that Edi Weitz, Shinmera or Fukamachi wrote something, and that you won't have to patch much to make it work for you. In Clojure, everything I've seen so far[3] makes me expect to find solid and feature-compl
Read the rest
ete libraries whenever I need something. Now to be clear, I still love working in Common Lisp. It's just that my experience with Clojure made me jealous. -- [0] - https://github.com/ring-clojure/ring [1] - https://github.com/clojure-liberator/liberator [2] - http://clojure-liberator.github.io/liberator/ [3] - I've been writing Clojure for only couple of months, so this may be colored by first impressions.
In A new version of Common-Lisp.net has been launched
I've worked with many kinds of statically typed languages, including Java, Haskell, and Scala for nearly a decade. I ultimately found that static typing introduces a lot of mental overhead in practice, and limits the way you're able to express yourself. Many dynamic patterns such as Ring middleware https://github.com/ring-clojure/ring/wiki/Middleware-Pattern... become difficult in static languages. I've been working with Clojure about 8 years ago, and I don't miss types in the slightest. If I did, I would've gone back to a typed language a long time ago. Dynamic typing tends to be problematic in imperative/OO languages. One problem is that the data is mutable, and you pass things around by reference. Even if you knew the shape of the data originally, there's no way to tell whether it's been changed elsewhere via side effects. The other pr
Read the rest
oblem is that OO encourages proliferation of types in your code. Keeping track of that quickly gets out of hand. What I find to be of highest importance is the ability to reason about parts of the application in isolation, and types don't provide much help in that regard. When you have shared mutable state, it becomes impossible to track it in your head as application size grows. Knowing the types of the data does not reduce the complexity of understanding how different parts of the application affect its overall state. My experience is that immutability plays a far bigger role than types in addressing this problem. Immutability as the default makes it natural to structure applications using independent components. This indirectly helps with the problem of tracking types in large applications as well. You don't need to track types across your entire application, and you're able to do local reasoning within the scope of each component. Meanwhile, you make bigger components by composing smaller ones together, and you only need to know the types at the level of composition which is the public API for the components. REPL driven development http://blog.jayfields.com/2014/01/repl-driven-development.ht... also plays a big role in the workflow. Any code I write, I evaluate in the REPL straight from the editor. The REPL has the full application state, so I have access to things like database connections, queues, etc. I can even connect to the REPL in production. So, say I'm writing a function to get some data from the database, I'll write the code, and run it to see exactly the shape of the data that I have. Then I might write a function to transform it, and so on. At each step I know exactly what my data is and what my code is doing. Where I typically care about having a formalism is at component boundaries. Spec provides a much better way to do that than types. The main reason being that it focuses on ensuring semantic correctness. For example, consider a sort function. The types can tell me that I passed in a collection of a particular type and I got a collection of the same type back. However, what I really want to know is that the collection contains the same elements, and that they're in order. This is difficult to express using most type systems out there, while trivial to do using Spec.
In Idioms of Dynamic Languages

Showing the 15 most recent of 28 sources. Enable JavaScript to search and browse every indexed mention.

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.