Migrating to GraphQL: A Practical Assessment
arxiv.org
arxiv.org
Seriously. For internal stuff you want to be as specific as humanly possible. You want to optimise the living fuck out of that hot path thousand-times-a-second query, and build an entirely separate service to handle that particular URL if necessary.
For a public API, why would you ever encourage submission of arbitrary queries? They will destroy your database servers.
Note that SQL vs. NoSQL is not even a competition here. An RDBMS can handle arbitrary queries much better than any brute-force map-reduce system, and doesn't require spending thousands of man-hours writing your own query planner. The difference is that it doesn't (always) automatically scale horizontally.
So from where I'm sitting, GraphQL is nothing more than an invitation to maintenance and performance headaches, predicated on the idea that everyone scales infinitely horizontally and can brute-force every query.
Personally I prefer being able to serve a thousand queries a second from a single server managing a 1TB database with a 50GB working set, with a latency under a second even when (looking at the raw query) it 'should' touch more rows than there are atoms in the universe.
[edit] In light of the replies, I should express some surprise that building REST endpoints is expensive. If you can execute at runtime an automatic combination of various other endpoints to produce a result, is it not equally simple to generate the code necessary to do the same? That can at least be examined and maintained more easily than dealing with the massive array of possible varations to known-working queries which comes with modifying the rules driving the GraphQL evaluator...?
Note that GraphQL does not allow the specification of arbitrary queries - much like REST does not allow access to arbitrary resources - the sever defines what queries are available and the user can choose to request a selection of them, and to pluck data from them (or follow links - simply a JOIN).
In other words, GraphQL lets you combine a subset of pre-defined queries in one go.
I may be misunderstanding something here, but when 'general' queries are combined externally a lot more work is done than is necessary. Which may be fine for small intermediate sets. But treating it as any kind of general solution is silly.
That said, people do similarly stupid things within individual codebases running in individual services to a single database, so as usual it likely comes down to how the tool is used rather than how it can be abused. Still, spreading these things across services and processes looks like it only makes abuse easier.
Let's say you call UserService::batchGetUsers(userIds) to get a list of users from a service backed by a MySQL DB, call WidgetService::widgetsForUser(userId) to get a list of a user's widgets from a separate service backed by a Redis cache and another MySQL DB, and return them. What's the problem here? Lack of transactions? Unnecessary fields sent down the wire? Something else?
The parent author holds some very stubborn beliefs about how systems are built (his/her way is the correct way!), which is great for discussion, but probably not the best example on how to actually build big systems.
AFAIK Facebook created GraphQL specifically for their gateway API - a service used as a facade between internal service mes(s/h) and their client - not for internal ones themselves. That's why things like schema stitching didn't came from FB - they weren't using it in that context.
>avoid having to write a custom endpoint for every single REST query
So what are you saving, really? Could you better implement GraphQL type functionality as a client side wrapper for traditional REST apis? Then you could keep the traditional tooling as well as simple query join semantics for client devs.
Of course we're very concerned about the performance drawbacks and are trying to plan for them. We don't expect it to be web scale and aren't replacing our REST APIs that serve the public. Even so we expect to need to be smart about calculating query complexity.
So while most of the criticisms of GraphQL on this page do apply, for us the net value is looking quite positive.
I'm just too used to being the person who then has to make the server deal with this particular use case (meaning a million possible variations on the same query) run a thousand times faster :P
So if it's understood at all stakeholder levels that there are tradeoffs, it could be a good tool. If. >_>
One easy way to fix this is by using graphql instead of REST. Now the frontend teams have access to somehow get the data they need and are thus unblocked.
An API is like a promise.
In the best case, people decide to use your API and build on it. Then, if for whatever reason and in whatever way, you break your API, you force everyone affected to reimplement at least to some degree.
With GraphQL you’ll be promising the sun the moon and the stars if you aren’t very careful. (Even if you are very careful you’re still promising a lot, though hopefully the available tools will help you out.)
With GraphQL you put yourself in a very tough position of either keeping big promises or breaking big promises.
Most of the time you are going to be better off keeping small promises.
But this one thing is so useful for almost everyone that for internal APIs using GraphQL is usually a no-brainer.
You actually don't want to be as performant as possible for internal APIs. There is a performance-flexibility trade-off involved and GraphQL lets you choose a different point on the Pareto frontier than maximum performance.
http://www.smashcompany.com/technology/caches-are-cheap-buil...
Things like dataloader exist but the behavior of your schema gets harder and harder to reason about when different caching mechanisms get thrown on top.
I think everyone agrees that the client facing api for GQL is fantastic... maybe that means graphdbs are the next wave :P
If you're using a column store structure for most data, you're mainly doing individual lookups based on a single key, graph data in another key/keys, and related keys looked up separately. Each optimized for the single record(s).
Especially since distributed/collectively this data will be accessed faster than a single rdbms would be able to manage.
That said, if your application can/does use a single sql datastore, and you don't need that much scale, the effort to setup/configure GQL may not be worth it in a given instance.
For front-end/public API parsing a query to a querable graph enables adhoc-ness and co-location which removes a lot of manually written controllers with much fewer resolvers, which also means removes huge amount of accidental complexities.
With that said, I'm not a big fan of GraphQL, but this particular trade-off seems like a win over REST.
E.g. I want all of my posts and their respective comments and the users making those comments. You can narrow things down a lot by specifying fields: only give me the name and avatars of the commenters. With REST I've done this in the past with N API calls: give me my posts, iterate over the IDs of those posts to make N API calls for their comments, and another N calls for the comment user data I need.
At that point, a custom API endpoint could trim down the network calls a lot, and this is where GraphQL shines in comparison. You wrote a resolver for a user, comment and post in the backend, and GraphQL server frameworks can piece those together for you.
Also if someone creates a query that triggers a path where the n+1 problem absolutely wreak havoc with the backing data storage you could have serious problems with that. By fixing an API you get predictability. There’s also the mismatching between data versions and on what version or view you are operating on.
And in the back-end, after the query was parsed it would be split to a bunch of requests depends what's your data source.
If the data source is in-memory then good everything's done.
If the data source is an RDBMS it would result in an n+1 query. However, people would always use data loader with GraphQL, which batches the query and converts the n+1 query to two queries. REST APIs usually only care about single kind of resource per API, so if there are cascaded joins (eg: get the user and its posts and comments and comments' comments), there would be less overall requests in you are thinking in amortization.
If the data source is another REST API, if there's batch APIs for resources, the similar approach as RDBMS would also be taken.
There are 10 different pages you need to read the 'Article' resource, in the index page you need the title and the summary, in details and edit pages you need the content, in the details page you also need comments.
The thing is, all of the pages read 'Article', they need it, but they only care about only some parts of it, some reads also require extra and possibly cascaded joins with other resources.
In most medium-sized web apps people will tend to write a bunch of different APIs to fulfill these needs, it's grunt works and hardly consistent because they're duplicating things. Or even worse, they will invent their own queries, a half-baked GraphQL everywhere, which is inconsistent and sometimes buggy.
Our application has est. 3000 internal APIs as of last year, it's becoming harder and harder to even find which API to use because there are so many nuances. Almost half are doing different kinds of reads, say if we can make everything in a queryable graph (ironically, almost all businesses are graphs, yet only very few people explicitly treat them as graphs), the APIs will be much lesser and easier to understand.
Also I fail to see how graphql can simplify an API, the graphql operates on the actual APIs so if you don’t understand those, you’ll probably not understand the graphql version of it too.
We're throwing decades of web architecture to the wind here. Caching is a an exercise left to the reader.
If we're going as far as GraphQL, why _not_ open your DB to the public?
TL;DR: it's not really a query language, like SQL. It's more of an opinionated RPC framework. I generally don't think building out one's entire data model in their GraphQL schema is a good idea, in general, but it's also not the true value-add of the system.
[1] http://artsy.github.io/blog/2018/05/08/is-graphql-the-future...
Ideally though, you don't use dependent fetches and you have a bespoke endpoint that collapses the dependent operations possibly as tight as a single join query. If this is the case you're trying to solve, then why shouldn't we strive for something like a declarative query language?
The bespoke endpoint thing is a big part of what GraphQL avoids. It gives you something explorable, but as a team, you can decide how comprehensive you want it to be. If you have a monolithic database, you can use SQL to explore it, but GraphQL often sits in front of a service layer, which connects to my next point.
GraphQL's mutations often map to entire processes, which might change a data store, like a DML SQL operation, but also have other sorts of effects.
Sure, if you try to shoehorn an API that does not fit the database, it can be challenging, but you can always start with the simple case, doing multiple queries to the DB for one GraphQL request and only later see what is actually used and could benefit from optimizing.
You have more opportunities to optimize. With REST, if a client needs to list some resource and then access all of them one by one, it is going to be n+1 requests and you can't do anything on the back end to change that. With GraphQL you can look at the query holistically and optimize as needed.
All in all it feels to me that GraphQL gives the client the ability to better communicate the intent of what they are trying to do. Declarative over imperative.
* It allows rapid product iteration over the "social graph". It allows each product to have unique behaviours and data requirements, and new product capabilities can be rolled out quickly. Facebook's entire data layer (TAO, Ent framework) is optimised towards rapid iteration.
* It optimised performance for client load times. Mobile networks are high latency and low bandwidth. GraphQL allows each application to load data in the most efficient way possible.
For myself, the 'practicality' of GraphQL would be more on complexity of the implementation, training of engineers, potential re-implementation of client logic, ease and depth of debugging, performance, etc. It seems these days that the size of the response is not typically a limiting factor in most applications I've interfaced with (though maybe I've never been exposed to that world before).
Can anyone speak to how a migration from REST to a GraphQL went? My biggest concern is around the complexity of the thing. It just seems so much more complex than REST, but maybe I haven't spent enough time with it.
There’s a reason that category of thinking exists. But since GraphQL libs are now busy implementing caching in top of non-cacheable POST requests, this argument is lost on them.
What's the reason?
> But since GraphQL libs are now busy implementing caching
What are you referring to? You mean like https://github.com/graphql/dataloader ?
> in top of non-cacheable POST requests, this argument is lost on them.
I think caching/serving HTTP results is a bad paradigm. It makes sense if you're serving HTML for a content site or something like that, but doesn't make sense when you're serving an API.
Also the tooling is next level . GraphIql alone is miles ahead of any REST tooling offerings. Also, typescript generation. Truly improved my workflow.
On the plus side GraphQL is very simple and adaptable for frontends. It just comes at the cost of moving so much complexity to the backend.
Graphene seems like a nightmare but the documentation is improving and once you get used to the design it's actually not so bad and more feature complete than the others.
I think moving complexity to the backend is actually not such a bad idea, so the frontend can focus more on actually just displaying the information.
Absolutely love graphql as a technology. The pagination and ability to structure a query to pull a lot of data is nice.
As for some of the other comments - it's true you can't just write any query: but you can always add new fields, lists, connections (basically a pagination-friendly list), etc against arbitrary things on the backend. It's your backend, you control what data you serve.
The graphql-python stack is layered like an onion (graphene on top, graphql-core inside). You will probably be doing something like flask-graphql or graphene-django on top of that.
There is a huge negative for python <-> graphql for us, and it's the error system and promises. Perhaps this is what graphql servers in JS are like, but graphql-core hijacks python's error system by wrapping fields in promises.
So graphql-core is acting very very true to graphql's implementation in node: to the point it creating a mountain of breadcrumbs in sentry.
It also overrides the "next" built-in and tries to emulate express-style callbacks. Another thing ported from node into python that doesn't translate well imo.
Aside from that though: graphene has been really speedy, fast to work with. Documentation is getting nicer. The developers on the issue tracker are very nice. And as a general graphql thing: https://github.com/graphql/graphiql is really nice!
And one more graphql thing: It's typed. In a big API, being able to lay out stuff like that goes a long way. We're generating typescript types via schema.graphql output, and response types via relay. In a real big frontend project, it pays off a lot.
I recommend giving graphql a shot.
As a "for your consideration," optimizing for the _client's_ concerns might not the true win, but if the server-side were able to side-step some joins, it could be a win for the whole community since it could -- in theory -- make everyone's experience better. The ability to push conditionals on the server side _could_ side-step multiple round-trips, too: https://graphql.github.io/learn/queries/#directives
Just this morning I tried out GitHub's GraphQL interface, and it for sure requires some thinking to reframe the question in terms of the GraphQL surface-area they expose, but GraphQL also has built-in schema discovery, which is something one would typically have to read the docs to access.
It's tremendously annoying that one must package a GraphQL query _inside_ a JSON field named `query` versus the much more sane `curl --data-binary 'query MyQuery { some fields }' https://example.com/graphql` but it seems that packaging is required for separating variables from the actual query itself: https://graphql.github.io/learn/queries/#variables (although having two endpoints, `/graphql` and `/graphql.variables`, or switching behavior based on content-type, would be amazing and, at least in theory, very little server-side work)
Speaking of switching endpoints, the GraphQL community claims that the api is a lot more versioned, too, getting one out of the business of `/v1/customer` and `/v2/customer` etc but I don't have experience to know how much of that is "in theory."
- GraphQL parsing and interpretation is considerably slower than RESTful JSON. I'm talking an order of magnitude difference in .NET Core.
- The required POSTs cannot easily be (if at all?) cached by caching services such as Cloudflare
- It's more work to have to define what data you want than to just spit out the data that's available. Having to continually update your client side queries in order to fetch all the available data is tedious as hell. The whole over/underfetching argument is not worth the incurred performance hit nor the bandwidth improvements.
- GraphQL promotes laziness about documentation because it's "self-documenting" -- turns out most API users still struggle to understand how it works and need better API guides anyway, so the reflection is largely useless (it's about as good as those auto-generated Java docs you find on Oracle's website.)
- Users get RESTful. They know it, it's not a toy, and it just works. I'll repeat this again because Silicon Valley doesn't seem to get it: GraphQL is not user friendly.
GraphQL allows queries to be performed with GET:
http://example.com/graphql?query=query{user{id}}
You want to use POST for mutations, but read queries can be run through GET and cached just like REST. There's literally no difference -- it's HTTP, after all.Looks like an implementation problem. I couldn't measure a significant difference between both for comparable requests in Java. Also, parsing/interpretation is almost never the bottleneck of an application.
> It's more work to have to define what data you want than to just spit out the data that's available. Having to continually update your client side queries in order to fetch all the available data is tedious as hell. The whole over/underfetching argument is not worth the incurred performance hit nor the bandwidth improvements.
"all the available data" is almost always an anti-pattern, so yes, this gets tedious. Frontends should have specific requirements what data they need and if you only request those data it isn't tedious at all + you don't waste any bandwith. This is the "select * from" school of sql all over again combined with "what do you mean we have more than one client with different requirements?" - now you either always send the superset or start an ad-hoc implementation of what GraphQL provides you to only send the required data to each client.
> GraphQL promotes laziness about documentation because it's "self-documenting" -- turns out most API users still struggle to understand how it works and need better API guides anyway, so the reflection is largely useless (it's about as good as those auto-generated Java docs you find on Oracle's website.)
GraphQL self-documentation and exploration are sufficient if whoever uses the API understands the domain model underlying it. You will always have to teach people the domain model, but after they've understood this people can use self-exploration to find what they need in that model.
With REST APIs you have to document the domain model and then painstakingly each and every technical API endpoint, cause domain model and technical API always differ (usually even two endpoints of APIs cannot be called in the same fashion - what do you mean the parameter here is called maxResults now? It was called maxCount over there!)
> Users get RESTful. They know it, it's not a toy, and it just works. I'll repeat this again because Silicon Valley doesn't seem to get it: GraphQL is not user friendly.
No, they don't. They have learned to accept that companies only provide them REST and they have to live with it, even if it is bad. SOAP or GraphQL are both (in different ways) vastly superior, but one has fallen out of fashion and the other one is seen by some of the "REST is great" people as a toy, cause they think REST is some kind of holy grail.
Disclaimer: Does GraphQL solve all your problems? No. Nothing does, you will have to do it yourself, that's part of your job.
That would depend on your definition of significant, and the complexity of the AST you're parsing. Obviously I'm talking about the implementation I have available to me, but GraphQL would routinely take 100+ms to return the same data that ASP.NET could deliver in 10ms or less. And this does indeed make sense as it is simply more overhead. The more likely cause of the difference is that it's just complicated to optimize GraphQL graph queries. When you have a single endpoint, you know exactly what data you need and a human can optimize around that specific requirement, whereas GraphQL attempts to fetch each piece of the graph in isolation (or not, if you optimize for that, which is additional work.)
> "all the available data" is almost always an anti-pattern, so yes, this gets tedious.
That's not really what I meant. What I meant was, in order to fetch the data I want for a specific task, I have to list all of it. Which is silly. I know what I need to return on the server side, why should I have to write it twice? This is especially cumbersome in the case of return a Dictionary<string, string> or whatever it might be, where the object keys can change over time.
> This is the "select * from" school of sql all over again combined with "what do you mean we have more than one client with different requirements?" - now you either always send the superset or start an ad-hoc implementation of what GraphQL provides you to only send the required data to each client.
Indeed, this is a real problem, but typically if all your data is mapped well you shouldn't run into this very often. Most people who are implementing GraphQL on their servers don't have this problem. They're doing it because it's cool. Problems like this are what versioning is supposed to cover. If you're having to constantly change your API for different clients however, your product is not the API itself. Most people are just trying to create an API that people can pick up and use easily.
> GraphQL self-documentation and exploration are sufficient if whoever uses the API understands the domain model underlying it. You will always have to teach people the domain model, but after they've understood this people can use self-exploration to find what they need in that model.
> With REST APIs you have to document the domain model and then painstakingly each and every technical API endpoint, cause domain model and technical API always differ (usually even two endpoints of APIs cannot be called in the same fashion - what do you mean the parameter here is called maxResults now? It was called maxCount over there!)
If you haven't explained the purpose for each endpoint (or queries/mutations as GraphQL calls them) then you haven't documented it. So you must "painstakingly" document each "endpoint" in either case. Your API docs should be guidance, not just "listTransactions(): This lists the transactions". This is exactly the kind of thing I'm talking about. At the very least, it's the same amount of work. The difference between not-GraphQL and GraphQL is with GraphQL you've probably set up GraphiQL and now your model attributions are no longer co-located with your real documentation!
> No, they don't. They have learned to accept that companies only provide them REST and they have to live with it, even if it is bad. SOAP or GraphQL are both (in different ways) vastly superior, but one has fallen out of fashion and the other one is seen by some of the "REST is great" people as a toy, cause they think REST is some kind of holy grail.
This is spoken like someone who hasn't actually worked with developers who have to implement this stuff. Users _in general_ are confused by GraphQL. Especially, of course, novice developers. They really don't know where to start with it. Real REST is not something people advocate for these days. All I'm talking about is, users are happy when you provide them with well thought out endpoints that each have their own URL and cover the use cases they have.
Imagine if Stripe had started with GraphQL. The number one question they'd have received would have been: "what do you mean I should install the GraphQL library so I can hit your APIs?". APIs work best when they're SIMPLE, and GraphQL is anything but simple.
SOAP is essentially the same as REST -- you have request/response for every resource. Its just more verbose and inscrutable thanks to XML and all the schemas.
GraphQL on the other hand enables new interactions that were not possible or practical with either of the two.
Beware if your favorite technology catches on in the BigCo world, because when those people fall in love with something they end up hugging it so hard they strangle it.
Latency and failure are core to network (and especially internet) RPC though, and something that presents as a method call is too leaky an abstraction. It inhibited people from structuring their network calls as batch operations (the documentation orientation palaver was an attempt to change the conversation) or treating errors in a first class way (errors in RPC are way, way more common than in-process method calls, and require much more attention) or using async calls (don't block the world due to latency, which is rarely a hard requirement locally).
All the standardization efforts as the CORBA etc. crowd piled on in and translated IDL (WSDL!), and layered authorization & authentication (rather than use existing HTTP idioms), transactions (WS-AT), etc. etc. just buried the already dying horse.
e.g. /api/v1/getUser?id=1234&return=name,zipCode,workplace.address,workplace.zipCode
Not sure if that has changed in the meanwhile, but when I tried GraphQL for the first time it was obvious for me, that every service call has to be implemented by hand. Depending on what functionality is required, implementations of service calls can get quite complicated.
As someone coming from Java where we have ORM frameworks and tools like Spring, I was surprised that Facebook didn't come up with something better.
But hey it's Facebook. A company where the CEO thinks it's cool maintaining a huge codebase mostly written in PHP and C(++).
You absolutely can define reports as graphQl entities (and really, table -> entity mapping should never be 1to1) and just let users leverage the field filtering component of the transit format.
Lastly, PHP is pretty sweet now a days you should give it a try!
query User {
user(id: "123") {
id
name
topPosts(limit: 10) { id, title }
drafts: comments(published: false) { id, body }
friends {
id
photo(size: "100x100") { url, width, height }
}
newestPosts(since: "3days", categories: ["news", "chat"]) {
id, title
forum { id, name }
creator { id, photo(size: "100x100") { url, width, height } }
}
}
latestNews(limit: 10) { title, url }
}
All one query. Selecting a subset of attributes is a small part of GraphQL. (gRPC also allows you filter fields, but doesn't have structured querying.)GraphQL is a protocol and a schema language, not an ORM. It's a way to provide a common interface on top of any implementation. For example, in the above query, users could be in the backend's own database, whereas "friends" could be something stored in a completely different backend. The GraphQL server could trivially act as a façade that federated/aggregated the results of both.
I would argue that GraphQL fulfills the objectives (or at least desired features) of REST much better than what has been delivered thus far. For example, REST's hypermedia aspect ("HATEOAS") has not been widely adopted — because discoverability is poor and there's no standard protocol for introspecting a schema. But look around and you can easily find a dozen different GraphQL clients (my personal favourite being Prisma's GraphQL Playground [1]) that can be pointed at any GraphQL-compliant server to run queries and browse the schema.
Those that criticize GraphQL generally don't seem to realize how simple it is, and how close it's still to the REST style of consuming and producing JSON.
In the end, an API written for one department may not match the needs of another, and another still may need additional related aggregate data. In the end, would you rather define your data sources once, or communicate API needs across several departments and maintain 3x or more the surface area to support them?
edit: I'm not saying GraphQL is appropriate for all scenarios... but I'm saying it is definitely better for many.
The data you expose via apis is often one “view” of the actual rich data store you own. If a dependent team wants more data than your rest api provides, it is blocked on you adding that data to your api.
If you instead provide a way to query your data store, the dependent team isn’t blocked on you.
This means that you bypass the API and give direct access to the data? Why have an API at all then?
Don’t you think that this mean you tightly couple the data storage with the client?
False dichotomy.
You’re not bypassing the api. You’re exposing a richer api. Use the right terms.
The purpose of exposing apis is not to limit the attributes of exposable data. Eg your definition of an object that the rest api represents May evolve over time while the api does not since they may not be in sync in the code. With graphql this is handled automatically.
Of course it’s tightly coupling data storage, that is the point! The problem with using rest apis when doing this is that you can’t then change your api without breaking dependencies. Graphql recognizes that in a rapidly evolving data model, trying to model a permanence is futile. Instead it gives you and your clients tools to a) reduce expansive dependencies by querying only for data you need and b) identifying which dependencies are used and how frequently allowing you to deprecate attributes safely.
This is not a tool meant for enterprises abusing rest apis as “contracts”, where every change needs to be communicated in advance yadda yadda. It’s targeted towards consumers who are ok with and actually _want_ a richer api in lieu of the stability provided by versioned apis. Good candidates for this use case include internal teams and external, non contractual apis.
With that approach you also don't end up writing a lot of specialized REST endpoints for the frontend. You can just use the queries directly and output for example HTML (or json and render that into HTML on the client). You can also do a lot more queries at once, because the services backend have usually more bandwidth and less connection limits than a frontend running inside a browser.
You are full control of the queries. Only UI related data is exposed to the frontend.
One draw back is the frontend team wouldn't do any business stuff anymore. They would do more generic components that the backend can drive.
And depending on the implementation you need more server round trips.
On the other hand, there is less JS to load and execute by the browser, since the backend is doing most of the orchestration and rendering.
GQL allows for the client UI/UX to describe what it needs, and the backend delivers that, within the context of well defined data structures that can then be distributed.
There's a cost to setting this up, but in the case of Facebook or Netflix, absolutely worth taking on. For a few hundred users, not so much.
> GQL allows for the client UI/UX to describe what it needs, and the backend delivers that, within the context of well defined data structures that can then be distributed.
In the other model, it is done in the same way, except it is being executed in the backend and not the frontend.
Both the frontend and the backend are general computing platforms, but they have different characteristics in terms of performanc, connectivity, control etc.
Most UI/UX requirements are not that specific that only a frontend heavy implementation will be adequate to fulfil the requirements.
Please keep in mind that back in the old days, when we didn't have JS in the browser, all html was rendered on the server and a thin client was rendering it.
And even in right now, sometimes there are architectures popping up that try to make the client thin and the push the majority of the computation to the backend: Like for example Google's Stadia.
Back in what old day? I've been at this since 1995, and have been using JS pretty proactively since 1998 or so, it's been 20+ years now. Computers are a considerably more powerful and power efficient compared to 1998.
If you want to be able to use the internet on an 80386 without a math co... have fun with that. I'll take modern tooling, with modern hardware.
React server side rendering, Vue's Nuxt, etc are all modern ways to render UX/UI in the backend for example. And there are more.
The discussion of rendering server side goes deeper than just performance or using a "modern" framework.
There is for example: how many teams can work on a single application at the same time and deploy independently features in an efficient manner.
How can we share business processing flow requirements between the web version of an application with the native mobile application counter parts in an efficient way, without implementing everything three times.
Or other aspects mentioned here, like predictable performance etc.
Or choice of language, in the backend your choice of languages to choose is greater and/or simpler than in the frontend.
You can add more monitoring in the backend about aspects of your application than you can do in the frontend.
Obviously, the frontend has advantages as well. I.e. if something needs to be calculated, animated it is almost instantly available to the user when done in the frontend.
Frontend can perform functions offline, can use device specific features: like camera, contact list, push notifications etc.
Thus I prefer to look at the requirements and pick the tools for the job that best fulfils all the requirements for a giving application in a given organization and strategy.
If that means i do more stuff in the frontend, sure fine. But I wouldn't go for GraphQL just because it is "modern". Being "modern" is not solid reason to choose a technology unless you want to attract developers that like "modern" technologies. Which could be a valid reason as well for some organizations.
If you are the one paying for the servers, and you want to pay for 10x+ the servers to support your app, feel free.