A simpler way to have fullstack Clojure[script] applications communicate, defining APIs as data.
FRs, PRs and comments welcome!
(:require [ensorcel.conjure :as conjure])
Ensorcel is easy to get starting with - there are 3 steps:
Let's define a widget retrieval service that has a get and get-all method..
; example/api.cljc
(ns example.api
(:require [ensorcel.types :as t]
[spec-tools.core :as st]
#?(:clj [clojure.spec.alpha :as s]
:cljs [cljs.spec.alpha :as s :include-macros true])))
(s/def :widget/id ::t/integer)
(s/def :widget/msg ::t/string)
; we wrap our key specs into spec-tools so that we can strip
; any extra keys
(s/def ::widget
(st/spec (s/keys :req-un [:widget/id :widget/msg])))
(s/def ::get-widget-request
(st/spec (s/keys :req-un [:widget/id])))
(def spellbook
{:version "1"
:services {:widgets {:path "widgets"
:endpoints {:get-all {:path ""
:method :GET
:returns (s/* ::widget)}
:get {:path [:id]
:method :GET
:args ::get-widget-request
:returns ::widget}}}}})
A spellbook defines one or more services, each of which has one or more endpoints.
In the example above, we define two endpoints located under <addr>:<port>/api/widgets/
,
one at widgets/
and one at widgets/<id>
.
Next up, we define our backend:
; example/server.clj
(ns example.server
(:require [example.api :as api]
[ensorcel.conjure :as conjure]
[org.httpkit.server :refer [run-server]]))
...
(defn- get-all-widgets
[]
[{:id 0 :msg "I am a widget!"}
{:id 1 :msg "I am another widget!"}])
(defn- get-widget
[{id :id}]
[{:id id :msg "I'm probably not what you wanted.."}])
; create our service
(def widget-service
(conjure/service api/spellbook :widgets
:get get-widget
:get-all get-all-widgets))
; tie it all together into our app
(def app
(conjure/app api/spellbook {} ; options go here
widget-service))
(defn start-server
[]
(run-server app {:port 8000}))
Finally, in our frontend Clojurescript..
; example/client.cljs
(ns example.client
(:require [example.api :as api]
[ensorcel.conjure :as conjure :refer [call->]]))
(def client (conjure/client api/spellbook :widgets))
(call-> (client :get-all)
println) ; the extracted, properly typed list of
; widgets is passed to `println`
(call-> (client :get {:id 0})
println)
Ensorcel is also easy to customise through your API specification:
In your API definition:
...
(s/def ::my-query-argument ::types/string)
(s/def ::do-thing-request
(st/spec (s/keys :opt-un [::my-query-argument])))
...
; in your spellbook
:do-thing {:path "thing"
:method :GET
:query [:my-query-argument]
:args ::do-thing-request}
Now if we provide the optional query-argument
parameter in our client call,
it will be added as a query argument to our URL. The backend is unaffected.
(call-> (client :do-thing)) ; becomes <path>/thing
(call-> (client :do-thing {:my-query-argument "hi")) ; becomes <path>/thing?my-query-argument=hi
You can attach custom headers to your responses:
...
; in your spellbook
:my-endpoint {:path "endpoint"
:method :GET
:headers {"Content-Type" "text/html"}
...
}
This will replace the Content-Type
header (which defaults to application/json
)
with text/html
.
Normal successful responses will return 200 Success
. You can customise this
(for example when POST
ing a new resource):
...
(:require [ring.util.http-response :refer [created]] ;using http-response for example
...
...
; in your spellbook
:new {:path "new-thing"
:method :POST
:response created
...
}
Backend function definitions can have zero to two arguments.
(defn endpoint-zero
[] ; no arguments
...)
(defn endpoint-one
[args] ; argument map will be provided
...)
(defn endpoint-two
[args request] ; the full ring request will also be provided
...) ; containing cookies, headers etc.
- Automatic spec checks on inputs and outputs
- Minimal fussing with infrastructure details
- API definition as data, easy to see and track changes
- Dead simple to set up and get started!
Ensorcel is currently a work in progress, and as such has not yet been tested in production. Examples of things that I haven't tried yet:
- HTTPS support
- Working with NGINX etc.
- Probably a myriad of other things
However, Ensorcel uses a minimal amount of magic, so it should be trivial to extend in the standard ways.
Please feel free to submit FRs, PRs and comments!
Copyright © 2018 efisef
Distributed under the Eclipse Public License version 1.0