Right, this is the approach I've always advocated. I don't mention it specifically in this post I wrote a while ago about optimising GraphQL, but my implicit assumption was that you're building your GraphQL server on top of an underlying platform:
In real-world UIs, I've found that queries rarely end up being more than a few levels deep and are relatively easily optimised as long as your internal APIs can handle batches (easy for entities, harder for pagination). Additionally, even though the only-return-IDs-for-relations pattern means you can't utilise joins effectively, the upside is that you end up with much simpler database queries that area easier to optimise at scale. My rule of thumb was that as long as the query representing an entire screen could typically return in sub 100ms in production, it was acceptable (this was without any caching at the GraphQL level, which I had planned but left the company before I could implement it).
https://blog.apollographql.com/optimizing-your-graphql-reque...
In real-world UIs, I've found that queries rarely end up being more than a few levels deep and are relatively easily optimised as long as your internal APIs can handle batches (easy for entities, harder for pagination). Additionally, even though the only-return-IDs-for-relations pattern means you can't utilise joins effectively, the upside is that you end up with much simpler database queries that area easier to optimise at scale. My rule of thumb was that as long as the query representing an entire screen could typically return in sub 100ms in production, it was acceptable (this was without any caching at the GraphQL level, which I had planned but left the company before I could implement it).