Fulcro uses EQL (EDN Query Language) over Transit protocol for all server communication. Single API endpoint processes both queries and mutations.
(ns app.server
(:require
[app.parser :refer [api-parser]]
[org.httpkit.server :as http]
[com.fulcrologic.fulcro.server.api-middleware :as server]
[ring.middleware.content-type :refer [wrap-content-type]]
[ring.middleware.resource :refer [wrap-resource]]))
(def middleware
(-> not-found-handler
(server/wrap-api {:uri "/api" :parser api-parser})
(server/wrap-transit-params)
(server/wrap-transit-response)
(wrap-resource "public")
wrap-content-type))
(defn start []
(http/run-server middleware {:port 3000}))
(ns app.parser
(:require
[com.wsscode.pathom.core :as p]
[com.wsscode.pathom.connect :as pc]))
(def pathom-parser
(p/parser {::p/env {::p/reader [p/map-reader pc/reader2 pc/ident-reader]}
::p/mutate pc/mutate
::p/plugins [(pc/connect-plugin {::pc/register resolvers})
p/error-handler-plugin
(p/post-process-parser-plugin p/elide-not-found)]}))
(defn api-parser [query]
(pathom-parser {} query))
(pc/defresolver person-resolver [env {:person/keys [id]}]
{::pc/input #{:person/id} ; Required inputs
::pc/output [:person/name :person/age]} ; Available outputs
(get-person-from-database id))
::pc/input
: Set of required input keys::pc/output
: EQL describing available outputs;; Entity resolver
(pc/defresolver person-resolver [env {:person/keys [id]}]
{::pc/input #{:person/id}
::pc/output [:person/name :person/age]}
(get people-table id))
;; Collection resolver
(pc/defresolver list-resolver [env {:list/keys [id]}]
{::pc/input #{:list/id}
::pc/output [:list/label {:list/people [:person/id]}]}
(when-let [list (get list-table id)]
(assoc list :list/people (mapv (fn [id] {:person/id id}) (:list/people list)))))
;; Root resolver (no inputs)
(pc/defresolver friends-resolver [env input]
{::pc/output [{:friends [:list/id]}]}
{:friends {:list/id :friends}})
EQL supports ident-based queries for specific entities:
[{[:person/id 1] [:person/name]}]
=> {[:person/id 1] {:person/name "Sally"}}
(ns app.application
(:require
[com.fulcrologic.fulcro.application :as app]
[com.fulcrologic.fulcro.networking.http-remote :as http]))
(defonce app (app/fulcro-app
{:remotes {:remote (http/fulcro-http-remote {})}}))
(defonce app (app/fulcro-app
{:remotes {:api (http/fulcro-http-remote {:url "/api"})
:auth (http/fulcro-http-remote {:url "/auth"})
:files (http/fulcro-http-remote {:url "/files"})}}))
(ns app.mutations
(:require [com.wsscode.pathom.connect :as pc]))
(pc/defmutation delete-person [env {list-id :list/id person-id :person/id}]
{::pc/sym `delete-person} ; Optional symbol override
(log/info "Deleting person" person-id "from list" list-id)
(swap! list-table update list-id
update :list/people (fn [old-list]
(filterv #(not= person-id %) old-list))))
(pc/defmutation save-person [env {:person/keys [id name]}]
{::pc/sym `save-person}
(let [saved-person (save-to-database {:person/id id :person/name name})]
{:person/id id :person/name name :person/updated-at (js/Date.)}))
Server mutations can return data that gets auto-merged:
;; Client mutation with returning
(defmutation save-person [params]
(action [env] ...)
(remote [env] (m/returning Person)))
;; Server handles the query automatically
(pc/defmutation risky-operation [env params]
{::pc/sym `risky-operation}
(try
(dangerous-operation params)
(catch Exception e
{::p/error (str "Operation failed: " (.getMessage e))})))
(defmutation save-data [params]
(action [env] ...)
(remote [env] true)
(error-action [{:keys [error]}]
(log/error "Save failed:" error)))
(df/load! this :people Person
{:params {:limit 10 :offset 20 :filter "active"}})
(pc/defresolver people-resolver [env params]
{::pc/output [{:people [:person/id]}]}
(let [{:keys [limit offset filter]} params]
{:people (query-people :limit limit :offset offset :filter filter)}))
Pathom automatically batches resolver calls:
;; Client makes these calls
[{[:person/id 1] [:person/name]}
{[:person/id 2] [:person/name]}
{[:person/id 3] [:person/name]}]
;; Pathom can batch into single resolver call with multiple inputs
(pc/defresolver user-posts [env {:user/keys [id]}]
{::pc/input #{:user/id}
::pc/output [{:user/posts [:post/id]}]}
{:user/posts (get-posts-for-user id)})
(pc/defresolver post-details [env {:post/keys [id]}]
{::pc/input #{:post/id}
::pc/output [:post/title :post/content]}
(get-post-details id))
;; Pathom chains: user/id → user/posts → post details
;; Available at query root (like GraphQL root queries)
(pc/defresolver current-user [env input]
{::pc/output [{:current-user [:user/id]}]}
(when-let [user-id (get-session-user-id env)]
{:current-user {:user/id user-id}}))
;; Test resolvers directly
(app.parser/api-parser [{[:person/id 1] [:person/name]}])
=> {[:person/id 1] {:person/name "Sally"}}
Enable in Pathom parser for debugging:
{::p/plugins [...
p/trace-plugin ; Add this for query tracing
...]}
(ns user
(:require [clojure.tools.namespace.repl :refer [refresh]]))
(defn restart []
(stop-server)
(refresh :after 'user/start))
(pc/defresolver sensitive-data [env input]
{::pc/output [:sensitive/field]}
(when (authorized? env)
{:sensitive/field "secret"}))
(pc/defmutation admin-operation [env params]
{::pc/sym `admin-operation}
(when-not (admin? env)
(throw (ex-info "Unauthorized" {:type :unauthorized})))
(perform-admin-operation params))
(pc/defmutation create-user [env {:user/keys [name email] :as params}]
{::pc/sym `create-user}
(when-not (and (valid-name? name) (valid-email? email))
(throw (ex-info "Invalid parameters" {:type :validation-error})))
(create-user-in-db params))
Can 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 |