Introduction and Overview

In this lesson, we will build upon your existing GraphQL skills by introducing advanced query and mutation arguments. These techniques will enable you to create more flexible and powerful APIs. Advanced arguments allow for better precision in the data you request and the operations you perform.

Defining Advanced Schema with Arguments

Let's start by defining our GraphQL schema. The schema is a blueprint for the structure of your API.

Below is the schema we will use:

const typeDefs = `#graphql
  type Book {
    id: ID!
    title: String!
    author: String!
    publishedDate: String
    genre: String
  }

  type Query {
    books(genre: String, author: String): [Book]
  }

  type Mutation {
    addBook(title: String!, author: String!, publishedDate: String, genre: String): Book
  }
`;

export { typeDefs };

In this schema:

  • The Book type defines the structure of a book object.
  • The Query type has a books field that accepts two optional arguments, genre and author, to filter books.
  • The Mutation type has an addBook field that accepts arguments to add a new book to our dataset.
Resolvers: Filtering Data with Query Arguments

Resolvers fetch the data specified in the schema. Here, we will write resolvers to handle the books query with filtering capabilities:

import { v4 as uuidv4 } from 'uuid';

const books = [
  { id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien', publishedDate: '1937', genre: 'Fantasy' },
  { id: '2', title: '1984', author: 'George Orwell', publishedDate: '1949', genre: 'Dystopian' },
  // More books...
];

const resolvers = {
  Query: {
    books: (_: unknown, { genre, author }: { genre?: string; author?: string }) => {
      return books.filter(book =>
        (genre ? book.genre === genre : true) &&
        (author ? book.author === author : true)
      );
    }
  },
  Mutation: {
    addBook: (_: unknown, { title, author, publishedDate, genre }: { title: string; author: string; publishedDate: string; genre: string }) => {
      const newBook = { id: uuidv4(), title, author, publishedDate, genre };
      books.push(newBook);
      return newBook;
    }
  }
};

export { resolvers };

In this resolver:

  • The books query accepts genre and author as optional arguments.
  • It filters the books array based on these arguments.
  • If an argument is provided, it filters by that argument; otherwise, it includes all books.

Notice how we use the uuidv4 function to generate a unique identifier for each new book added to the dataset. This ensures that every book has a distinct ID, which is crucial for identifying and managing individual entries in the database.

Setting Up Apollo Server 4

To use Apollo Server 4, you need to set up the server with the schema and resolvers we defined above.

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './schema'; // Assuming the schema is in a file named schema.ts
import { resolvers } from './resolvers'; // Assuming the resolvers are in a file named resolvers.ts

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

startStandaloneServer(server, {
  listen: { port: 4000 },
}).then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

This setup initializes Apollo Server 4 with the specified type definitions and resolvers and starts the server on port 4000.

Querying with Filter

For example, querying for books by a specific author:

query {
  books(author: "J.R.R. Tolkien") {
    title
    author
  }
}

This would return:

{
  "data": {
    "books": [
      {
        "title": "The Hobbit",
        "author": "J.R.R. Tolkien"
      }
    ]
  }
}
Mutations: Adding New Entries with Arguments

Next, we handle mutations to add new entries with the resolver already detailed:

Example mutation request:

mutation {
  addBook(title: "1984", author: "George Orwell", publishedDate: "1949", genre: "Dystopian") {
    id
    title
    author
  }
}

Response:

{
  "data": {
    "addBook": {
      "id": "unique-id",
      "title": "1984",
      "author": "George Orwell",
      "publishedDate": "1949",
      "genre": "Dystopian"
    }
  }
}
Fetching Data Using Queries and Mutations in TypeScript: Fetching Books

Finally, let's see how to fetch data using the fetch API in TypeScript. We'll start by querying the list of books and then adding a new book.

import fetch from 'node-fetch';

const fetchBooks = async () => {
  const query = `
    query {
      books {
        title
        author
        publishedDate
        genre
      }
    }
  `;

  const url = 'http://localhost:4000/';

  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ query }),
    });

    const data = await response.json();
    console.log('Books:', JSON.stringify(data, null, 2));
  } catch (error) {
    console.error('Error:', error);
  }
};

fetchBooks();
Adding Data Using Queries and Mutations in TypeScript: Adding a Book
Summary and Next Steps

In this lesson, we covered how to enhance your GraphQL API by using advanced arguments in queries and mutations. You learned how to:

  • Define a GraphQL schema with advanced arguments.
  • Implement resolvers to handle these arguments.
  • Set up Apollo Server 4 to execute your queries and mutations.
  • Perform queries and mutations via the fetch API in TypeScript.

This knowledge allows you to create more flexible and powerful GraphQL APIs. Now, it's time for you to practice these concepts with the exercises that follow, which will help you solidify your understanding and build confidence in using advanced GraphQL features.

Good luck, and happy coding!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal