{"id":13760525,"url":"https://github.com/efisef/ensorcel","last_synced_at":"2025-04-09T22:40:59.042Z","repository":{"id":57716557,"uuid":"138727127","full_name":"efisef/ensorcel","owner":"efisef","description":"Opinionated clj(s) APIs as data","archived":false,"fork":false,"pushed_at":"2018-10-17T09:55:53.000Z","size":3007,"stargazers_count":17,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-24T00:38:08.103Z","etag":null,"topics":["bidi","clj","cljs","clojure","clojurescript"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/efisef.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-26T11:15:19.000Z","updated_at":"2019-08-15T03:47:32.000Z","dependencies_parsed_at":"2022-08-26T09:30:53.076Z","dependency_job_id":null,"html_url":"https://github.com/efisef/ensorcel","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efisef%2Fensorcel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efisef%2Fensorcel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efisef%2Fensorcel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/efisef%2Fensorcel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/efisef","download_url":"https://codeload.github.com/efisef/ensorcel/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248124848,"owners_count":21051757,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bidi","clj","cljs","clojure","clojurescript"],"created_at":"2024-08-03T13:01:12.185Z","updated_at":"2025-04-09T22:40:59.018Z","avatar_url":"https://github.com/efisef.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# ensorcel\n\nA simpler way to have fullstack Clojure[script] applications communicate, defining\nAPIs as _data_.\n\n[![Clojars](https://img.shields.io/clojars/v/efisef/ensorcel.svg)](https://clojars.org/efisef/ensorcel)\n[![CircleCI](https://circleci.com/gh/efisef/ensorcel.svg?style=svg)](https://circleci.com/gh/efisef/ensorcel)\n\nFRs, PRs and comments welcome!\n\n## Importing\n\n```clojure\n(:require [ensorcel.conjure :as conjure])\n```\n\n## Getting Started\n\nEnsorcel is easy to get starting with - there are 3 steps:\n\n### Define Your API\n\nLet's define a widget retrieval service that has a get and get-all method..\n\n```clojure\n; example/api.cljc\n\n(ns example.api\n  (:require [ensorcel.types :as t]\n            [spec-tools.core :as st]\n            #?(:clj  [clojure.spec.alpha :as s]\n               :cljs [cljs.spec.alpha :as s :include-macros true])))\n\n(s/def :widget/id  ::t/integer)\n(s/def :widget/msg ::t/string)\n\n; we wrap our key specs into spec-tools so that we can strip\n; any extra keys\n(s/def ::widget\n  (st/spec (s/keys :req-un [:widget/id :widget/msg])))\n\n(s/def ::get-widget-request\n  (st/spec (s/keys :req-un [:widget/id])))\n\n(def spellbook\n  {:version \"1\"\n   :services {:widgets {:path \"widgets\"\n                        :endpoints {:get-all {:path \"\"\n                                              :method :GET\n                                              :returns (s/* ::widget)}\n                                    :get     {:path [:id]\n                                              :method :GET\n                                              :args ::get-widget-request\n                                              :returns ::widget}}}}})\n```\n\nA spellbook defines one or more services, each of which has one or more endpoints.\nIn the example above, we define two endpoints located under `\u003caddr\u003e:\u003cport\u003e/api/widgets/`,\none at `widgets/` and one at `widgets/\u003cid\u003e`.\n\n### Define Your Server\n\nNext up, we define our backend:\n\n```clojure\n; example/server.clj\n\n(ns example.server\n  (:require [example.api :as api]\n            [ensorcel.conjure :as conjure]\n            [org.httpkit.server :refer [run-server]]))\n\n...\n\n(defn- get-all-widgets\n  []\n  [{:id 0 :msg \"I am a widget!\"}\n   {:id 1 :msg \"I am another widget!\"}])\n\n(defn- get-widget\n  [{id :id}]\n  [{:id id :msg \"I'm probably not what you wanted..\"}])\n\n; create our service\n(def widget-service\n  (conjure/service api/spellbook :widgets\n                   :get     get-widget\n                   :get-all get-all-widgets))\n\n; tie it all together into our app\n(def app\n  (conjure/app api/spellbook {} ; options go here\n               widget-service))\n\n(defn start-server\n  []\n  (run-server app {:port 8000}))\n```\n\n### Define Your Client\n\nFinally, in our frontend Clojurescript..\n\n```clojure\n; example/client.cljs\n\n(ns example.client\n  (:require [example.api :as api]\n            [ensorcel.conjure :as conjure :refer [call-\u003e]]))\n\n(def client (conjure/client api/spellbook :widgets))\n\n(call-\u003e (client :get-all)\n        println)          ; the extracted, properly typed list of\n                          ; widgets is passed to `println`\n\n(call-\u003e (client :get {:id 0})\n        println)\n```\n\n## Other API Features\n\nEnsorcel is also easy to customise through your API specification:\n\n#### Query Arguments\n\nIn your API definition:\n\n```clojure\n...\n\n(s/def ::my-query-argument ::types/string)\n(s/def ::do-thing-request\n  (st/spec (s/keys :opt-un [::my-query-argument])))\n\n...\n  ; in your spellbook\n  :do-thing {:path   \"thing\"\n             :method :GET\n             :query  [:my-query-argument]\n             :args   ::do-thing-request}\n```\n\nNow if we provide the optional `query-argument` parameter in our client call,\nit will be added as a query argument to our URL. The backend is unaffected.\n\n```clojure\n(call-\u003e (client :do-thing)) ; becomes \u003cpath\u003e/thing\n(call-\u003e (client :do-thing {:my-query-argument \"hi\")) ; becomes \u003cpath\u003e/thing?my-query-argument=hi\n```\n\n#### Custom Headers\n\nYou can attach custom headers to your responses:\n\n```clojure\n...\n  ; in your spellbook\n  :my-endpoint {:path \"endpoint\"\n                :method :GET\n                :headers {\"Content-Type\" \"text/html\"}\n                ...\n               }\n```\n\nThis will replace the `Content-Type` header (which defaults to `application/json`)\nwith `text/html`.\n\n#### Custom Responses\n\nNormal successful responses will return `200 Success`. You can customise this\n(for example when `POST`ing a new resource):\n\n```clojure\n...\n  (:require [ring.util.http-response :refer [created]] ;using http-response for example\n    ...\n...\n  ; in your spellbook\n  :new {:path \"new-thing\"\n        :method :POST\n        :response created\n        ...\n        }\n```\n\n#### Accessing The Request\n\nBackend function definitions can have zero to two arguments.\n\n```clojure\n(defn endpoint-zero\n  [] ; no arguments\n  ...)\n\n(defn endpoint-one\n  [args] ; argument map will be provided\n  ...)\n\n(defn endpoint-two\n  [args request] ; the full ring request will also be provided\n  ...)           ; containing cookies, headers etc.\n\n```\n\n## Why Use Ensorcel\n\n- Automatic spec checks on inputs and outputs\n- Minimal fussing with infrastructure details\n- API definition as data, easy to see and track changes\n- Dead simple to set up and get started!\n\n## Limitations\n\nEnsorcel is currently a work in progress, and as such has not yet been tested in production.\nExamples of things that I haven't tried yet:\n\n- HTTPS support\n- Working with NGINX etc.\n- Probably a myriad of other things\n\nHowever, Ensorcel uses a _minimal amount of magic_, so it should be trivial to extend in\nthe standard ways.\n\nPlease feel free to submit FRs, PRs and comments!\n\n## License\n\nCopyright © 2018 efisef\n\nDistributed under the Eclipse Public License version 1.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fefisef%2Fensorcel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fefisef%2Fensorcel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fefisef%2Fensorcel/lists"}