Fork me on GitHub

Hello. I am trying to have a REST interface for a Fulcro client app. This is described in section 21.5.1 of the book but unfortunately it is for Fulcro 2. As anyone managed to get it working for Fulcro 3? I have tried with the book code but so far I get a compilation error: The required namespace "" is not available, it was required by "com/wsscode/pathom/fulcro/network.cljs" Any pointers on how I can interact with a REST api without Fulcro backend support?

Chris O’Donnell11:05:49

Here's an example of a custom, client-side fulcro remote that hits a graphql server: Assuming you've built a client-side pathom parser to hit REST endpoints, basically the same thing should work for you.


@U0DUNNKT2 Thank you. I’ll try to adapt this to my problem.

Chris O’Donnell16:05:42

Good luck! Please feel free to ask for help if you get stuck.


Hi @U0DUNNKT2. As suggested, I have defined the same exact remote as yours and I have called it ‘rest-remote’. I have also defined pathom resolvers and when I load data through these resolvers, everything works as expected. E.g.: (df/load! SPA :the-datoms root/Datoms {:remote :rest-remote}) Where I am stuck is with mutations. I have defined a client mutation as follows (n otice the ‘rest-remote’ section). (defmutation update-datoms "Client Mutation" [{:datoms/keys [datom]}] (action [{:keys [state]}] ... (rest-remote [env] true)) Then I also created a corresponding Pathom mutation expecting that it would get invoked and that I could inject my REST interactions inside it. (pc/defmutation update-datoms [env params] (log/info "In client-mutations - update-datoms !!!!!") ;; <REST Call here> ) But this does not seem to work. What I see actually is a weird behaviour: when I trigger the fulcro mutation, the pathom mutation is not invoked. BUT after I reload the app, then I see that the pathom mutation is triggered!? May be I misunderstood something about Fuclro and Pathom as I am still learning. But is this a good way to integrate REST client side? And if so, how do I get the Pathom mutation correctly invoked?

Chris O’Donnell10:05:55

@U0DP57ZT9 At a high level you've set things up exactly right! Perhaps you're having an issue with your pathom mutation because it's missing a config map?


Ok. At least i know i am on the right track. I’ll check the link. Thank you very much again.


@U0DUNNKT2 I have the below and many variants of it but nothing works. For instance below I have put the Fuclro and the Pathom mutations in the same name space (just to be sure) but still the pathom mutation is never called. If by any chance you see something, please let me know. ;; pathom.cljs (def all-resolvers [datoms/send-message]) (def parser (p/parallel-parser {::p/mutate pc/mutate-async ::p/env {::p/reader [p/map-reader pc/parallel-reader pc/open-ident-reader p/env-placeholder-reader] ::p/placeholder-prefixes #{">"} } ::p/plugins [(pc/connect-plugin {::pc/register all-resolvers}) p/error-handler-plugin p/request-cache-plugin p/trace-plugin]})) ;; datoms.cljs (defmutation update-datoms "Client Mutation: Replaces the vector which contains all the datoms" [{:datoms/keys [datom]}] (action [{:keys [state]}] ;; ... (ok-action [env] (log/info "OK actionn")) (error-action [env] (log/info "Error action")) (rest-remote [env] (eql/query->ast1 [(send-message {:message/text "hello"})])))` (pc/defmutation send-message [env {:keys [message/text]}] {::pc/sym 'send-message ::pc/params [:message/text] ::pc/output [:message/id :message/text]} (log/info "In client-mutations - some-other-thing !!!!!") {:message/id 123 :message/text text})

Chris O’Donnell15:05:00

Your pathom mutation sym should be quoted with a backtick.


Ok, let me try.

Chris O’Donnell15:05:40

Or you can omit ::pc/sym. The default should work for you in this case.


Quoting works!!! Thanks!!!


Without the ::pc/sym as well. Nice.


If you don’t mind just two questions… Do I have to keep both mutations in the same namespace? And ::pc/params ::pc/output are important for getting the dispatch to the pathom mutation to work correctly, right?

Chris O’Donnell15:05:21

It's convenient for them to be in the same namespace and be defined with the same symbol, since that allows you to just have (rest-remote [_] true) in your fulcro mutation. They don't have to be, though. The mutation fulcro sends to pathom via rest-remote just has to match the pathom mutation's sym.

Chris O’Donnell16:05:08

IIRC ::pc/params is optional--just documentation, really. ::pc/output is used for mutation joins I think.


All right, thank you so much.

Chris O’Donnell16:05:29

Actually, does it work when they have the same symbol? One might overwrite the other.

Chris O’Donnell16:05:36

I'm not actually sure.


Ya I faced that problem and the answer is no it does not work.


That’s why I defined the pathom mutation with another name.


Hey, to any that are interested, we’re doing our weekly catchup here:


Hi I’m stuck again :face_palm:. I have a state machine that should handle logging in/logging out/checking credentials/checking “session” (existing jwt). I got everything else working, but now that the JWT is expired I’m getting 401 on initial state load, but neither of the “post-event” functions are called. If I manually remove the expired token, everything works again. I think my problem is that I don’t know how to process logic if load returns a 401

    {::uism/target-states #{:state/logged-in :state/logged-out}
     ::uism/events        {::uism/started  {::uism/handler
                                            (fn [env]
                                              (-> env
                                                  (uism/assoc-aliased :error "")
                                                  (uism/load :session/current-user :actor/current-user
                                                             {::uism/ok-event    :event/complete
                                                              ::uism/error-event :event/failed})))}
                           :event/failed   {::uism/target-state :state/logged-out
                                            ::uism/handler      (fn [env] (println "FAILED") env)}
                           :event/complete {::uism/target-states #{:state/logged-in :state/logged-out}
                                            ::uism/handler       #(process-session-result % "")}}}
possible relevant logs
core.cljs:159 DEBUG [com.fulcrologic.fulcro.ui-state-machines:288] - Updating value for  :session-machine alias :error -> 
core.cljs:159 DEBUG [com.fulcrologic.fulcro.ui-state-machines:536] - Starting load of :session/current-user

POST  401 (Unauthorized)
WARN [com.fulcrologic.fulcro.networking.http-remote:182] - Transit decode failed!
DEBUG [] - Running load failure logic.
core.cljs:159 DEBUG [com.fulcrologic.fulcro.ui-state-machines:543] - Handling load error nil : nil
core.cljs:159 WARN [com.fulcrologic.fulcro.ui-state-machines:549] - A fallback occurred, but no event was defined by the client. Sending generic ::uism/load-error event.


@koivistoinen.tuomas Hm. The “transit decode failed” message in your example means that the response itself wasn’t understood (it tried to transit decode it)…what version of Fulcro? What should have happened is it fired your error-event, but in this case it seems to think you didn’t define one, so it sent a generic error event


this is likely a bug in Fulcro, so I’d make sure you’re up to date


also, have you overridden :remote-error? on the application? If that doesn’t return the correct thing it can confuse the internals.


basically, if it isn’t a network error, and the body contains something, then it expects it to contain transit-encoded edn. If it does not, then it turns the response into a status code 417 internally (expectation failed). I.e. Fulcro’s remote expects your server to return EDN from /api…even when you use an HTTP error status code. Your login code is sending an error code but probably a string body, meaning that Fulcro itself will not “understand” the response in any meaningful way. The error handling in Fulcro is meant to be transit-based. An application-level error should NOT use REST-style errors. They should return values that can be merged and understood as informative errors. You don’t have to do that, though…just a tip. That is also why you can override the remote-error? definition: you will often want to define a shape for result values that indicate errors at a higher level…but you can also use status codes….but in either case you should consider responding with transit. Doing what you’re doing is technically “ok”, as long as your remote-error? function still sees it as an error. But, all that said, I still would expect the default internals to have fired your UISM event you set in load


Ah….the error message from line 543: nil : nil. The first is supposed to be the asm-id and the second the actual event to send…why were those nil?


So, either that is a bug, or perhaps you started the machine incorrectly and gave it nil as an id?



:deps      {org.clojure/clojure                 {:mvn/version "1.10.1"}
            com.fulcrologic/fulcro              {:mvn/version "3.0.9"}
            com.fulcrologic/semantic-ui-wrapper {:mvn/version "1.0.0"}
            com.wsscode/pathom                  {:mvn/version "2.2.31"}
            ring/ring-core                      {:mvn/version "1.8.0"}
            ovotech/ring-jwt                    {:mvn/version "1.2.5"}
            yogsototh/clj-jwt                   {:mvn/version "0.3.0"}
            com.taoensso/timbre                 {:mvn/version "4.10.0"}
            http-kit                            {:mvn/version "2.3.0"}
            com.cognitect/anomalies             {:mvn/version "0.1.12"}
            com.datomic/client-cloud            {:mvn/version "0.8.83"}
            com.datomic/ion                     {:mvn/version "0.9.35"}
            clj-commons/pushy                   {:mvn/version "0.3.10"}}
I haven’t overriden :remote-error. I read that 200 should be ok and anything else error.
(def authenticated-request-middleware
    (fn [request]
      (assoc-in request [:headers "Authorization"] (str "Bearer " @jwt-token)))

(defonce app (app/fulcro-app
               {:remotes {:remote (http/fulcro-http-remote
                                    {:url                   "/datomic/api"
                                     :request-middleware authenticated-request-middleware})}}))
The response 401 is coming from ring-jwt middleware because of an expired jwt. Eventually I really should look for another option or make my own, because erroring out and then doing something isn’t really ideal in the long run. I was wondering about the state machine nil id too. I tweaked around with my application init order but couldn’t get a result that worked in that situation (works fine without 401's). Don’t judge me for having stuff poorly organized. It’s getting better as I go
(ns app.client
    [app.application :as app]
    [app.ui2 :as ui]
    [com.fulcrologic.fulcro.application :as application]
    [ :as df]
    [com.fulcrologic.fulcro.routing.dynamic-routing :as dr]
    [com.fulcrologic.fulcro.ui-state-machines :as uism]))

(defn ^:export init []
  "On shadow-cljs init"
  (application/mount! app/app ui/Root "app")
  (dr/initialize! app/app)
  (uism/begin! app/app ui/session-machine :session-machine
               {:actor/login-form   ui/Login
                :actor/current-user ui/CurrentUser})
  (js/console.log "Loaded"))

(defn ^:export refresh []
  "On shadow-cljs hot reload"
  (application/mount! app/app ui/Root "app")
  (js/console.log "Hot reload"))
Thanks for the response, this is a lot of new useful info for me. I’ll do more research based on that and get back to you.


So @koivistoinen.tuomas I would suggest upgrade to latest version. I seem to remember some historical bugs around this, but don’t remember when they were fixed. You’re quite a ways behind

👍 4

After upgrade, everything works as expected 👌 I should pay more attention to deps versions. Learned a lot again thanks