Why You Can’t Select All Fields on a Type in GraphQL
One of the most asked questions I see around GraphQL are “How do I select all the fields on a type?” and/or “is there a SELECT * equivalent in GraphQL?”. It makes sense: It can be a little annoying for a client to always be adding fields once they’re available. While the answer is unfortunately just “you can’t”, I’m here to tell you it’s for very good reasons, and that hopefully after reading this you won’t be wishing for this feature anymore!
The first part of this is how the declarative nature of the GraphQL query language (Yes, I know that gives “Graph Query Language Query Language”, but GraphQL the spec also includes the type system and server implementation 🙈) gives absolutely great predictability for clients. Simply looking at a GraphQL string gives a client developer all the information they need to build a UI for example. Pair this with a client that uses fragments in conjunction with a good component model (Like Relay or Apollo+React) and you get something quite amazing to use on the client side. If clients were to use
SELECT * or some other notation to select just everything on a type, suddenly we lose that predictability and clients don't necessarily know what will happen at runtime. GraphQL requests and response shapes are incredibly similar thanks to this feature!
Evolution: The Client Perspective
Next up: Evolution. If you go to https://graphql.org right now and scroll down, you’ll see this:
This is a bit of a marketing statement because evolving a GraphQL API and keeping a single version is quite hard, just like any other API. In fact, I believe continuous evolution is a good practice no matter what API architecture or style we’re using.
There is one thing that makes it much nicer for clients however: the fact that existing clients do not get affected by any additive changes to the schema. Maintaining a single version of an API and evolving it over time often demands additive changes (Providing a new, better way of doing things while deprecating the older way for example) to avoid constantly breaking your integrators. With GraphQL, since clients select the subset of the schema they’re interested in, they don’t get all these new changes and can keep using just what they need.
Now imagine clients are using a “select all” trick. We instantly lose that power. Every addition to API, any evolution slowly becomes a burden to our client.
Evolution: The API Provider Perspective
Evolving an API is a two player game. Clients care about stability and avoiding overhead of a growing API, but API providers need to get very good at change management, the hardest part of versioning or evolution.
Knowning how your API is used is a super power when it comes to making changes. Here’s the fun part: Because every client is selecting everything they need down to every single field and that these requirements are sent to the GraphQL server, this lets API providers track API usage with amazing precision. Compare this with a typical HTTP endpoint with no (or optional) special field selection mechanism, where API providers can simply know which endpoints or resources are used, but not which fields are actually used on the client side.
Providing a way to select all fields on a type would make this kind of usage analytics impossible, meaning API providers would have a much harder time making changes since they have no idea what is being used or not. Not only does this make evolution easier, but it makes API providers know more on which fields provide the most values, and which fields are barely used!
Still want that SELECT *?
While selecting every field on a type sounds convenient, you can see it would cost us a few awesome features. It turns out the lack of that select all feature is one of my favorite properties of GraphQL. One part of me even thinks that I’d like to try to have the same property when I work on an endpoint-based HTTP API next. Something like requiring field selections on endpoints, like JSON:API sparse field sets.
Thanks for reading!