Some data flows end up with dependencies you don't know when you build a query. Such as a auth token, credentials, etc. So you end up with several round trips anyway for these cases.
A lot of the advantages to GraphQL don't materialize due to how front ends are built. It encourages the use of a global store to shove all data into from the large query you just completed. Except every front end I've ever worked on that used GraphQL basically used it just like REST with a few extra features because typical front end architecture encourages this. Taking little advantage of what GraphQL is suppose to offer.
In my experience versioning was more painful with GraphQL than it was with REST. With REST you know you just need to rev the path or some other obvious marker like a header, which allows you to run both versions side by side. In GraphQL this isn't so obvious that you need to do the same kind of separation for braking changes. And breaking changes are occasionally needed contrary to what some people believe.
So when I make a backend now, it's vanilla REST without anything fancy. I favor simplicity.
Sending data to an api for creation or updating should be simple business commands and not based on resources or transport.
My mantra is to think like the business, not like an API or data store.
Cache times are usually short, < 5 minutes, and are cleared whenever a POST/DELETE/PUT are issued. Works pretty well in my experience.
If your baseline is so a new fetch every single time anyway, it doesn’t take much to achieve some pretty impressive wins.
I create a JavaScript object that matches the interface of my request library, axios in my case. The cache is basically a middleware to it created by function wrapping. Kind of like how memoize works but with a bit of extra smarts.
I have my own .get(), .post(), .delete() and .put() functions. So when a .get() is called, it's a cache check and return the stored value. If not found call axios.get() with the passed in args and store in cache before returning.
The post() function clears the cache then passes through the arguments to axios.post().
Etc.
The 5 minutes is accomplished with an entry on the event loop to remove said item.
It's only like 30 lines of code.
The real wins for this is in basic navigation. Clicking around site exploring, makes it snappy. Another advantage is lowering the total request count a bit to the backend, lowering the cloud bill.