Understanding and Implementing GraphQL

Understanding GraphQL: Why Do We Need It?

GraphQL is a query language for APIs and a runtime for executing those queries using a type system defined for your data. It offers a flexible and efficient alternative to REST APIs, solving several problems faced by developers when building and consuming APIs.


Why Did We Need GraphQL?

To understand the need for GraphQL, let’s look at some common issues in REST APIs:

1. Overfetching Data

Imagine a scenario where the client needs only the title of a to-do item.
In a REST API, when a client requests data, the server responds with a full object. For example:

{
  "id": 1,
  "userId": 1,
  "title": "Learn GraphQL",
  "completed": false
}

Even though the client only requires the title, the server sends the entire object, wasting bandwidth and resources.

2. Underfetching Data

What if the client needs both the title of a to-do item and the user who created it?
In REST, this typically requires two separate API calls:

  • One to fetch the to-do items.

  • Another to fetch the user data based on the userId in the to-do response.

This increases the complexity on the client side and adds extra latency due to multiple requests.


How Does GraphQL Solve These Problems?

GraphQL allows clients to request exactly the data they need by defining queries.

Example: Fetching Titles

A GraphQL query can specify only the required fields, such as the title of to-do items:

query {
  gettodos {
    title
  }
}

This eliminates overfetching since the server sends only the title fields.

Example: Fetching Nested Data

GraphQL supports nested queries, enabling clients to fetch related data in a single request.
For example, to fetch a to-do item and the details of the user who created it:

query {
  gettodos {
    title
    user {
      name
      email
    }
  }
}

This query combines multiple REST endpoints into one efficient request, addressing the underfetching problem.


Setting Up a GraphQL Server

Below is an example of a simple GraphQL server implemented in Node.js using Apollo Server and Express. It uses mock APIs from jsonplaceholder.typicode.com for demonstration.

import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import axios from 'axios';

async function StartServer() {
  const app = express();
  app.use(cors());
  app.use(bodyParser.json());

  // Define the schema and resolvers for GraphQL
  const server = new ApolloServer({
    typeDefs: `
    type User {
        id: ID!
        name: String!
        username: String!
        email: String!
        address: String!
        phone: String!
        website: String!
        company: String!
    }

    type Todo {
        id: ID!
        user: User
        title: String!
        completed: Boolean!
    }

    type Query {
        gettodos: [Todo]
        getusers: [User]
        getuserbyid(id: ID!): User
    }
    `,
    resolvers: {
      // Define how each type of data is resolved
      Todo: {
        user: async (parent) => {
          try {
            const user = await axios.get(
              `https://jsonplaceholder.typicode.com/users/${parent.userId}`
            );
            return user.data;
          } catch (error) {
            console.error('Error fetching user:', error);
            throw new Error('User not found');
          }
        },
      },
      Query: {
        gettodos: async () => {
          try {
            const todos = await axios.get(
              'https://jsonplaceholder.typicode.com/todos'
            );
            return todos.data;
          } catch (error) {
            console.error('Error fetching todos:', error);
            throw new Error('Failed to fetch todos');
          }
        },
        getusers: async () => {
          try {
            const users = await axios.get(
              'https://jsonplaceholder.typicode.com/users'
            );
            return users.data;
          } catch (error) {
            console.error('Error fetching users:', error);
            throw new Error('Failed to fetch users');
          }
        },
        getuserbyid: async (_, { id }) => {
          try {
            const user = await axios.get(
              `https://jsonplaceholder.typicode.com/users/${id}`
            );
            return user.data;
          } catch (error) {
            console.error('Error fetching user by ID:', error);
            throw new Error('User not found');
          }
        },
      },
    },
  });

  await server.start();

  // Mount the GraphQL middleware
  app.use('/graphql', expressMiddleware(server));

  app.listen(4000, () => {
    console.log('Server is running on port 4000');
  });
}

StartServer();

Output:


Key Features of This GraphQL Server

  1. Type Definitions

    • Define the structure of your data with typeDefs.

    • Example: Todo and User types represent to-do items and users, respectively.

  2. Resolvers

    • Specify how to fetch the data for each type.

    • Example: Fetch a user from jsonplaceholder when resolving the user field in Todo.

  3. Queries

    • Implement GraphQL queries like gettodos, getusers, and getuserbyid for fetching to-dos, all users, and a specific user by ID.

Benefits of Using GraphQL

  1. No Overfetching or Underfetching
    Clients get exactly the data they need.

  2. Nested Queries
    Fetch related data (e.g., a to-do and its user) in a single request.

  3. Improved Developer Experience

    • A strongly typed schema ensures that clients and servers agree on the data structure.

    • Tools like GraphiQL make testing and exploring APIs easy.