Mastering GraphQL: A Beginner's journey

Mastering GraphQL: A Beginner's journey

Hello there! And Welcome to another blog. In this blog we're gonna dive into a technology, as you already know from the title: "GraphQL". If you sometimes feel like you are getting lost in a sea of endpoints or struggling to get just the right data from the server, GraphQL is there to rescue! Imagine a world where you can ask for exactly what you need from your API, without the hassle of over-fetching or under-fetching data. That's the magic of GraphQL. And in this blog we'll dive deeper into GraphQL so that you get some idea over this topic

Introduction

In recent years, GraphQL has emerged as a powerful alternative to the traditional RESTful APIs, mainly due to its flexibility and ease of control. But for people who don't know about REST APIs or simply want to revisit it, let's get a basic understanding of RESTful APIs.

In RESTful APIs, resources are represented by URIs, and interactions with these resources are performed using standard HTTP methods such as GET, POST, PUT, and DELETE. Here's a simple example of a RESTful endpoint for fetching a list of posts:

GET /api/posts

This endpoint would typically return a JSON array containing a list of post objects:

[
  {
    "id": 1,
    "title": "Introduction to GraphQL",
    "content": "some content about graphql",
    "author": "John Doe",
    "created_at": "2024-02-27T07:00:00Z",
    "updated_at": "2024-02-27T07:00:00Z"
  },
  {
    "id": 2,
    "title": "Building a REST API with Expressjs",
    "content": "tutorial guides you through creating a REST API...",
    "author": "Gwen Smith",
    "created_at": "2024-02-26T15:00:00Z",
    "updated_at": "2024-02-26T15:00:00Z"
  },
  // ... more posts
]

While RESTful APIs have served for many years, they can often lead to issues like under-fetching and over-fetching of data in client server interactions. So basically, let's say you just wanted the title and content in the above retrieved posts, but you still got all the redundant data (obv this is over-fetching)! This creates undue load on the server, also network usage is increased and also consumes more bandwidth, leading to slower loading times, especially for users on slower internet connections. This is where GraphQL comes to rescue!

What is GraphQL?

GraphQL, which was originally developed by Facebook and was open sourced later in 2015, is a query language for APIs that allows you to retrieve exactly the data you need, from the server. Nothing more, nothing less!

It is also stateless, just like REST. This means that each request from the client (like your web browser) to the server stands on its own. The server doesn't "remember" anything about previous interactions with that specific client, and every request needs to contain all the necessary information to be understood and fulfilled.

Also it exposes only ONE endpoint to the client. Yes! I know. And that is:

POST /graphql

Lets look at how a basic GraphQL query looks like.

query{
  getPosts {
      _id,
      title,
      content
  }
}

The response will look something like this:

{
  "data": {
    "getPosts": [
      {
        "_id": "1",
        "title": "What is GraphQL?",
        "content": "This blog post explains the basics of GraphQL..."
      },
      {
        "_id": "2",
        "title": "REST API with Express js",
        "content": "tutorial guides you through creating a REST API"
      },
      // ... more such posts
    ]
  }
}

As you can see, the client specifies exactly which fields it needs, and the server responds with only that data. You can clearly feel how much control developers can have over the data, leading to more efficient client-server interactions.

There are some core concepts in GraphQL like schema, query, mutation, etc. We'll see them one by one. Also I'll also show how can you integrate everything in an express.js app. Remember there are various packages available for this. I have used graphql and express-graphql. You might simply google if you want some other approach but the crux remains the same. The core concepts remain the same.

Schema

GraphQL Schema is like a blueprint that defines the type of data that can be queried and the relationships between them. Describes what actions are possible: It tells you what you can do with that data:

  • Queries: Fetching data

  • Mutations: Changing data (creating, updating, deleting)

Also it defines relationships : It explains how different pieces of data are connected (e.g., a post has an creator, a creator can have many posts).

Sounds confusing? Let's look at an example:

type User {
  _id: ID!
  name: String!
  posts: [Post]
}

type Post {
  _id: ID! 
  title: String!
  content: String
  creator: User
}

# Query (GET)
type Query {
  getPosts: [Post]
  getPost(postId: ID!): Post
}

type Mutation {
  createUser(name: String!, email: String!): User!
  updateUser(id: ID!, name: String, email: String): User!
  deleteUser(id: ID!): User!
}

Explanation of this Schema:

  • Types: Defines the "Post" and "User" objects and their fields (like title, content, name).

  • ! means required: Fields with an exclamation mark (!) are required and can't be left blank, simply they cannot be null.

  • Relationships: The creator field on Post and the posts field on User show the relationship between the two.

  • Operation types: (Query and Mutation) Defines the entry points for fetching data. getPosts allows getting multiple posts, while getPost lets you find one by its postId. createUser, updateUser and deleteUser are mutations. Mutations are used to modify data on the server, such as creating, updating, or deleting data.

Queries

In GraphQL, queries are the mechanism for fetching data from an API. GraphQL queries give clients full control over the data they receive. You can ask whatever you want. Can also request nested fields. This flexibility allows developers to optimize data fetching, reduce over-fetching and under-fetching, and tailor responses to the specific needs of each client.

Lets see an example:

If this is the schema:

type Book {
  _id: ID!  
  title: String!
  author: String!
  genre: String
}

type Query {
  getAllBooks: [Book]!
  getBookByTitle(title: String!): Book
}

type Mutation {
  createBook(title: String!, author: String!, genre: String): Book!
}

Here are some graphql queries we can write:

query {
  getAllBooks {
    title 
  }
}
# this will return all books just with their title
query {
    getBookByTitle(title: "The Richest Engineer") {
        title,
        author,
        genre
    }
}
#this will give title, author and genre of the specific book.

Clearly, there is no over-fetching, you get exactly what you asked for!

Mutations

While queries are used to fetch data from a GraphQL API, mutations are used to modify data on the server. They can be used to create, update, or delete resources.

Mutations follow a similar syntax to queries but are distinguished by their use of the mutation keyword. Like queries, mutations allow clients to specify exactly what data they need to perform the operation, ensuring that only the necessary fields are returned in the response.

mutation {
  createBook(title: "Mugen Train", author: "XYZ", genre: "Action") {
    _id
    title
    author
  }
}
#this is a query for creating a book (a mutation query -> look for schema)
#Here we're sending the specified fields and after successfull creation of book
#we'll get _id, title and author of the book created.
  • Mutations use the mutation keyword instead of query.

  • They typically have arguments corresponding to the data needed for the operation (e.g., book details for creation/update).

  • They often return the modified data (like the newly created or updated book) as a response.

How I used GraphQL in an express.js app?

I have used graphql and express-graphql package. First install them using npm install command.

Create a graphql folder -> inside that 2 files namely schema.js (for defining schema) and resolvers.js (will explain shortly what are resolvers).

//graphql/schema.js

const { buildSchema } = require("graphql");

module.exports = buildSchema(`
    type Post {
        _id: ID!
        title: String!
        content: String!
        creator: User!
        createdAt: String!
        updatedAt: String!
    }

    type User {
        _id: ID!
        name: String!
        email: String!
        password: String
        posts: [Post]!
    }

    input UserInputData {
        email: String!
        name: String!
        password: String!
    }

    input PostInputData{
        title: String!
        content: String!
        imageUrl: String!
    }

    type PostData {
        posts: [Post]!
        totalPosts: Int!
    }

    type RootQuery {
        getPosts(page: Int): PostData!
        getPost(postId: ID!): Post!
        user: User!
    }

    type RootMutation {
        createUser(userInput: UserInputData): User!
        createPost(postInput: PostInputData): Post!
    }

    schema {
        query : RootQuery
        mutation: RootMutation
    }

`);

//we can also define the query and mutation like this:
// type Query {
//     getUsers: [User]
//     getPosts: [Post]
// }

// type Mutation {
//     createUser(userInput: UserInputData): User
// }

Resolvers

If you have used REST, you probably know about controllers. Each controller function is associated with an endpoint in a REST API. Controllers are basically the logic which gets execute whenever an endpoint gets hit. So for signing up a user, you would need some logic, right? So that all is written inside a controller function. Resolvers are the same!

Lets say a client sends a GraphQL query, specifying the exact data they need. The graphql server finds the corresponding resolver function. Resolver is responsible for knowing how to get the data for its specific field. This could involve:

  • Fetching from a database

  • Calling a different API

  • Transforming or calculating data on the fly

Resolvers return the fetched data to the GraphQL server, which assembles the response and sends it back to the client.

Example:

//graphql/resolvers.js 

module.exports = {
  createUser: async function (args, req) {
        // code for creating a user
        //....
        //return json response
    }

    getAllPosts: async (args, req) => {
        // code for getting all posts
        //req to database
        //return json response
    }
}

And In you app.js you can configure your schema and resolvers:

//app.js

const { graphqlHTTP } = require("express-graphql");
const schema = require("./graphql/schema");
const resolvers = require("./graphql/resolvers");


//code...

//or app.post(...)
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: resolvers,
    graphiql: true, //go visit localhost:PORT/graphql and look for the magic!

    customFormatErrorFn(err) {
        //if any error is thrown in resolvers, it'll come here

        //handle error and return some appr. response

      return {
        status: statusCode,
        message: message
      };
    },
  })
);

//more code...

GraphiQL is an interactive development environment for building and testing GraphQL queries, mutations, and subscriptions. It provides a user-friendly interface that allows developers to explore the schema, compose queries, and visualize responses in real-time.

In the frontend you can find various packages for sending graphql queries to the server. If you do not want to use any specific package, here's how you can send requests to your server. I have used React.js in the frontend.

const graphqlQuery = {
      query: `
            getPosts {
              posts{
                _id,
                title,
                creator{
                  name
                },
            },
            totalPosts
        }
    }`
 };

//send the request
fetch("http://localhost:8000/graphql", {
      method: "POST",
      headers: {
        Authorization: "Bearer " + token, //if you want to pass the token
        "Content-Type": "application/json",
      },
      body: JSON.stringify(graphqlQuery),//server parses the query expression 
                                        //and calls the appr. resolvers
    })
    .then(res => {
        //handle the response
    })
    .catch()

Core concepts

  • Graphql is stateless and client independent.

  • Only exposes one endpoint POST /graphql.

  • A graphql API is made up of queries and mutation.

  • Offers higher flexibility than REST APIs due to the custom query language.

It's not that REST APIs are the bad guy here. Both have their uses. e.g. RESTful APIs can be great for static data requirements like in case of file uploads, scenarios where you need the same data all the time.

But if its not the case, Graphql can give you more flexibility. Both of them can be implemented with any framework, any server side language. They are not limited to just Node.js and React.js.


That is it for this blog. I am sure you have got some basic idea of GraphQL and how you can implement it in your next project!

Thank you for staying till the end!