In the last cycle I spent quite some time using Apollo client to interact with our GraphQL service from React app. Here are some gotchas and nice features I learnt.
Typing system in GraphQL
I first got to know how custom scalar types in GraphQL work when pairing with Nathan on submitting data to graphQL.
This discovery started when we were trying to submit data to GraphQL that has Int64 type, and we keep getting expected an Int64 while the data we had was of TypeScript number type. Turned out that Int64 was a custom scala type that our GraphQL microservice defined.
Then we discovered that there are only a limited set of default scalar types in GraphQL:
- Int
- Float
- String
- Boolean
- ID (The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.)
And Int64 is our custom scalar, and its serialization/deserialization rule is defined inside our GraphQL microservice.
Inside schema.graphql, these custom scala are defined by mapping to model files:
1 | """ |
I just had a go with what the type really is when I submit a number:
1 | func UnmarshalInt64(v interface{}) (Int64, error) { |
The console output shows that the input v is of type json.Number 🤔 And we did not define how this type unmarshals to Int64 scalar.
Solution
If we need to transfer an Int64, we need to transfer it as a string.
Although here because we do not need this big of a number, we changed the expected input type on GraphQL side to be Int.
Lessons learnt
GraphQL:
scalaris the “minimum”/“leaf” type for GraphQL that is used to build up object- there is a limited set of default scalar, and the others are customer defined. Keep in mind that we do not have 100% control of how the endpoint is called.
- When choosing the type for a GraphQL schema, try to choose the simplest scalar data that does the job
GraphQL Client:
- Do not make assumptions of how the scalar types are used. The custom scalar is available via the GraphiQL docs, good to have a look. e.g.
Use the InMemoryCache feature of Apollo client to save network round trips on CRUD operations
Three options to make apollo update the cache:
1. If there is a field that has a 1:1 relationship to the data, apollo can do the cache update itself
Got this trick from Greg, the keys are:
- to help apollo find out who the
keyFieldsis for this data type:
1 | cache: new InMemoryCache({ |
- make sure the response that updates the data has the correct data structure of this type (i.e. when
delete, returningdeleted: trueis not good enough, need to return the full data structure withstatusset toOFF)
then whenever there are this data type coming in from a response of a useQuery or useMutation call, apollo knows where to update it.
2. use cache.modify to do a “surgical” update on the data
e.g. To delete a field’s value:
1 | cache.modify({ |
3. use cache.readQuery and cache.writeQuery to operate on the cache just like we interact with gql server.
Most expressive.
Use standard GraphQL queries.