top of page
Writer's pictureTandid Alam

Working with GraphQL

Updated: Dec 19, 2022

A crash course on working with GraphQL


Contents


- Introduction

- GraphQL Architecture and Components

- Environment Setup

- Schemas and Types

- Object Types and Fields

- Arguments

- The Query and Mutation Types

- Scalar Types

- Resolvers

- Parent

- Arguments

- Context

- Mutations

- Adding Graphiql

- A Different Implentation of GraphQL

- Learning Resources

 

Introduction


GraphQL is a query language which gives the client the power to ask for exactly what they need.


Typically, RESTful APIs follow clear and well-structured resource-oriented approach. However, when the data gets more complex, the routes get longer, so sometimes it's not possible to fetch data with a single request. This is where GraphQL comes in.


GraphQL structures data in the form of a graph with its powerful query syntax for traversing, retrieving, and modifying data.


An example of this is if we have a store with products that have an ID, name, image, price, description, and reviews. If we made a simple request without using GraphQL, we'd get all of this data back instead of specific characteristics. However, if we make a query with GraphQL and ask for product name and price, we'd get only the data we need, nothing more, nothing less.



Here we make a query to get all products with their name and price to display on our website.

{
   query {
      products {
         name
         price
      }
   }
}

This is the data we would get back from out query.

{
  "data": {
     "products": [
        {
           "name": "apple"
           "price": $1
        },
        {
           "name": "orange",
           "price": $3
        }
     ]
  }
}


GraphQL also provides great developer tools for documentation and testing queries. GraphiQL is a tool which generates documentation of the query and its schema. It also gives a query editor to test GraphQL APIs and intelligent code completion capability while building queries.


 

GraphQL Architecture and Components


GraphQL has a simple architecture and can be broken down to the following components below:


Schema

A GraphQL schema is at the center of any GraphQL server implementation and describes the functionality available to the clients which connect to it.

Query

A GraphQL query is the client application request to retrieve data from database or legacy API's.

Resolver

Resolvers provide the instructions for turning a GraphQL operation into data. They resolve the query to data by defining resolver functions.


GraphiQL

Browser based interface for editing and testing GraphQL queries and mutations.


ApolloClient

Best tool to build GraphQL client applications. Integrates well with all javascript front-end.




 

Environment Setup


We are going to be connecting GraphQL with Apollo Server for demonstration purposes, but GraphQL has documentation on their site for connecting with other libraries. You can find the link to the code here: GraphQL Code Libraries, Tools and Services



Install the following in your command line:

  • graphql

  • graphql-tools

  • apollo-server

npm i graphql graphql-tools apollo-server

Starter Code:


To set up the backend, we need to import ApolloServer and connect it to our localhost.

const { ApolloServer } = require("apollo-server");

const server = new ApolloServer()

server.listen().then(({ url }) => {
  console.log("Server is up at " + url);
});

You will notice that we'll have an error because we need to set up our Schemas and Resolvers. We'll need to create schemas using type definitions and initialize our "root" using the Query Object. This Query Object is essentially the structure of the data that we want to get back.


We'll create this structure using gql from apollo-server.

const { gql } = require("apollo-server");

Once we import gql, we can create our type definitions using back ticks. Then, we'll need to create resolvers which will return data when making a query. Our structure will look something like this.


//Here we define how our Query should look like
const typeDefs = gql`
  type Query {
    hello: String
  }
`
//Here we make a resolver to return data when a query is made
const resolvers = {
  Query: {
    hello: () => {
      return 'World!'
    }
  }
}

//We place our typeDefs and resolvers within our server
const server = new ApolloServer({
  typeDefs, 
  resolvers
})

From this, we will get a playground that is very similar to graphiql where we can test our data. To create a query in the playground we simply start with the query block and ask for data:


{
   query {
      hello
   }
}


 

Schemas and Types


Object Types and Fields


Before we move on, it's important to understand the type system in GraphQL. Let's take a look at the following object type which is an object you can fetch data for.


type Character {
  name: String!
  appearsIn: [Episode!]!
}

The language is pretty readable, but let's go over it so that we can have a shared vocabulary:

  • Character is a GraphQL Object Type, meaning it's a type with some fields. Most of the types in your schema will be object types.

  • name and appearsIn are fields on the Character type. That means they are the only fields that can appear in any part of a GraphQL query that operates on the Character type.

  • String is one of the built-in scalar types - these are types that resolve to a single scalar object, and can't have sub-selections in the query. We'll go over scalar types more later.

  • ! means that the field is non-nullable, meaning that the GraphQL service promises to always give you a value when you query this field. In the type language, we'll represent those with an exclamation mark.

  • [Episode!]! represents an array of Episode objects. Since it is also non-nullable, you can always expect an array (with zero or more items) when you query the appearsIn field. And since Episode! is also non-nullable, you can always expect every item of the array to be an Episode object.

Now you know what a GraphQL object type looks like, and how to read the basics of the GraphQL type language.


Arguments


Every field on a GraphQL object type can have zero or more arguments, for example the length field below:

type Starship {
  id: ID!
  name: String!
  length(unit: LengthUnit = METER): Float
}

Arguments can be either required or optional. When an argument is optional, we can define a default value - if the unit argument is not passed, it will be set to METER by default.


The Query and Mutation Types

Most types in your schema will just be normal object types, but there are two types that are special within a schema:

  • Query: Holds the structure of your data

  • Mutation: Holds methods to manipulate data

It's important to remember that other than the special status of being the "entry point" into the schema, the Query and Mutation types are the same as any other GraphQL object type, and their fields work exactly the same way.


Scalar Types


GraphQL comes with a set of default scalar types out of the box:

  • Int: A signed 32‐bit integer.

  • Float: A signed double-precision floating-point value.

  • String: A UTF‐8 character sequence.

  • Boolean: true or false.

  • ID: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.

 

Resolvers

Resolvers have three arguments:

  • Parent

  • Arguments

  • Context


Parent


When you want to create relationships between data, you'll want to make use of the parent argument. For instance, if you wanted to find all products that are within a certain category, you can create a link from each product to the category by making use of IDs. The parent ID would be product in this case and you are returning all the products within categories where category ID matches the parent ID.



//schema.js
 exports.typeDefs = gql`
  type Query {
    products: [Product!]!
    product(id: ID!): Product
    categories: [Category!]!
    category(id: ID!): Category
  }
  
  type Product {
    id: ID!
    name: String!
    description: String!
    image: String!
    price: Float!
    category: Category
  }

  type Category {
    id: ID!
    name: String!
    products: [Product!]!
  }`

//resolvers/Product

exports.Product = {
//This is a context to another file that holds our db and all its data
  category: (parent, args, { db }) => {
    return db.categories.find((category) => category.id === parent.categoryId);
  },
};


//index.js

const { ApolloServer } = require("apollo-server");
const { typeDefs } = require("./schema");
const { Query } = require("./resolvers/Query");
const { Category } = require("./resolvers/Category");
const { Product } = require("./resolvers/Product");
const { db } = require("./db");

const server = new ApolloServer({
  typeDefs,
  resolvers: {
    Query,
    Category,
    Product,
  },
  context: {
    db,
  },
});

server.listen().then(({ url }) => {
  console.log("Server is up at " + url);
});



Arguments


Arguments are what you pass in as an input to the schema object and you can utilize that input within the resolver.



//schema.js
 exports.typeDefs = gql`
  type Query {
  ...
    product(id: ID!): Product
    //the ID is an argument that we can use in our resolver
  }


//resolvers/Query
exports.Query = {
  ...
  product: (parent, { id }, { db }) => {
    return db.products.find((product) => product.id === id);
  },
};


Context


If you look at the first example in this section you can see the context being used to refer to the database. We do this by creating a context property in our apollo server, passing in the database that we get from another file, db.js, and calling it in in our resolver for reference.


 

Mutations


Mutations allow us to create, update, and delete our data. To make use of this, we create an Object Type in our Schema called Mutation and create properties for things we want to do.


Here's an example on how we add a product using GraphQL Mutations.


//schema.js
  
exports.typeDefs = gql`
  type Query {
  ...
  }

  type Mutation {
    addProduct(input: AddProductInput!): Product!
  }

  type Product {
    id: ID!
    name: String!
    description: String!
    image: String!
    price: Float!
    category: Category
  }
  
  input AddProductInput {
    name: String!
    description: String!
    quantity: Int!
    image: String!
    price: Float!
    categoryId: String
  }


  ...

//resolvers/Mutation.js
const { v4: uuid } = require("uuid");

exports.Mutation = {

  addProduct: (parent, { input }, { db }) => {
    const { name, image, price, quantity, categoryId } = input;

    const newProduct = {
      id: uuid(),
      name,
      image,
      price,
      quantity,
      categoryId,
    };

    db.products.push(newProduct);

    return newProduct;
  },
  
};

Now when we call a Mutation and pass in certain parameters within our playground, it'll push a new product to our existing database.


 

Adding Graphiql


GraphiQL is useful during testing and development but should be disabled in production by default. If you are using express-graphql, you can toggle it based on the NODE_ENV environment variable:


app.use('/graphql', graphqlHTTP({
  schema: MySessionAwareGraphQLSchema,
  graphiql: process.env.NODE_ENV === 'development',
}));


 

A Different Implementation of GraphQL


Here's an example of GraphQL with Express in case you want to compare different implementations.

const express = require("express");
const expressGraphQL = require("express-graphql").graphqlHTTP;
const {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLString,
  GraphQLList,
  GraphQLNonNull,
  GraphQLInt,
} = require("graphql");

const app = express();

const authors = [
  { id: 1, name: "J. K. Rowling" },
  { id: 2, name: "J. R. R. Tolkien" },
  { id: 3, name: "Brent Weeks" },
];

const books = [
  { id: 1, name: "Harry Potter and the Chamber of Secrets", authorId: 1 },
  { id: 2, name: "Harry Potter and the Prisoner of Azkaban", authorId: 1 },
  { id: 3, name: "Harry Potter and the Goblet of Fire", authorId: 1 },
  { id: 4, name: "The Fellowship of the Ring", authorId: 2 },
  { id: 5, name: "The Two Towers", authorId: 2 },
  { id: 6, name: "The Return of the King", authorId: 2 },
  { id: 7, name: "The Way of Shadows", authorId: 3 },
  { id: 8, name: "Beyond the Shadows", authorId: 3 },
];

const BookType = new GraphQLObjectType({
  name: "Book",
  description: "This represents a book written by an author",
  fields: () => ({
    id: { type: new GraphQLNonNull(GraphQLInt) },
    name: { type: new GraphQLNonNull(GraphQLString) },
    authorId: { type: new GraphQLNonNull(GraphQLInt) },
  }),
});

const RootQueryType = new GraphQLObjectType({
  name: "Query",
  description: "Root Query",
  fields: () => ({
    books: {
      type: new GraphQLList(BookType),
      description: "List of All Books",
      resolve: () => books,
    },
  }),
});

const schema = new GraphQLSchema({
  query: RootQueryType,
});

app.use(
  "/graphql",
  expressGraphQL({
    schema: schema,
    graphiql: true,
  })
);
app.listen(5000, () => {
  console.log("Server Running");
});

This method is the code first method whereas this entire time we were utilizing graphQL Tools. Check out the differences in the following video to get a better understanding for the two use cases. Link: (25) Schema-first or code-first GraphQL - YouTube

 

Learning Resources:








Comments


bottom of page