Use GraphQL Codegen to reduce boilerplate code

Christian Budde Christensen
3 min readApr 23, 2022

--

When I introduced GraphQL Codegen last year I did so through two articles. One article outlined the motivations behind the library and how it could be used to structure your GraphQL apps in a maintainable way, the other was a deep dive covering the code-generation algorithm.

The idea was always to leverage the typed nature of GraphQL to facilitate a better developer experience by reducing the number of lines of boilerplate code developers need to write. Boilerplate code is hard to maintain and leads to bugs.

GraphQL Codegen was a step in the right direction. It added widgets and methods for all operations, e.g. for a given query:

query FetchPerson($id: ID!) {
person(id: $id) {
name age
}
}

we could now call queryFetchPerson directly on the GraphQLClient

final result = client.queryFetchPerson(
OptionsQueryFetchPerson(
variables: VariablesQueryFetchPerson(id: "id"),
),
);
final parsedResult = result.parsedQueryFetchPerson;

In this example, we generate serializers, shortcuts to use the serializers, and utilize the dart type system to indicate variable requirements.

Furthermore, the initial version of GraphQL Codegen introduced fragment “interfaces” which allow us to express the data requirement of widgets by defining fragments. I.e., given the fragment

fragment PersonLabel on Person { 
name
}

for the widget

class PersonLabelWidget extends StatelessWidget {
final FragmentPersonLabel data;
PersonLabelWidget(this.data); Widget build(BuildContext context) {
return Text(data.name);
}
}

To render PersonLabelWidget the parent widget will have to spread the PersonLabel fragment on their query/fragment and can then pass the data to the label. I.e., given a query:

query PeopleList {
people { // An array of People
...PersonLabel
}
}

the widget can be rendered with:

class PeopleList extends StatelessWidget {
Widget build(BuildContext context) {
...
final parsedData = result.parsedQueryPeopleList;
return Column(chilren: parseData.people.map(p => PeopleLabel(p)));
}
}

Notice how we easily can change the data required to render the PeopleLabel widget by modifying the fragment, in one place! This maintainability is the power of co-locating your queries with your code.

Now, the initial version still had some usability issues. For starters, there wasn’t a great coupling between the query and the parsed result. I.e., if you have multiple queries, there was nothing stopping you from parsing the wrong result for a given query.

Furthermore, the query/mutation/subscription widgets might have been an improvement wrt. usability, but they are still not very maintainable; they encourage nesting of code through builders which is arguably not very readable.

To solve these issues, significant changes to the graphql and graphql_flutter packages were needed, and while I have a history of endeavoring on larger projects, I wasn’t prepared to write my own client with everything that this entails. As luck would have it, the packages were looking for new maintainers, so I signed up! By becoming a maintainer for graphql and graphql_flutter, I could give something back to the community and facilitate tighter integration with the code-generator.

So, what have I done as a maintainer? With the latest version of the graphql packages (5.1.x) and the code generator (0.7.x), I’m thrilled to introduce significant usability features:

First of all, we’ve removed the issue with the serializer mismatch mentioned above. This is done by adding a parsedData property on the QueryResult returned from e.g., queries. This means that our example above can now be written as:

final result = client.queryFetchPerson(
OptionsQueryFetchPerson(
variables: VariablesQueryFetchPerson(id: "id"),
),
);
final parsedResult = result.parsedData;

while this change might seem small, the ability to provide a parser to the query options, which is autogenerated with GraphQL Codegen, is just one less thing for developers to get right!

The other major improvement that I’d like to call out is the introduction of flutter hooks as an alternative to query widgets. For every operation, appropriate hooks will be generated, which means that we can query our API with the following code

class PeopleList extends HookWidget {
Widget build(BuildContext context) {
final query = useQueryPeopleList();
final parsedData = query.result.parsedData;
return parsedData == null
? Container()
: Column(children:
parsedData.people.map(p => PeopleLabel(p)),
);
}
}

This is just more readable code!

I hope you enjoy these changes as much as I do. These are ultimately driven by issues I’ve faced when developing apps. If you are experiencing any similar hurdles to your developer productivity caused by the libraries, please don’t hesitate to reach out!

--

--