Supercharge Your Data Queries: A Beginner’s Guide to GraphQL

CodeStax.Ai
9 min readSep 9, 2024

--

Introduction to GraphQL

GraphQL is a powerful Query Language for APIs, and a runtime for executing those queries using a type system defined for your data. GraphQL leverages your existing code and data. It is a layer between database and client-side queries.

Why GraphQL — GraphQL vs REST API

In the traditional REST API model, you often encounter two common issues:

Overfetching:

Overfetching is receiving more data than needed. For example, You need to query on restaurant data, but if you only want restaurant names and ratings, a REST API might return the restaurant data as a whole including additional unnecessary data, such as cuisine types.

Example: Overfetching in REST

Endpoint: GET /restaurants
Response:
[
{
"id": "1",
"name": "The Gourmet Kitchen",
"cuisine": ["Italian", "French"],
"reviews": [
{
"id": "1",
"rating": 8,
"content": "Delicious food, great ambiance!",
"reviewer_id": "1"
},
...
]
},
...
]

Here, you receive more information than needed if all you want are the restaurant names and ratings.

Underfetching:

For example, to get a review with reviewer’s name, you might need to first request the review, then request the reviewer’s details using the reviewer id separately. Underfetching leads you to make multiple requests to gather all necessary data.

Example: Underfetching in REST

Endpoint: GET /reviews/1
Response:
{
"id": "1",
"rating": 8,
"content": "Delicious food, great ambiance!",
"reviewer_id": "1"
}

To get the reviewer’s name, you’d need an additional request:

Endpoint: GET /reviewers/1
Response:
{
"id": "1",
"name": "John Doe",
"badge": "Top Reviewer"
}

Overcoming Overfetching and underfetching using GraphQL

Using GraphQL, both overfetching and underfetching can be avoided as GraphQL allows you to specify exactly what data you need in a single query

Example: Avoiding Overfetching and Underfetching with GraphQL

Query:
query {
review(id: "1") {
rating
content
reviewer {
name
}
restaurant {
name
}
}
}

Response:

{
"review": {
"rating": 8,
"content": "Delicious food, great ambiance!",
"reviewer": {
"name": "John Doe"
},
"restaurant": {
"name": "Sushi World"
}
}
}

The result has exactly the same shape as the query. The GraphQL server knows exactly what data the client is asking for and the result will always be what you want.

Let us overview the GraphQL concept using a restaurant review system.

GraphQL Schema

In GraphQL, types define the shape of the data you can query.

Object Types

Let us define a type Restaurant to query on restaurant data.

type Restaurant {
id: ID!
name: String!
cuisine: [String!]!
reviews: [Review!]
}

This defines a new type called Restaurant to describe the shape of data for a “restaurant” object in your GraphQL schema.

The Restaurant type consists of several fields, each representing some property of a restaurant.

Fields and Their Types

  1. id: ID!
  • The id field is of type ID, which is a special scalar type in GraphQL. It’s often used to represent a unique identifier for an object.
  • The ! after ID means this field is non-nullable.

2. name: String!

  • The name field is a String type, representing the restaurant’s name. This field is also non-nullable.

3. cuisine: [String!]!

  • The cuisine field is an array of String values, representing the types of cuisines the restaurant serves.
  • [String!]! means this is an non nullable array of non nullable strings.

4. reviews: [Review!]

  • The reviews field is an array of Review objects. The Review type would be defined elsewhere in the schema and would represent a review associated with the restaurant.
  • [Review!] means this is an array of non-nullable Review objects. However, the array itself could be null or empty.

Query Types

The Query type in GraphQL defines the entry points for querying your data. It specifies what queries are available and what data they will return.

type Query {
restaurants: [Restaurant]
restaurant(id: ID!): Restaurant
}

In this example, the Query type provides two possible queries: restaurants and restaurant.

Fields in the Query Type

restaurants: [Restaurant]

  • This field allows you to query for a list of all Restaurant objects.
  • The return type for this query is an array of Restaurant objects — [Restaurant]

You can further specify what fields of each restaurant you want in your query.

Query:

query {
restaurants {
id
name
cuisine
}
}

Response:

"restaurants": [
{
"id": "1",
"name": "The Gourmet Kitchen",
"cuisine": [
"Italian",
"French"
]
},
...
]
}

restaurant(id: ID!): Restaurant

  • This field allows you to query for a single Restaurant object by providing its unique id.
  • The argument (id: ID!) indicates that the query requires an non-nullable id as input.
  • The return type is Restaurant, which means it returns a single Restaurant object that matches the given id.
  • If no restaurant is found with the provided id, the result could be null.

You can also specify what fields of the restaurant you want in your query.

Query:

query {
restaurant(id: "1") {
name
cuisine
}
}

Response:

{
"restaurant": {
"name": "The Gourmet Kitchen",
"cuisine": [
"Italian",
"French"
]
}
}

Mutation Type

GraphQL mutations allows you to modify data on the server, such as creating, updating or deleting records.

type Mutation {
addRestaurant(restaurant: AddRestaurantInput!): Restaurant
}

This mutation is used to add a new restaurant to the database.

Input Argument:

  • restaurant: This is an object of type AddRestaurantInput. The AddRestaurantInput will be explained later in this article.

Return Type:

  • The mutation returns a Restaurant object, which represents the newly added restaurant.

Similarly we can have mutations for delete and update too. These mutations provide a structured way to manage data, ensuring that only the necessary and valid data is passed to the server for each operation.

Input Types

As Mutations modify the data, input types are responsible for maintaining the structure of the data being sent to the server through mutations. These types define the shape and types of data that the mutation expects.

input AddRestaurantInput {
name: String!
cuisine: [String!]!
}

This input type is used to provide the necessary data when adding a new restaurant.

Fields:

  • name: A String representing the name of the restaurant. It is required.
  • cuisine: An non nullable array of non nullable Strings, representing the types of cuisine the restaurant offers.

Input types are used to ensure that the data being sent in these mutations follows the expected structure and rules.
Here’s an example mutation to add a restaurant:

mutation {
addRestaurant(restaurant: {
name: "Ocean's Delight",
cuisine: ["Seafood", "Mediterranean"]
}) {
id
name
cuisine
}
}
  • The above mutation adds a new restaurant of input type AddRestaurantInput.
  • The mutation returns a Restaurant, the fields id, name and cuisine are requested to be returned.

Response:

{
"addRestaurant": {
"id": "6794",
"name": "Ocean's Delight",
"cuisine": [
"Seafood",
"Mediterranean"
]
}
}

Resolver Functions in GraphQL

We have defined the queries and mutations that will be performed, but how are these actually executed by the server?

In GraphQL, resolver functions handle the logic for each field in a query or mutation. When a query or mutation is executed, the corresponding resolver functions are triggered to access or change the data. Let us see with an example.

Query Resolvers

The Query object defines how to resolve the data for the queries made to your GraphQL API. Each key in this object corresponds to a field in the Query type of your GraphQL schema.

Query: {
restaurants() {
return databaseClient.getRestaurants();
},
restaurant(_, args) {
return databaseClient.getRestaurant(args.id);
},
}

restaurants():

  • This resolver handles the restaurants query, which fetches all the restaurants.

restaurant(_, args):

  • This resolver handles the restaurant query, which fetches a single restaurant by its id.

Parameters:

  • The first parameter (_) is the parent object, which will be explained later in this article.
  • The second parameter (args) contains the arguments passed to the query.

Mutation Resolvers

The Mutation object defines the functions that handle the logic for modifying data in response to mutations (like adding, deleting, or updating records).

Mutation: {
addRestaurant(_, args) {
return databaseClient.addRestaurant(args.restaurant);
},
}

addRestaurant(_, args):

  • This resolver handles the addRestaurant mutation, which adds a new restaurant to the database.

Parameters:

  • The first parameter (_) is the parent object, which isn’t used in this context.
  • The second parameter (args) contains the arguments passed to the mutation, specifically the restaurant data to be added.

Return:

  • The resolver function returns the result of databaseClient.addRestaurant(args.restaurant). This result will be sent back to the client as the response to the mutation.

How Resolvers Work Together

  • When a query like restaurants is executed, the GraphQL engine calls the corresponding resolver (restaurants()), which retrieves and returns all the restaurant data.
  • For a more specific query, such as restaurant(id: “1”), the restaurant(_, args) resolver is called, and it fetches the particular restaurant based on the provided id.
  • In the case of a mutation like addRestaurant, the resolver function addRestaurant(_, args) is executed. It processes the input data, adds a new restaurant to the database, and returns the new restaurant object.

Resolvers define how the data should be fetched or manipulated based on the fields in your schema. They are the bridge between your GraphQL schema and your data sources.

Querying Nested data

In one of the previous example for underfetching, we will be querying for the review and also the reviewer’s name in a single query.

This querying of nested data involves retrieving related data objects(reviewer data) within a single query. This capability is one of the strengths of GraphQL, as it allows you to fetch related data in a single request, reducing the need for multiple API calls as you might with REST.

GraphQL uses parent resolvers to traverse the relationships between different data types.

Parent Resolvers

A parent resolver is a resolver function that operates on a parent object to fetch related data. In GraphQL, each type can have fields that are resolved using a function that receives the parent object as its first argument. This allows the resolver to access properties of the parent object, such as an ID, which can then be used to fetch related data.

Parent Resolvers for Restaurant

Restaurant: {
reviews(parent) {
return databaseClient.getReviews().filter((review) => review.restaurant_id === parent.id);
}
}

Restaurant.reviews Resolver:

Fetch all reviews associated with a specific restaurant.

Parent Argument (parent):

  • The parent object is an instance of a Restaurant that’s being resolved in the query.

Implementation:

  • parent.id provides the ID of the restaurant currently being resolved.
  • The resolver calls databaseClient.getReviews() to get all reviews and then filters them to find only those reviews where restaurant_id matches parent.id.
  • This filtered list of reviews is returned, providing the nested data for the Restaurant.reviews field.

Similarly we can query any number of related data defined in the schema.

Review: {
reviewer(parent) {
return databaseClient.getReviewer(parent.reviewer_id);
},
restaurant(parent) {
return databaseClient.getRestaurant(parent.restaurant_id);
}
}

Here Review.reviewer Resolver fetches the restaurant that a specific review is about and also Review.restaurant Resolver fetches all reviews written by a specific reviewer.

How Nested Data Queries Work

When you execute a GraphQL query that requests nested data, GraphQL first resolves the outer fields (e.g., Restaurant), and then the parent resolver is used to fetch the related nested fields (e.g., reviews within a Restaurant).

For example, consider this query:

{
restaurant(id: "1") {
name
cuisine
reviews {
rating
content
reviewer {
name
badge
}
}
}
}

Response:

{
"restaurant": {
"name": "The Gourmet Kitchen",
"cuisine": [
"Italian",
"French"
],
"reviews": [
{
"rating": 9,
"content": "Best sushi in town!",
"reviewer": {
"name": "Jane Smith",
"badge": "Foodie"
}
},
...
]
}
}
  • Step 1: The restaurant query fetches the Restaurant with id: “1”.
  • Step 2: The Restaurant.reviews resolver is called to fetch reviews related to that restaurant.
  • Step 3: For each review, the Review.reviewer resolver is called to fetch the reviewer who wrote that review.

This is where the chain of resolvers comes into play. GraphQL automatically resolves each nested field in the order they are nested in the query. Each resolver relies on the parent data, effectively creating a chain of data fetching that mirrors the structure of the query itself.

Conclusion

GraphQL’s ability to fetch data precisely by delivering exactly the data you requested, it’s flexible schema design with it’s resolver functions makes it a powerful Query Language for APIs, especially when dealing with complex, nested data. GraphQL allows you to streamline development and enhance user experiences with more efficient and effective data queries.

About the author

Janani Priya T is a Software Development Engineer with two years of experience working with a various technologies, including Node.js, Vue.js, ASP.NET, and Angular. She is passionate about learning new technologies to enhance her skills and contribute to innovative projects.

About CodeStax.Ai

At CodeStax.AI, we stand at the nexus of innovation and enterprise solutions, offering technology partnerships that empower businesses to drive efficiency, innovation, and growth, harnessing the transformative power of no-code platforms and advanced AI integrations.

But what is the real magic? It’s our tech tribe behind the scenes. If you have a knack for innovation and a passion for redefining the norm, we have the perfect tech playground for you. CodeStax. Ai offers more than a job — it’s a journey into the very heart of what’s next. Join us and be part of the revolution that’s redefining the enterprise tech landscape.

--

--

CodeStax.Ai
CodeStax.Ai

Written by CodeStax.Ai

Tech tales from our powerhouse Software Engineering team!

No responses yet