A RAD server provides an EQL API endpoint (typically /api) using Pathom for query resolution. The server setup
involves: configuration files, form save/delete middleware, a Pathom parser with RAD plugins, and Ring middleware. RAD
integrates with database adapters and auto-generates resolvers from attributes.
From DevelopersGuide.adoc:606-607:
"A RAD server must have an EQL API endpoint, typically at
/api. This is standard Fulcro stuff..."
Setup Stack:
From DevelopersGuide.adoc:609-646:
Use Fulcro's EDN-based config system with mount for state management.
(ns com.example.components.config
(:require
[com.fulcrologic.fulcro.server.config :as fulcro-config]
[com.example.lib.logging :as logging]
[mount.core :refer [defstate args]]
[taoensso.timbre :as log]
[com.example.model :as model]
[com.fulcrologic.rad.attributes :as attr]))
(defstate config
"The overrides option in args is for overriding configuration in tests."
:start (let [{:keys [config overrides]
:or {config "config/dev.edn"}} (args)
loaded-config (merge (fulcro-config/load-config {:config-path config})
overrides)]
(log/info "Loading config" config)
;; Set up Timbre logging, etc.
(logging/configure-logging! loaded-config)
loaded-config))
From DevelopersGuide.adoc:640-646:
config/defaults.edn - Base configuration
config/dev.edn - Development overrides
config/prod.edn - Production overrides
{:my-database {:host "localhost"
:port 5432}
:my-config-value 42}
Fulcro's config system merges files and supports environment variables. See Fulcro Developer's Guide for details.
From DevelopersGuide.adoc:648-693:
Form middleware hooks into RAD's I/O subsystem using a Ring-like pattern. Two middlewares are required: save and * delete*.
From DevelopersGuide.adoc:656-677:
(ns com.example.components.save-middleware
(:require
[com.fulcrologic.rad.middleware.save-middleware :as r.s.middleware]
[com.fulcrologic.rad.database-adapters.datomic :as datomic]
[com.example.components.datomic :refer [datomic-connections]]
[com.fulcrologic.rad.blob :as blob]
[com.example.model :as model]))
(def middleware
(->
(datomic/wrap-datomic-save)
(r.s.middleware/wrap-rewrite-values)))
Purpose: Receives Pathom mutation env augmented with ::form/params and handles database saves.
From DevelopersGuide.adoc:677:
"This is also the best place to put things like security and schema validation enforcement for save."
Composition:
wrap-datomic-save)wrap-rewrite-values)From DevelopersGuide.adoc:679-692:
(ns com.example.components.delete-middleware
(:require
[com.fulcrologic.rad.database-adapters.datomic :as datomic]))
(def middleware
(datomic/wrap-datomic-delete))
Purpose: Invoked during entity deletion requests.
From DevelopersGuide.adoc:692:
"Of course you'll also want to add things to this middleware to check security and such."
From DevelopersGuide.adoc:694-751:
The Pathom parser processes EQL queries. RAD auto-generates resolvers from attributes and database adapters provide additional resolvers.
From DevelopersGuide.adoc:699-714:
Generate resolvers from attributes with ::pc/resolve keys:
(ns com.example.components.auto-resolvers
(:require
[com.example.model :refer [all-attributes]]
[mount.core :refer [defstate]]
[com.fulcrologic.rad.resolvers :as res]
[taoensso.timbre :as log]))
(defstate automatic-resolvers
:start
(vec (res/generate-resolvers all-attributes)))
What This Does:
all-attributes for those with ::pc/resolve functionsFrom DevelopersGuide.adoc:716-747:
(ns com.example.components.parser
(:require
[com.example.components.auto-resolvers :refer [automatic-resolvers]]
[com.example.components.config :refer [config]]
[com.example.components.datomic :refer [datomic-connections]]
[com.example.components.delete-middleware :as delete]
[com.example.components.save-middleware :as save]
[com.example.model :refer [all-attributes]]
[com.example.model.account :as account]
[com.fulcrologic.rad.attributes :as attr]
[com.fulcrologic.rad.blob :as blob]
[com.fulcrologic.rad.database-adapters.datomic :as datomic]
[com.fulcrologic.rad.form :as form]
[com.fulcrologic.rad.pathom :as pathom]
[mount.core :refer [defstate]]))
(defstate parser
:start
(pathom/new-parser config
;; Plugins
[(attr/pathom-plugin all-attributes)
(form/pathom-plugin save/middleware delete/middleware)
(datomic/pathom-plugin (fn [env] {:production (:main datomic-connections)}))]
;; Resolvers
[automatic-resolvers
form/resolvers
account/resolvers
; ... your other resolvers ...
]))
From DevelopersGuide.adoc:749-752:
"The supplied constructor for pathom parsers is not required, you can use the source to see what it includes by default. The RAD parser construction function takes a Fulcro-style server config map, a vector of plugins, and a vector of resolvers (the resolvers can be nested sequences)."
attr/pathom-plugin (from DevelopersGuide.adoc:740):
"required to populate standard things in the parsing env"
Provides RAD attribute data to the Pathom env.
form/pathom-plugin (from DevelopersGuide.adoc:741):
"installs form save/delete middleware"
Registers save/delete mutations using the provided middleware.
Database Adapter Plugin (from DevelopersGuide.adoc:742):
"db-specific adapter"
Example: datomic/pathom-plugin - provides database connection lookup and generates resolvers from schema.
automatic-resolvers: Generated from attributes
form/resolvers: Predefined resolvers for form save/delete
Custom resolvers: Your hand-written resolvers
From DevelopersGuide.adoc:754-798:
Wrap the Pathom parser in a Ring-based HTTP server.
From DevelopersGuide.adoc:759-794:
(ns com.example.components.ring-middleware
(:require
[com.fulcrologic.fulcro.server.api-middleware :as server]
[mount.core :refer [defstate]]
[ring.middleware.defaults :refer [wrap-defaults]]
[com.example.components.config :as config]
[com.example.components.parser :as parser]
[taoensso.timbre :as log]
[ring.util.response :as resp]
[clojure.string :as str]))
(defn wrap-api [handler uri]
(fn [request]
(if (= uri (:uri request))
(server/handle-api-request (:transit-params request)
(fn [query]
(parser/parser {:ring/request request}
query)))
(handler request))))
(def not-found-handler
(fn [req]
{:status 404
:body {}}))
(defstate middleware
:start
(let [defaults-config (:ring.middleware/defaults-config config/config)]
(-> not-found-handler
(wrap-api "/api")
(server/wrap-transit-params {})
(server/wrap-transit-response {})
(wrap-defaults defaults-config))))
Middleware Stack (bottom to top):
not-found-handler - Base 404 responsewrap-api - Routes /api to Pathom parserwrap-transit-params - Decodes Transit request paramswrap-transit-response - Encodes Transit responsewrap-defaults - Ring security defaults (CSRF, etc.)From DevelopersGuide.adoc:796-798:
"See the RAD Demo project for the various extra bits you might want to define around your middleware. You will need to add middleware to support things like file upload, CSRF protection, etc."
(defstate datomic-connections
:start
{:production (d/connect production-uri)
:analytics (d/connect analytics-uri)})
(defstate parser
:start
(pathom/new-parser config
[(datomic/pathom-plugin (fn [env]
{:production (:production datomic-connections)
:analytics (:analytics datomic-connections)}))]
[automatic-resolvers ...]))
(defn wrap-security-check [handler]
(fn [env]
(let [{:keys [ring/request]} env
user (get-in request [:session :user])]
(if (authorized? user (get-in env [::form/params]))
(handler env)
{:error "Unauthorized"}))))
(def middleware
(->
wrap-security-check
(datomic/wrap-datomic-save)
(r.s.middleware/wrap-rewrite-values)))
(defn wrap-save-logging [handler]
(fn [env]
(log/info "Saving:" (get-in env [::form/params ::form/delta]))
(let [result (handler env)]
(log/info "Save result:" result)
result)))
(defstate parser
:start
(pathom/new-parser config
[(attr/pathom-plugin all-attributes)
(form/pathom-plugin save/middleware delete/middleware)
(datomic/pathom-plugin db-connection-fn)
;; Add custom env data
{:env {::my-app/feature-flags (load-feature-flags config)}}]
[automatic-resolvers ...]))
From DevelopersGuide.adoc:749-750:
"The supplied constructor for pathom parsers is not required, you can use the source to see what it includes by default."
You can build the Pathom parser manually if you need more control. pathom/new-parser is a convenience.
Pathom auto-connects resolvers based on inputs/outputs. The order in the resolver vector doesn't affect query resolution.
Form middleware uses Ring-style composition. The rightmost middleware runs first:
(-> handler-fn
wrap-outer ; runs last
wrap-middle ; runs second
wrap-inner) ; runs first
Using mount, the config is a stateful component. It's loaded once at startup. To reload config in development, restart
the mount state.
From DevelopersGuide.adoc:677, 692:
RAD provides hooks for security (middleware), but you must implement authorization checks. Never trust client data - validate and authorize in middleware.
RAD uses Transit for serialization. The Ring middleware wraps requests/responses with wrap-transit-params and
wrap-transit-response.
The parser is called with {:ring/request request}, giving resolvers access to sessions, headers, etc.:
(defresolver current-user-resolver [{:keys [ring/request]} _]
{::pc/output [:user/current]}
{:user/current (get-in request [:session :user])})
config defstate starts)datomic-connections starts)automatic-resolvers starts)parser starts with plugins + resolvers)middleware starts)http-kit, jetty)form/save! and how it uses server middleware::pc/resolve for virtual data/api endpointCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |