Fork me on GitHub
#fulcro
<
2024-03-11
>
Rei08:03:46

Hey there, What is the recommended way of sending REST requests to some endpoints (not Pathom, just normal endpoints) and retrieving responses the Fulcro-way, i.e. doing so via transactions and inserting the response's data into the local state? There's a section on creating a custom remote in the book but tbh I'm a bit lost here. Any help or advice would be highly appreciated. Thank you.

tony.kay18:03:19

The recommend way is to make a new remote. The pluggable middleware of http-remote will let you do what you want. Mainly, you need to convert the request from Fulcro into the GET request (at the middleware layer) and the rewrite the response so it matches the naming/shape of the query you’re using for normalization. For example, here’s some middleware for making a GET request against a CDN that holds EDN files that already have the shape I want:

(defn handle-get-response [{:keys [status-code original-transaction body] :as resp}]
  (let [k (some-> original-transaction eql/query->ast1 :dispatch-key)]
    (if (= 200 status-code)
      (assoc resp :body {k (edn/read-string body)})
      (assoc resp :body {}))))

(defn handle-get-request [{:keys [url body] :as req}]
  (if-let [url (some-> body eql/query->ast1 :params :url)]
    (assoc req :url url :method :get :body "" :response-type "text")
    (throw (ex-info "Invalid GET request. No URL" {:body body}))))

(def get-response-mw (fn [resp] (handle-get-response resp)))
(def get-request-mw (fn [req] (handle-get-request req)))

tony.kay18:03:56

where the URL can be passed in as a parameter to load

tony.kay18:03:11

(net/fulcro-http-remote {:url                 "ignored"
                                                        :response-middleware get-response-mw
                                                        :request-middleware  get-request-mw})

tony.kay18:03:06

so I just put that in :remotes option as :get, and then issue loads like this:

(df/load! @app-atom :all-interests Interest
    {:remote :get
     :params {:url ""}})
and in the EDN file I’ve got a vector of maps that have the k/v pairs for the query of Interest

tony.kay18:03:39

so if it was a REST endpoint, you’d need to convert the JSON to EDN and rename the keys

tony.kay18:03:43

that’s about it

tony.kay18:03:02

If it were me, I’d follow the RAD modelling. I’d make up options for the RAD attributes that specify the incoming JSON name of the key, then you can automate the middleware using that model to know how to rename the keys as they come in.

tony.kay18:03:21

But in structure at least, you just need to see that submitting an EQL query like: [{:all-interests [:interest/name :interest/category]}] whose response from the GET is:

[{"name": "foo", "category": "bar"}, ...]
needs to be transformed into:
{:all-interests [{:interest/name "foo" :interest/category "bar"} ...]}
by the middleware, and then all of the mechanisms of Fulcro will work.

Jakub Holý (HolyJak)19:03:45

Notice that Pathom 3 can be run in the browser and I believe has some nice integration with Rest.

Rei09:03:19

I see. Thanks for the help. I think I'll create a custom remote to handle this. RAD is a bit foreign to me given that I'm not that well-versed in Fulcro to begin with to understand what it's doing. Running Pathom 3 in CLJS is interesting, but it does seem like it's similar to creating a custom remote, i.e. I still have to write code to handle the conversion, the difference is just where to do so.

Rei15:03:32

@U0CKQ19AQ Ok so after trying it out for a bit, I'm seeing some weird behaviour that I don't fully know why it's happening. I have a remote:

:identity (net/fulcro-http-remote {:url                 IDENTITY_URL
                                                                    :response-middleware get-response-mw
                                                                    :request-middleware  get-request-mw})
where the middlewares are:
(defn handle-get-response [{:keys [status-code original-transaction body] :as resp}]
  (assoc resp :body {}))

(defn handle-get-request [{:keys [url body] :as req}]
  (let [body (some-> body eql/query->ast1 :params :body)]
    (assoc req :url url :method :post :body body  :content-type "application/json")))

(def get-response-mw (fn [resp] (handle-get-response resp)))
(def get-request-mw (fn [req] (handle-get-request req)))
On the server side, this is a simple server:
(ring/ring-handler
      (ring/router
        ["/identity" {:post {:middleware [#(wrap-defaults % defaults-config-native)]
                             :handler    identity-handler}}]))
where the identity-handler is
(defn identity-handler
  [{:keys [body] :as req}]
  {:status  200
   :headers {"Content-Type" "application/json"}
   :body    "coolbean"})
The load call (df/load! this :cool Root {:remote :identity :params {:body {:cool "bean"}}}) does produce an HTTP request and send to the server correctly. However, the body is always nil on the server side. Printing out the outgoing request and Fulcro was telling me that it does have {:cool "bean"} as the body, so somewhere along the line the body got nuked but I'm not sure where exactly. Am I missing anything here?

tony.kay15:03:00

1. You’re using the url of the req, not the prams…so just IDENTITY_URL will be used 2. Where are you converting body to actual JSON? at least those 2 things look wrong to me

Rei16:03:41

1. The params dont have url as I've set it up as a new remote. 2. I dont have that atm. So the conversion has to manual? In which case, is it a JSON string or something else?

tony.kay16:03:47

JSON string…look at the implementation of http-remote. You can control everything about the low-level request, but it is just using google’s xhrio. So yes, if YOU say content type is JSON, then body has to be a JSON string.

tony.kay16:03:42

response types are controllable in terms of low level types, see line 109 in http-remote:

(def response-types {:default      ""
                     :array-buffer "arraybuffer"
                     :blob         "blob"
                     :document     "document"
                     :text         "text"})

tony.kay16:03:22

you have to understand the low-level env you’re in, which is js/browser. The remote IS the interfacing between the Fulcro/Clojurescript world and the “rest of the world”

tony.kay16:03:11

see also line 129, where wrap-fulcro-request is

tony.kay16:03:47

it uses cognitect’s transit write to turn EDN into a transit string

Rei16:03:25

Got it. Let me give it another go. Thanks.

tony.kay17:03:46

same with the response…you have to make sure to convert it to EDN, namespace the kws, and put it in the right shape for the query. In your load case, you need to put EDN like this in the resp body:

{:cool {kvs that Root asked for}}

tony.kay17:03:46

because load will send (and therefore expect a proper EQL reqponse to): [{:cool (comp/get-query Root)}] and the default tx processing of fulcro will filter OUT any :ui namespaced keywords. In general you would never use a real Root this way.

Rei18:03:24

I just did some playing around and yeah by simply converting into JSON string I can see the body now. Chuck in and everything's seems to be golden thus far. Next step is to have a look into the client's response middleware as you've mentioned. Thanks a lot for the detailed help so far 🙂