This repository has been archived by the owner. It is now read-only.
Multi-target Clojure/script HTTP client
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.
src/kvlt Generate doc-examples string at compile time (#34) Jan 19, 2018

Hail Satan Build Status

Attempts to present a uniform, asychronous client interface for HTTP across JVM / Node / browsers.

Latest documentation / examples

Clojars Project


  • Supports Clojure/JVM, Clojurescript/Node and Clojurescript/Browser
  • Individual deferred values are exposed via promises (kvlt.core), or asynchronous channels (kvlt.chan)
  • core.async-based support for Websockets and Server-sent Events
  • Raw responses available as Javascript typed arrays (on Node, and in browsers with XHR Level 2 support)
  • Ring-like API


  • Clojure use requires JDK8

Todo / Notes

  • Automated/CI testing is currently limited to JVM, Node and recent Chrome & Firefox builds
  • No support for streamed requests/responses. Open to suggestions about how this might be handled across platforms
  • Young project, etc. - please file issues


kvlt.core/request! returns a promesa promise, which can be manipulated using promise-specific (e.g. promesa/then) operations, or treated as a monad using the primitives from cats. Below, we're working with something like:

(ns kvlt.examples
  (:require [kvlt.core :as kvlt]
            [promesa.core :as p]))

The default :method is :get:

(p/alet [{:keys [status]} (p/await (kvlt/request! {:url url}))]
  (is (= status 200)))

Explicit Callback

 (kvlt/request! {:url url})
 (fn [{:keys [status]}]
   (is (= status 200))))


The kvlt.chan namespace parallels the promise-driven kvlt.core, using asynchronous channels to communicate deferred values.

  (let [{:keys [status]} (<! (kvlt.chan/request! {:url url}))]
    (is (= status 200))))

Writing Data

In addition to Ring-style :form-params/:type, metadata may be applied to :body, indicating the desired content-type and body serialization:

(p/alet [{:keys [body]}
           {:url    url
            :method :post
            :body   ^:kvlt.body/edn {:hello "world"}
            :as     :auto}))]
  (is (= (body :hello) "world")))

:as :auto causes the :body key in the response to be processed in accord with the response's content-type. :as :edn, in this case, would have the same effect.


 (kvlt/request! {:url (str url "/404")})
 (fn [e]
   (is (= :not-found ((ex-data e) :type)))))

All requests resulting in exceptional response codes, or more fundamental (e.g. transport) errors will cause the returned promise's error branch to be followed with an ExceptionInfo instance - i.e. an Exception/Error with an associated response map retrievable via ex-data.

Example Map

{:headers {:content-type "text/plain" ...}
 :type   :not-found
 :reason :not-found
 :status 404

More request/response examples

Server-sent events

(def events (kvlt.core/event-source! url))

events is a core.async channel (rather, something a lot like a core.async channel, which'll terminate the SSE connection when async/close!'d).

Assuming no event types or identifiers are supplied by the server, a value on events looks something like:

{:type :message
 :data "String\nfrom\nthe server"}

More SSE examples


kvlt.core/websocket! takes a URL, and returns a promise which'll resolve to a core.async channel:

(p/alet [ws (p/await (kvlt/websocket! "http://localhost:5000/ws" {:format :edn}))]
    (>! ws {:climate :good, :bribery :tolerated})
    (let [instructions (<! ws)]
      (is (instructions :proceed?)))))

Closing the ws channel will terminate the websocket connection.

More Websocket examples


kvlt is free and unencumbered public domain software. For more information, see or the accompanying UNLICENSE file.