P2 - Implementing GraphQL Queries and Mutations

Kush MunotKush Munot
9 min read

GraphQL Queries

Imagine you're ordering food from a restaurant. Instead of ordering a fixed set menu, you can pick exactly what you want from the menu. GraphQL works similarly for fetching data from a server.

Here's how it works:

  1. Query Structure: You build a query by specifying the data you need in a structured format. This structure looks like a tree, where each branch represents a specific piece of data you want.

  2. Fetching Data: You send this query to the server. The server understands your request and returns only the exact data you asked for, avoiding unnecessary information.

Example:

Let's say you have a GraphQL API for a bookstore. You want to fetch information about a specific book with id as 123456, and in response you need fields like its title, author, and publication year. Here's how your query would look:

{
  book(id: "123456") {
    title
    author {
      name
      birthDate
    }
    publicationYear
  }
}

GraphQL Mutations

In GraphQL, a mutation is a type of query used to make changes to the data on the server, such as creating, updating, or deleting data. The mutation includes fields that specify the data to be changed, the operation to be performed (create, update or delete) and also can include arguments to specify the specific data to be affected.

Think of them as actions that change the state of your data. In our bookstore example, mutations could be used to add a new book, update an existing book, or delete a book.

Add Operation Mutation

mutation {
  addBook(
    title: "The Hitchhiker's Guide to the Galaxy"
    author: "Douglas Adams"
    publicationYear: 1979
  ) {
    title
    author
    publicationYear
  }
}

This Mutation tries to add a new book with title, author and publication year as fields.

Update Operation Mutation

mutation {
  updateBook(
    id: "123"
    title: "The Harry Potter"
  ) {
    title
  }
}

This operation tries to modify the book title of a book with id as 123 rest all parameters are same like author and publication year.

Delete Mutation

mutation {
  deleteBook(id: "123")
}

This will delete a book with id as 123.

Chaining and Batching of Mutations

In GraphQL, batching and chaining are techniques used to optimize multiple mutations into a single request, improving performance and reducing network overhead.

Batching Mutations:

Batching involves combining multiple independent mutations into a single request. This is especially useful when you need to perform several unrelated operations, such as creating multiple records or updating multiple fields.

How it works:

  1. Multiple Mutations: You define multiple mutations, each with its own arguments and return values.

  2. Single Request: You send a single request to the GraphQL server, containing all the mutations.

  3. Server-Side Processing: The server processes the mutations in a batch, often executing them in parallel or in a transaction.

  4. Single Response: The server returns a single response containing the results of all the mutations.

Consider an example of Quora Integrating GenAI or LLMs in their Business and the very first answer for each question will be answered by AI then the mutation woulf Look like -

mutation {
  createQuestion(question: "What is Sieve of Eratosthenes") {
    id
    user
    question
  }
  createAnsByQuanti(question: "What is Sieve of Eratosthenes") {
    id
    user
    question
    answer
  }
}

This helps in triggering potentially two API calls in one go and trying to minimize the API calls, improving performance, lowering costs.

Continuing our Expense Tracker Project

Docker Setup

We will be using our MongoDB in a Docker Instance. For specifying our container details we will need a docker-compose.yml file setup in our root directory.

NOTE - Make sure you have Docker Desktop installed in your local machine. If you are facing any problems or need a guide for installation please refer this installation video - Windows and MacOS

version: '3.8'

services:
  mongo:
    image: mongo:latest
    container_name: mongo-graphql
    ports:
      - '27017:27017'
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
      MONGO_INITDB_DATABASE: mongodb-graphql
    volumes:
      - mongo-data:/data/db
volumes:
  mongo-data:

Then in the terminal you can run docker-compose up command to get your MongoDB instance up and running.

Accessing and Managing Your MongoDB Database

  1. Accessing the MongoDB Shell:

    To interact with your MongoDB database, you'll need to use the MongoDB shell. Here's how to access it:

     docker exec -it mongo mongosh -u root -p example --authenticationDatabase admin
    

    This command will start an interactive MongoDB shell session. You'll be logged in as the root user with the password "example."

  2. Listing Databases:

    To see all the databases available in your MongoDB instance, run the following command in the MongoDB shell:

     show dbs
    
  3. Selecting a Database:

    To work with a specific database (in this case, "mongodb-graphql"), use the use command:

     use mongodb-graphql
    
  4. Listing Collections:

    To see the collections within the "mongodb-graphql" database, run:

     show collections
    
  5. Querying Data:

    To retrieve data from a collection, use the find() command:

     db.items.find()
    
  6. Deleting Data:

    To delete all documents from a collection, use the deleteMany() command:

     db.items.deleteMany({})
    

**NOTE- For more commands you can refer this Official Documentation

Adding Queries and Mutations

Schema Creation

We have created a schema Folder and 3 files in it, in our previous blog trail when we were setting up the Project Structure.

typeDefs.ts (GraphQL Schema)

This file will define the GraphQL schema, including queries and mutations. Using gql from Apollo Server, you can establish the structure and required fields for each request. This keeps your queries and mutations organized and clearly defined in one place.

Query

We have two Queries

  1. getExpenses: This will help us retrieve all the expenses in the database. it will return us an Array of type Expense.

  2. getExpense(id: ID!) : This Query will help us retrieve a specific record with that mentioned id.

Mutations

We currently have 3 Mutations

  1. deleteExpense : This expects an id to search In the database and then delete a specific record with that same id

  2. updateExpense: This mutation expects id as compulsory field to find by id and then update the required fields queried by the user.

  3. addExpense : This mutation expects all sorts of field that you wnatt o have for a particular record.

import { gql } from "apollo-server-micro";

export const typeDefs = gql`
  type Expense {
    id: ID!
    category: String!,
    modeOfPayment: String!,
    amount: Float!,
    message: String!,
    type: String!,
  }

  type Query {
    getExpenses: [Expense]
    getExpense(id: ID!): Expense
  }

  type Mutation {
    addExpense(
        category: String!,
        modeOfPayment: String!,
        amount: Float!,
        message: String!,
        type: String!
    ): Expense

    updateExpense(
        id: ID!, 
        category: String,
        modeOfPayment: String,
        amount: Float,
        message: String,
        type: String
    ): Expense

    deleteExpense(id: ID!): Expense
  }
`;

Interfaces and Types

types.ts will define TypeScript Types and Interfaces that will be used across the application.

  1. We will be making a type for all our expense records or the expense Object

  2. Add Expense Arguments that will be needed to add an expense

  3. Update Expense Argument will have parameters as optional because we might only need to update some fields instead of updating all of then hence you will observe a question mark near the field definition.

  4. Delete a specific Expense will happen on the id field

  5. Get a specific expense will also be happening on the id field.

export type ExpenseType = {
    id: string;
    category: string,
    modeOfPayment: string,
    amount: number,
    message: string,
    type: string,
};

export interface AddExpenseArgs {
    category: string,
    modeOfPayment: string,
    amount: number,
    message: string,
    type: string,
}

export interface UpdateExpenseArgs {
    id: string;
    category?: string,
    modeOfPayment?: string,
    amount?: number,
    message?: string,
    type?: string,
}

export interface DeleteExpenseArgs {
    id: string;
}

export interface GetExpenseArgs {
    id: string;
}

Resolvers for Query and Mutations

resolvers.ts (GraphQL Resolvers)

The resolvers file will define the resolver functions for queries and mutations, using the types defined in types.ts. These help us in doing the Database level commands like save(), findById(), findByIdAndUpdate() .

import Expense from "../models/Expense";
import { AddExpenseArgs, DeleteExpenseArgs, ExpenseType, GetExpenseArgs, UpdateExpenseArgs } from "./types";

export const resolvers = {
    Query: {
        getExpenses: async (): Promise<ExpenseType[]> => {
            return await Expense.find();
        },
        getExpense: async (_: unknown, { id }: GetExpenseArgs): Promise<ExpenseType | null> => {
            return await Expense.findById(id);
        },
    },
    Mutation: {
        addExpense: async (_: unknown, { category, modeOfPayment, amount, message, type }: AddExpenseArgs): Promise<ExpenseType> => {
            const newExpense = new Expense({ category, modeOfPayment, amount, message, type });
            await newExpense.save();
            return newExpense;
        },
        updateExpense: async (_: unknown, { id, category, modeOfPayment, amount, message, type }: UpdateExpenseArgs): Promise<ExpenseType | null> => {
            const updatedExpense = await Expense.findByIdAndUpdate(id, { category, modeOfPayment, amount, message, type }, { new: true });
            return updatedExpense;
        },
        deleteExpense: async (_: unknown, { id }: DeleteExpenseArgs): Promise<ExpenseType | null> => {
            const deletedExpense = await Expense.findByIdAndDelete(id);
            return deletedExpense;
        },
    },
};

Integrating above files with Database

Creating Database Schema

As we are using MongoDB so we will have to define a schema for the database also which will help it structure the data accordingly.

In Models folder, we will create an Expense.ts file which will have the below content

import mongoose from 'mongoose';

const ExpenseSchema = new mongoose.Schema(
    {
        category: String,
        modeOfPayment: String,
        amount: Number,
        message: String,
        type: String,
    },
    { timestamps: true }
);

export default mongoose.models.Expense || mongoose.model('Expense', ExpenseSchema);

Connecting above files for Backend Setup

in the graphql root folder, we have an index.ts file this will act as the place where our server instance will be defined -

import { ApolloServer } from 'apollo-server-micro';
import mongoose from 'mongoose';
import { NextApiRequest, NextApiResponse } from 'next';
import { typeDefs } from './schema/typeDefs';
import { resolvers } from './schema/resolvers';

// Connect to MongoDB
const MONGODB_URI = process.env.MONGODB_URI as string;

if (!mongoose.connection.readyState) {
    mongoose.connect(MONGODB_URI);
}

// Create Apollo Server
const apolloServer = new ApolloServer({ typeDefs, resolvers });
const startServer = apolloServer.start();

// Next.js API route handler
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    await startServer;
    await apolloServer.createHandler({ path: '/api/graphql' })(req, res);
}

export const config = {
    api: {
        bodyParser: false,
    },
};

Running the Server ๐Ÿš€

First open your docker desktop app, then open a new terminal in the project directory and run the following command docker-compose up to start the docker instance up and running so that we can connect to MongoDB. Open a new terminal and run the next.js app by using this command - npm run dev or npm start . This will start your server at port 3000 on link - http://localhost:3000 and we can access our backend links at /api/graphql route.

Up next โญ๏ธ

in the third part we will see how we can test this running server on Postman and see the GraphQL Magic.

1
Subscribe to my newsletter

Read articles from Kush Munot directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Kush Munot
Kush Munot