Sign in
Log inSign up
Queries in GraphQL

Queries in GraphQL

Leonardo Maldonado's photo
Leonardo Maldonado
·Feb 15, 2019

This is the second tutorial in the series about GraphQL. In this tutorial, we are going to learn more about how GraphQL works. First, we’re going to start with Types, and then dive deep into Queries.

Getting Started

In this series, we’re not going to learn how to set up a GraphQL server. We’re going to learn more about how queries work in GraphQL. If you don’t know how to set up a GraphQL server, that's not going to be a problem at all, you can simply clone this repository which I made just for this series.

When you clone the repo, just run npm install or yarn install. After that, start the server by running npm start, and go to localhost:4000/playground.

playground

If you see this screen, this means you’re running the playground successfully. The GraphQL Playground is an IDE for working with GraphQL APIs to write queries, mutations & subscriptions with features like auto-completion, error handling and team collaboration. It makes it possible for us to easily test our APIs, see if there are any errors, and so on.

Now that we’re running it, we’re ready to learn more about types, queries, and mutations.

Go to our graphql folder inside src, and you will see that we have two folders: Author and Book. These two folders are going to contain our types and resolvers.

For each folder, we’re going to have two files: resolvers.js and typeDefs.js.

Basically, the resolvers.js is going to contain all of our code such as queries, mutations, and subscriptions.

The typeDefs.js is used only for the types needed to create our schema. So, every time you hear someone talking about resolvers or typeDefs in GraphQL, remember that.

typeDefs = a file that contains our types.

resolvers= a file that contains queries, mutations, and subscriptions.

Types in GraphQL

To write our first set of queries, we need to know a little bit about types, since we’re going to use it very often in GraphQL.

What are types in GraphQL and why do they matter?

Types in GraphQL are custom objects that define the way your API is going to look. Pretty simple! You can define as many types as you want, for every part of your API.

All types must have fields, so you need to pass the exact fields that you want, and define the type of data that those fields will have.

Let's write our first Type in GraphQL.

Inside the Author folder, you’re going to see that we already have one type called Author, and we’re going to add some new fields. This type looks like this:

    const typeDefs = `
        type Author {
            _id: ID!
            firstName: String!
            lastName: String!
            age: Int!
            books: [Book!]!
        }
    `;

    export default typeDefs;

Author type pretty self-explanatory. By reading it you can understand that it is an Author type, and it has a few fields. Each field must have a type. You can define String, ID, Int, Date, or even other Custom Types as well.

The books field contains an array of Book, and this Book is a custom object type that we’re going to define later. Each Author can have one or more Book and that’s why you should put it inside brackets.

Those ! at the end of each field mark it as a non-nullable field, which means that the field must return a value as a result of a query. In our Author type we have marked every field as non-nullable so that each field returns a value.

Now that we defined our Author type, we’re going to define the other type that we’re going to need which is the Book type. Go to the Book folder inside the graphql folder, and you will see that we have a Book type. We’re going add some fields to this type, like this:

    const typeDefs = `
        type Book {
            _id: ID!
            title: String!
            description: String!
            language: String!
            author: Author!
        }
    `;

    export default typeDefs;

Each Book is going to have a title, description and a language, and all of them should be a String. Each Book is also going to have an Author, and we defined the type for it a few moments ago. It's pretty similar to the Author type and doesn’t have much difference besides the fact that each Book will have only one Author.

Now that we know how to write types, we’re going to learn more about Queries and write our first Query type.

Queries

The only way we can get data in GraphQL is through Queries. We need to specify what we need to get and from where we need to get the data. It’s not similar to REST because in a REST API we get a fixed structure of data. However, in GraphQL we can get just the data that we want. This solves a lot of problems for us, especially the over-fetching or under-fetching of information.

To make our queries, we need to define the types, after which we’re going to write the code for our queries. So, don’t get confused -- We’re just defining the type of queries and what queries we’re going to have.

So, inside the Author folder, inside the Query type, we’re going to add a query called getAllAuthors, and after that our type is going to look like this:

    type Query {
        getAuthor(_id: ID!): Author!
        getAllAuthors: [Author!]!
    }

Our queries live inside a Query type. So, every time we need to add or remove a query, we will directly go to this type.

Each query can have one or more arguments. In this example we made a query called getAuthor that needs an _id to return a specific Author. The other query is called getAllAuthors. We’re not going to pass any argument as it returns all authors.

Now, inside the Book folder, inside the Query type, we’re going to add another query called getAllBooks, and after that our type is going to look like this:

    type Query {
        getBook(_id: ID!): Book!
        getAllBooks: [Book!]!
    }

Pretty similar to the Author queries. The query getBook needs an _id to return a specific Book. The other query is called getAllBooks. We’re not going to pass any argument since we’re just going to return all the books that we have.

Now, we’re going to write the code for our queries. First, go back to the Author folder, and this time go to resolvers.js. We’re going to write our first set of queries. Inside that file paste the following code:

    import Author from "../../models/Author";
    import Book from "../../models/Book";

    const resolvers = {
        Query: {
            getAuthor: async (parent, { _id }, context, info) => {},
            getAllAuthors: async (parent, args, context, info) => {}
        },
    };

    export default resolvers;

We just defined the structure for our resolvers. Now we’re going to write the code. In this series of tutorials I’m going to work with MongoDB (using Mongoose npm module), but with GraphQL you can use any database that you want such as PostgreSQL, MySQL, etc.

So, inside our getAuthor function, put the following code:

    return await Author.findById(_id)
            .populate()
            .then(author => author)
            .catch(err => err);

We’re going to return a specific Author. So, in the parameters of the function, instead of args, you should pass _id. Our modified function is going to look like this:

    getAuthor: async (parent, { _id }, context, info) => {
        return await Author.findById(_id)
            .populate()
            .then(author => author)
            .catch(err => err);
    },

Now, we’re going to write another query called getAllAuthors which is going to return all authors we have. Put the following code inside the function:

    return await Author.find()
            .populate()
            .then(authors => authors)
            .catch(err => err);

Now our function should look like this:

     getAllAuthors: async (parent, args, context, info) => {
            return await Author.find()
            .populate()
            .then(authors => authors)
            .catch(err => err);
    }

Inside the resolvers (but not inside the Query object), write the following code:

    Author: {
        books: async ({ _id }, args, context, info) => {
            return await Book.find({ author: _id });
        }
    }

With this, we make it possible to query the Author from a specific Book when making queries. If we don't specify it and try to get the Author from Book, it will return an error.

Now, our file should look like this:

    import Author from "../../models/Author";
    import Book from "../../models/Book";

    const resolvers = {
        Query: {
            getAuthor: async (parent, { _id }, context, info) => {
                return await Author.findById(_id)
                    .populate()
                    .then(author => author)
                    .catch(err => err);
            },
            getAllAuthors: async (parent, args, context, info) => {
                return await Author.find()
                    .populate()
                    .then(authors => authors)
                    .catch(err => err);
            }
        },
        Author: {
            books: async ({ _id }, args, context, info) => {
                return await Book.find({ author: _id });
            }
        }
    };

    export default resolvers;

We now defined all queries that our Author type is going to have. We need to define the Book queries, so go to our Book folder and inside the resolvers.js put the following code:

    import Author from "../../models/Author";
    import Book from "../../models/Book";

    const resolvers = {
        Query: {
            getBook: async (parent, { _id }, context, info) => {},
            getAllBooks: async (parent, args, context, info) => {}
        }
    };

    export default resolvers;

The code is almost the same as the Author queries, so inside our getBook, pass an _id as an argument, and inside the function we’re going to pass the following code:

    return await Author.findById(_id)
            .populate()
            .then(author => author)
            .catch(err => err);

And inside thegetAllBooks query, pass the following code:

    return await Book.getAllBooks()
            .then(books => books)
            .catch(err => err);

Inside the resolvers (but not inside the Query object) write the following code:

    Book: {
        author: async ({ author }, args, context, info) => {
            return await Author.findById(author);
        }
    }

Now, our code should look like this:

    import Author from "../../models/Author";
    import Book from "../../models/Book";

    const resolvers = {
        Query: {
            getBook: async (parent, { _id }, context, info) => {
                return await Book.getBook({ _id })
                    .then(book => book)
                    .catch(err => err);
            },
            getAllBooks: async (parent, args, context, info) => {
                return await Book.getAllBooks()
                    .then(books => books)
                    .catch(err => err);
            }
        },
        Book: {
            author: async ({ author }, args, context, info) => {
                return await Author.findById(author);
            }
        }
    };

    export default resolvers;

Pretty simple and concise. Now, we need to test our queries. For that, we need to go to the GraphQL Playground at localhost:4000/playground, and write our first query to check if it's working.

Inside the playground, we have two sides: the left side, where we write our queries, mutations, and subscriptions, and we also have the right side which shows the result of our requests.

To write a query in GraphQL, we need to pass a string that looks like an object and inside that string, we pass the fields that we like to get.

A simple query in GraphQL looks like this:

    query {
      getAllBooks {
        _id
        title
        description
        language
        author {
          _id
          firstName
          lastName
          age
        }
      }
    }

First, we specify if it's a query, a mutation or a subscription. Then, we pass the name of the query, since the getAllBooks query doesn’t have any args that need to pass, we just need to pass inside the fields that we like to get.

Now if you write that query above, and press the button, we’re going to run the query on our server and get the following result:

Running the GraphQL query

We get only one Book that’s because we only have one Book, for now. Now, we’re going to make a query to getAllAuthors, and this query is going to look like this:

    query {
      getAllAuthors {
        _id
        firstName
        lastName
        age
        books {
          _id
          title
          description
          language
        }
      }
    }

If you write this query and run on at the playground, we’re going to get the following result:

Now that we have written our first set of queries, feel free to write one yourself. Try to get a specific Author by _id, and a Book by _id.

Hint: You can pass a query to specify a parameter, such as this one:

    getAuthor(_id: "5c5ed863eb1772069c8d12e6")

This query should return a specific Author.

Conclusion

In this tutorial, we learned how to write queries in GraphQL. We’re going to learn more about mutations in the next post of this series.

So, stay tuned and see you in the next tutorial!