This article was originally posted on my blog: ctrl-y.
A Spanish version of this article can also be found in ctrl-y.
On the Side Note section at the end of each article, I like to share what I was listening to while writing machine or human words. I ❤️ music so I spend time (sometimes a bit too much) searching for, or creating, long playlists so I can listen to them while I'm working.
In the past months, I have been working with the magic package of React, Apollo, GraphQL, and Prisma. I say the magic package because learning these four layers of programming, provides you with the skills required to create apps from user click (React and Apollo) to handle and save information in the database (Prisma). They say that building this makes you a Full Stack developer.
I want to focus this article on GraphQL and Prisma because even thou I was already familiar with back-end languages, I only copied code. In other words, I didn’t understand how the back-end works.
I reached a massive roadblock when I started working with the search function for the digital product I am helping to develop.
At first, it’s easy to build functions because tutorials, even with complex topics, offer basic examples of how to create them. But what happens when basic examples are not enough?
The articles I read about filtering in the database, describe examples that consider the search of the terms inside one query. Sometimes these examples included nested data.
My roadblock, what happens when I need to filter more than one query?
I want to share with you my incorrect approach when working on this task. It took me a while to figure out why this approach was wrong, and what is a better approach when searching for data in different queries.
Let’s make a quick recap of how ReactJs, Apollo, GraphQL, and Prisma work together.
The ReactJs submit function must manually query the Apollo Client. Apollo communicates with GraphQL resolvers to request and receive information from the database using the GraphQL query language.
It is a query language for APIs that handles the website’s data. It is where the data schemas and resolver functions are defined.
GraphQL is a language. Therefore, we need to instruct which data will it filter and receive. These functions written in NodeJs (that is why usually resolver function files are Js and not Graphql) are called resolvers.
It is web developer-friendly because it gives the ability to specify the data that needs to be received by the website instead of collecting everything available on the endpoint.
It is an interface between GraphQL and the database.
It is where the different queries from the database are defined and implemented.
Define queries in Prisma. After a couple of commandos, Prisma autogenerates a file with the GraphQL data schema in Prisma. The data is filtered from the database using this schema.
Let’s make some assumptions before going the wrong path for the search implementation.
We are making multiple query searches on a database for a small business that handles farming equipment.
GraphQL’s and Prisma’s schemas are defined. An example of a data schema on Prisma data model (prisma/datamodel.prisma):
Next, the example of the GraphQL resolver schema (server/src/schema.graphql) with Prisma’s data schema imported:
Where the resolver company requires (! = “it is required so it can’t be null”) the id argument to return data type Company.
And the users and equipment resolvers do not require an argument to return a value. The resolver users should return an array of User data types, while the equipment resolver should return a data array of Equipment data types.
Also, the equipment resolver should return an array of no null values. Neither the data array can itself be null.
So the sever/src/resolvers/Query.js file:
The resolver for the company query must receive an id type argument. These resolvers should return the query data filtered by id.
The resolvers for the users and equipment queries do not require an argument to return filtered data. Therefore, it will return a data array of all the records of the requested query.
An example of an equipment query requested by Apollo:
An example of a company query requested by Apollo:
An example of a users query requested by Apollo:
Remember the company query we defined earlier? An id argument filters the company query. That’s a basic example of how to filter data in GraphQL. It’s easy to find documentation on this topic.
An example of the resolver for the company query if we want to search the id argument in each of the Company type items:
Most of the articles about filtering on GraphQL finish up to this point, where we can only filter data on a query.
But, what happens if I need to search for more than one query and the data is nested?
My instinct told me, after reading documentation, that I should filter queries editing the already defined resolvers (remember the previously stated examples).
But, the idea of performing multiple requests to the database seemed heavy. The “edit the resolvers” approach requires that the front-end triggers a network request for every filtered query at the moment the search is submitted.
I admit that the implementation of the search feature was after other elements in the app had been validated, so I inhibited myself from editing the resolvers that already worked.
Also, trying to consider that code repetition is a waste, fueled my reasoning that creating a query from the queries I needed to filter was the go-to approach for handling the server.
So, I defined my new query, Feed, on the Prisma data type schema (prisma/datamodel.prisma) as:
Import the query Feed to the GraphQL resolver schema after auto-generating the Prisma file, ** (server/src/schema.graphql) ** as:
Also, the auto-generated Prisma file provides the conditions you can use as a developer to filter data on Prisma. The feed resolver (sever/src/resolvers/Query.js) below:
Finally, the query requested from the front-end:
Some received data from the three queries as a result of these composite codes. But, the data was incomplete. I could access the nested data from the contacts and company inside the equipment. I could only see what type of data I was receiving, but the value was null.
Looking through the documentation and frequently asked questions about this topic, the primary reason why invalid data is received when you should be able to access it is that because the data structure you are passing is incorrect.
But, what a correct schema for Feed would look like if we are already passing the right data types?
Queries have records attached to them in the database. An assigned id identifies these records. To assign these ids, one needs to create a function that allows the query to mutate (create a new id) and can save the data attached to the database record.
So, you need to create a Feed mutation and connect the data queries for User, Company, and Equipment. It means that you need to create and save on the database a record of type Feed every time you perform a search.
Imagine how loaded would be the database if, for every search you make, you save the query combination you defined as Feed!
Besides the fact that it would be too expensive and unnecessary to keep something like that, and we are not taking full advantage of the power of GraphQL.
It’s obvious now, but to filter data queries is done in…. ta-da, the same data queries. So my instinct of adding the capacity to filter on the already defined resolvers was a better approach.
When we search multiple queries on the database, the front-end requests the data query individually. Apollo handles the requests to the GraphQL resolvers, so the response is as lean as the developer needs.
We do not need to define a new query to perform a search on multiple queries. So, let’s go back and re-define prisma/datamodel.prisma as:
Also, let’s go back and edit the GraphQL resolver schema (server/src/schema.graphql). Delete the Feed type definition and add the filter parameter to each query. The filter parameter is the data string the user wrote as the input for the search function:
Note: I assigned filter as an optional parameter so I can use these resolvers on other components.
On the GraphQL resolvers (sever/src/resolvers/Query.js), where the magic happens, we eliminate the feed resolver and edit the other resolvers, so each data query accepts the filter argument:
And the data queries that front-end requests:
This approach to filtering multiple queries should result in receiving all the data requested, including the nested data.
Ufff, we finished!
Thanks for reading all of this! 🙌🎉
It took me a while to come to this conclusion. If I can help you save a couple of hours or days by reading this article, that would be a mega mission accomplished!
I wrote this article while listening to the Ladybug Podcast. It is rare for me to listen to a podcast while writing, but the topics the girls talked about motivated me to write this article.
Even thou I binge listened to the podcast, I am sharing the episode that is about GraphQL, which I also listened to while writing this article.