Liking cljdoc? Tell your friends :D

Clojars Project cljdoc badge

The goal of this library is to facilitate using data described by realms in web applications contexts, i.e. when transpoting data via http.

Latest Version

API Docs

Introduction

There are two main facilities included in this library. One part is for defining translations of your data in Transit/EDN form. The second part is to simplify defining and using server endpoints used by fat web clients.

Transit format

Based on active-data-translate, there is a "batteries included" transit format that can translate between most of your data and a transit compatible form automatically:

active.data.http.formats.transit/transit-format

Use this only if the coupling introduced by that is not an issue, or can be mitigated by other means.

The coupling can for example be

  • between producer and consumer code of the transit values, if they are developed independently, or

  • between past and future versions of the code, if transit values are written to some kind of databases, or if producer and consumer can have different versions of the code.

In those situations, you should define explicit translations for the realms that are defined in your code and thus are subject to potential change over time. You can use

active.data.http.formats.transit/basic-formatters

as a base for that, which includes formatters for things that are unlikely to change, like formatting strings as strings, numbers as numbers, and sequences as vectors, etc.

Reitit coercion

A reitit coercion based on realms and a format is available in the active.data.http.reitit namespace. This allows you to declare parameters and response bodies as realms, and have them automatically translated via the format.

(require '[active.data.http.reitit :as http-reitit])

(def-record user [...])

(def my-format ...)

["/api/public/get-user/:id"
 {:get {:handler (fn [request]
                   {:status 200
                    :body (db/get-user-from-db (:id (:path (:parameters request))))})
        :parameters {:path {:id realm/integer}}
        :responses {200 {:body user}}
        :coercion (http-reitit/realm-coercion my-format)}}]

See active-data-translate on how to define formats.

Note that to get this working you'll need a few middlewares for these routes. Namely the coersion middlewares, parameters-middleware and some transit middleware like muuntaja:

{:middleware 
 [reitit.ring.coercion/coerce-exceptions-middleware
  reitit.ring.coercion/coerce-request-middleware
  reitit.ring.coercion/coerce-response-middleware
  reitit.ring.middleware.parameters/parameters-middlware]
 :muuntaja muuntaja.core/instance}

See Reitit documentation for more details on how this can be set up.

RPCs

To make endpoints for a webclient served by the same server and the usage of them even easier to set up, there is an "RPC"-like facility included in this library.

It is intended for "internal apis" and shared code (cljc) only, where the coupling between the server and the client code is not an issue.

To use this, you would first define a so called context and the api in some shared code (cljc file):

(require '[active.data.http.rpc :as rpc #?(:cljs :include-macros true)])

(def internal-api (rpc/context "/api/internal"))

(rpc/defn-rpc get-user! internal-api :- user [id :- realm/integer])

And define implementations for those RPCs in some server code, for example with reitit:

(require '[active.data.http.rpc.reitit :as rpc-reitit])

(def routes
  (rpc-reitit/context-routes
    internal-api
    [(rpc-reitit/impl get-user! db/get-user-from-db]))

And finally to "call" those RPCs from the client side, for example with the reacl-c library, you would modify the shared api code to add a "caller" to the context like so:

(require '[active.data.http.rpc :as rpc #?(:cljs :include-macros true)])
#?(:cljs (require '[active.data.http.rpc.reacl-c :as rpc-reacl-c]))

(def internal-api (-> (rpc/context "/api/internal")
                      #?(:cljs (rpc/set-context-caller rpc-reacl-c/caller))))

Which then enables you to call the names defined by defn-rpc as a function to get a reacl-c request, which can then be executed in various ways:

(require '[reacl-c-basics.ajax :as ajax])

(ajax/fetch (get-user! 4711))

See reacl-c-basics for more details on that.

Signals

Signals are (small) pieces of data sent by the server to your fat webclient, for example to inform it that new data is available. It is not recommended to send that data itself, but to fetch that data separately after receiving a signal to do so.

To add that to your shared internal api, you can write something like this:

(require '[active.data.http.signals :as signals])

(def wakeup-signal ::wakeup)

(defonce signals
  (signals/context "/api/internal-signals"
                   (realm/enum wakeup-signal)))

Note: the context has some internal state, so it is recommended to use defonce for it.

To get the necessary reitit routes use

(require '[active.data.http.signals.reitit :as signals.reitit])

(def routes
  (signals.reitit/context-routes api/signals))

To actually signal something

(require '[active.data.http.signals :as signals])

(signals/broadcast! api/signals api/wakeup-signal)

And to receive signals when using reacl-c:

(require '[active.data.http.signals.reacl-c :as signals])

;; an item that emits the current timestamp when a wakeup is received
(signals/receive api/signals api/wakeup-signals)

Note that signals use websockets under the hood, so you need a recent version of ring and a ring-adapter that supports websockets.

License

Copyright © 2024 Active Group GmbH

This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.

This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close