GraphQL: The Case of the Missing Fields

It goes without saying that debugging issues between the frontend and backend of a codebase can be frustrating. But, it’s also like solving a mystery! As with any juicy case, it’s good to make sure the steps towards the solution are written down to help future sleuths.

When framed in this way, I always think it makes the debugging/documenting process less of a chore and more like being Sherlock Holmes. So come along, dear Watson: we have a case to investigate!

The Case

We added some new fields to a GraphQL object in the backend. This feature work was tested and approved, so we began updating the frontend to display the new information. Upon starting development, however, we noticed that the new fields weren’t showing up in the frontend’s generated version of the GraphQL logic.

Where were those fields? We could see them and interact with them in the backend, but why weren’t they joining us in the frontend?

At this point, we knew we had a debugging task on our hands, so the Chrome developer tools were opened to the Network tab. There, we could review requests and responses. Lo and behold, even if we manually added the missing fields to the frontend, our request to GraphQL was throwing a 400 error. It was claiming that the new fields we had added didn’t exist on the GraphQL object. (This is where it’s appropriate to take out a pipe and chew on it ponderously.)

So, the case was officially opened: Was it the frontend or the backend that was responsible for the missing fields?

The tricky thing about modular apps: it can be difficult to place the failure on one side or the other. As a result, I’ve found that it is better to debug one side at a time. Since we discovered the issue working with the frontend, we started there.

The frontend

The first thing that stuck out to us was that our frontend was trying to use an older version of the GraphQL objects. Why? It occurred to us that maybe something was cached. Resetting caches is always a good first step, especially since caches can show up in unexpected places. (Looking at you, Jest.) After each cache we cleared, we ran the script that regenerated our GraphQL objects to see if there were any changes. No dice.

We then moved on to deleting and regenerating the package directory (e.g., node_modules or elm_stuff). This is an especially helpful thing to do if your problem extends beyond the current example and ventures into “build error” territory. We reran the script to see if that fixed anything. Nope.

After we exhausted our methods for testing the frontend and found nothing wrong, we started investigating the backend.

The backend

With compiled languages, you get a bonus technique for finding red flags: errors before your code can even run. Since the backend was written in Scala, the first thing we did was run the compiler. Maybe something was accidentally typed into a file or a line was added on the latest pull of the feature branch we didn’t catch. It’s always a good starting point to (re-)compile if you have the capability to do so.

Unsurprisingly, though, this was not the issue. We found nothing had been changed in the files save for the new GraphQL fields we already knew were there. Even the tests were all (still) passing. With nowhere to turn, we went to a third party to figure out once and for all: which end of our codebase was truly incorrect?

Introducing a third party

AKA: Interviewing the Witness

Sometimes this is just another human to talk the issue through with. Maybe they can point out something you cannot see after spending several hours dragging your eyes down the same thousands of lines. There is also another debugging technique called “rubber ducking”, where you talk to an inanimate object about the issue you are trying to solve. In fact someone even bought a domain name to dedicate a page to the subject.

Sherlock leaning with his head next to a
skull Even super sleuths need a little help from their resident skull. Alas, poor Yorick…

However, sometimes there are tools that can also give you a gut check. In our case, we found a witness: our REST client, Insomnia. We used Insomnia because it had the feature we needed: a GUI to interact with our GraphQL models.

After a detailed interview with this witness, we noticed that its version of the schema was also missing the new GraphQL fields. We now had two independent sources claiming the same, incomplete GraphQL schema. What did this mean? It meant that there was a stale cache of some sort blocking both Insomnia and the frontend from getting the new GraphQL fields. With that, we found out we had violated one of our earlier rules about debugging: clearing all of the caches. Caches show up in the darndest of places! Turns out we didn’t clear our backend’s build cache (sbt clean).

Case Closed!

When your app is spread across more than one codebase, there are a few extra steps to take to adapt your troubleshooting methods. This is because there are more avenues for failure to occur, more moving parts to take care of. Hopefully, our case will help you solve your own debugging conundrum or will inspire you to share your own. In the mean time, Watson, let us call this mystery solved!