SI

June 23, 2019

Building a Mock Blue Bottle GraphQL API

GraphQL

13 min read

Photo by NordWood Themes on Unsplash

tl;dr

  • At Eve Porcello's and Alex Bankā€™s GraphQL Workshop, I built a mock Blue Bottle GraphQL API using apollo-server and faker.
  • apollo-server allows you to add mock resolvers for all of your types in your GraphQL Schema.
  • interfaces in GraphQL help you make your GraphQL API more organized/scalable by creating a relationship between similar GraphQL types.
  • You can play around with my final example in the GraphQL Playground here:

  • Check out my Github repository here.

I recently had the opportunity to attend a GraphQL Workshop by Eve Porcello and Alex Banks. In one of the labs, we were tasked with creating our own GraphQL Schema to mock one of our favorite web applications. (If you arenā€™t familiar with a GraphQL Schema, a schema defines all of the possible operations (mutations and queries,) and types from your GraphQL endpoint. It is pretty much the blueprint for your GraphQL API.) I decided to try to mock one of my favorite e-commerce websites: bluebottlecoffee.com.

Disclaimer: I donā€™t work for Blue Bottle. I just like their website (and some of their coffees.)

My Process šŸ’­

I started by browsing the coffee route and got to work:

blend coffee section on bluebottlecoffee.com

single origin section on bluebottlecoffee.com

I immediately noticed they divided the coffees into two sections: Blends and Single Origin coffees. (For any non-coffee connoisseurs, a blend consists of coffees from multiple countries, whereas a single origin coffee comes from a single farm, multiple farms in a similar area, or, from multiple farms from the same country.) This sounds like a great place to use an enum type. An enum or ā€œenumerationā€ type is a collection of named values (used in many different programming languages.) This is how you define an enum type in GraphQL:

enum SourcingType {
  BLEND
  SINGLE_ORIGIN
}

Letā€™s include the SourcingType within our Coffee type:

type Coffee {
  id: ID!
  sourcingType: SourcingType!
}

Iā€™ve also added a unique id to each coffee. (The ! at the end of ID and SourcingType defines a non-nullable field.) Great, letā€™s add more properties to a Coffee type. Both Single Origin and Blend coffee share common properties that Iā€™ve marked out in the diagrams below:

a card for a coffee blend.

a card for a single origin coffee.

A. An image URL B. A sourcing type (Single Origin or a Blend) C. The name of the coffee D. Flavor Profiles / Notes E. Unit Price F. Unit Weight

Letā€™s add these to our schema:

   type Coffee {
     id: ID!
     sourcingType: SourcingType!
+    name: String!
+    flavorNotes: [String!]!
+    unitPrice: Float!
+    unitWeight: Float!
+    imageURL: String!
   }
  • String and Float are primitive scalars in GraphQL.
  • [String!]! looks a little spooky šŸ‘» at first, but all this means is that the flavorNotes property expects a non-nullable array of non-nullable strings. We expect at a minimum, an empty array.

Our schema looks great but, letā€™s make it more Robusta (this is not a typo, this is a terrible coffee dad joke.) We will:

  • define a Coffee interface and create a SingleOrigin and Blend type and
  • add queries to our Schema.

The Coffee Interface ā˜•ļø

Recall - coffees come in two types: Blends and Single Origin coffees. They share exactly the same properties but, what if we wanted to include information about the country of origin for each coffee? A blend will include many different countries, whereas, a single origin coffee will only have one. It would be nice to be able to abstract the Coffee type and create two new types - SingleOrigin and Blend that use the Coffee type as a base.

  • A SingleOrigin type will have a property called country while,
  • the Blend type will include a property - countries.

countries and country will return type Country (which we will construct later.) The only difference is that the countries property on a Blend will be [Country!]! (an array of countries) and the country property on a SingleOrigin will be Country! (one country.)

This all sounds great, but, how do we implement this into our schema? In GraphQL, there is a special interface type that defines a base data type to be used to implement other similar types. Letā€™s use this to create a Coffee base type.

-  enum SourcingType {
-    BLEND
-    SINGLE_ORIGIN
-  }
-
-  type Coffee {
+  interface Coffee {
     id: ID!
-    sourcingType: SourcingType!
     name: String!
     flavorNotes: [String!]!
     unitPrice: Float!
   }

Notice that the interface includes all properties from the previous Coffee type however, the sourcingType is removed. We no longer need this property since we are going to organize our coffees into Blend and SingleOrigin types so, this property can be inferred. To create Blend and SingleOrigin we will need to use a special keyword: implements to define a relationship between our new types and the base Coffee type.

type Blend implements Coffee {
  # ...
}

type SingleOrigin implements Coffee {
  # ...
}

Now letā€™s give our new types some properties! You might be thinking it would suffice to do this:

type Country {
  id: ID!
  name: String!
}

type Blend implements Coffee {
  countries: [Country!]!
}

type SingleOrigin implements Coffee {
  country: Country!
}

This is what I initially thought as well. Since we are explicitly defining a relationship between our new types and the base Coffee interface using the implements keyword, you might think it is inferred that these new types will inherit everything from the Coffee interface. Our Schema, however, needs to be as explicit as possible. This means that we must redefine all properties from the Coffee interface to our respective types.

  type Country {
    id: ID!
    name: String!
  }

   type Blend implements Coffee {
+    id: ID!
+    name: String!
+    flavorNotes: [String!]!
+    unitPrice: Float!
+    unitWeight: Float!
+    imageURL: String!
     countries: [Country!]!
   }

   type SingleOrigin implements Coffee {
+    id: ID!
+    name: String!
+    flavorNotes: [String!]!
+    unitPrice: Float!
+    unitWeight: Float!
+    imageURL: String!
     country: Country!
   }

Adding Queries for Coffee! šŸŒ±

Our schema is looking great however, we have not defined any queries in our schema. To do so we will define a type Query which will hold all queries from our GraphQL API.

type Query {
  allCoffee: [Coffee!]!
  allSingleOrigin: [SingleOrigin!]!
  allBlends: [Blend!]!
}

Weā€™ve defined three different queries:

  1. allCoffee will return all coffees in our database: Blend and SingleOrigin,
  2. allSingleOrigin will return all SingleOrigin coffees, and
  3. allBlends will return all Blend coffees.

Adding our Schema to apollo-server šŸš€

Letā€™s start by cloning the repository Iā€™ve created for you by running:

git clone https://github.com/iwakoscott/mock-blue-bottle-graphql-api.git

After cloning my repository, run cd mock-blue-bottle-graphql-api and run npm install to download dependencies. Letā€™s start coding! We will begin by requiring apollo-server by writing:

const { ApolloServer } = require('apollo-server')

In the same file, letā€™s create another variable - typeDefs that will hold on to our Schema weā€™ve been building as a string template.

// ...
+const typeDefs = `
+  interface Coffee {
+    id: ID!
+    name: String!
+    flavorNotes: [String!]!
+    unitPrice: Float!
+    unitWeight: Float!
+    imageURL: String!
+  }
+
+  type Country {
+    id: ID!
+    name: String!
+  }
+
+  type Blend implements Coffee {
+    id: ID!
+    name: String!
+    flavorNotes: [String!]!
+    unitPrice: Float!
+    unitWeight: Float!
+    imageURL: String!
+    countries: [Country!]!
+  }
+
+  type SingleOrigin implements Coffee {
+    id: ID!
+    name: String!
+    flavorNotes: [String!]!
+    unitPrice: Float!
+    unitWeight: Float!
+    imageURL: String!
+    country: Country!
+  }
+
+  type Query {
+    allCoffee: [Coffee!]!
+    allSingleOrigin: [SingleOrigin!]!
+    allBlends: [Blend!]!
+  }
+`;

Letā€™s now create a new instance of ApolloServer and pass in our schema.

// ...
+  const server = new ApolloServer({
+    typeDefs,
+  });

We now have an ApolloServer instance with our schema. You might be wondering: ā€œThis is great and all but, how are we resolving the queries specified in our schema?ā€ We donā€™t have any resolver functions let alone a database or any micro-services to get data from. We definitely canā€™t get real data but, we can get close! I want to show you the power of mocked resolvers in apollo-server.

Building Mock Resolvers šŸ¦œ

Letā€™s begin by installing my favorite package for creating fake data: faker

npm install --save-dev faker

Weā€™ll create an object literal and save it in a new variable called mocks:

+  const mocks = {
+
+  };

Now what we want to do is mock out all custom types weā€™ve defined in our schema:

  • Blend,
  • SingleOrigin, and
  • Country

I will demonstrate how to mock out a Blend type (I leave Country and SingleOrigin as an exercise for the curious reader. Check out my Github repository for my implementation. )

+  const faker = require('faker');
+
+  function getRandomPrice() {
+    const randomPrice = Number(faker.finance.amount(12, 22, 2));
+    return Math.round(randomPrice * 10) / 10;
+  }
+
const mocks = {
+  Blend: () => ({
+    id: faker.random.number(),
+    name: faker.commerce.productName(),
+    flavorNotes: [
+      faker.commerce.productAdjective(),
+      faker.commerce.productAdjective()
+    ],
+    unitPrice: getRandomPrice(),
+    unitWeight: faker.random.number({ min: 8, max: 12 }),
+    imageURL: faker.image.imageUrl()
+  })
};
  • ā€œWhat about the Coffee interface?ā€ The interface is no longer is treated as a type, instead Coffee defines a relationship between all types that implements this interface.
  • How about the Query type? The Query type doesnā€™t need to be mocked since all queries: allCoffee, allSingleOrigin, and allBlends will resolve with our custom types that weā€™ve already mocked out.

Starting our apollo-server ā–¶ļø

After mocking out the rest of the custom types, we are now ready to see all of our hard work come to life. Letā€™s add our mocks to our server instance, start our server, and listen to requests for coffee ā˜•ļø :

 const server = new ApolloServer({
   typeDefs,
+  mocks
 });
+
+  server
+    .listen(4001)
+    .then(({ url }) =>
+      console.log(`Listening for coffee orders on ${url}... ā˜•ļø`);

Run npm run start in your terminal and wait until you see

ā€œListening for coffee orders on http://localhost:4001/... ā˜•ļøā€

Now open up your favorite browser and navigate to: http://localhost:4001/ You should see the GraphQL Playground.

GraphQL Playground graphical user interface.

This is where we will order query our coffee. The left panel is where we will write our queries, and our response will appear on the right panel after clicking the play button. On the very right-hand side of the browser, you might have noticed the two tabs - DOCS and SCHEMA. SCHEMA will show you your raw schema youā€™ve defined and passed to the Apollo server instance, and the DOCS will present your schema in a nice, human-readable way.

GraphQL Playground DOCS tab.

Letā€™s write our first query! In the left panel type in the following query and click the ā€œplayā€ button.

query {
  allSingleOrigin {
    id
    name
    flavorNotes
    unitPrice
    unitWeight
    imageURL
    country {
      id
      name
    }
  }
}

If everything went accordingly, you should see a response in the right panel:

our first GraphQL query in the playground.

Letā€™s now try querying for allCoffee. This is where things get a little bit more interesting. Recall that allCoffee query will return both SingleOrigin and Blend.

How would you write a query for allCoffee and include properties that are exclusive to each type of coffee? In our example, SingleOrigin has a property country while a Blend has property countries. Unfortunately, you cannot just include both properties in the query, otherwise, you will get an error:

GraphQL Error

The error gives us a helpful hint, however:

Did you mean to use an inline fragment on "SingleOrigin"?

An inline fragment takes on an interesting syntax that looks similar to the ES6 Spread Operator :

query {
  allCoffee {
    id
    name
    flavorNotes
    unitPrice
    unitWeight
    imageURL
    ... on SingleOrigin {
      country {
        id
        name
      }
    }
    ... on Blend {
      countries {
        id
        name
      }
    }
  }
}

We use the ... and we use the on keyword to specify which type the fragment is coming from. We then provide the properties we want from the specific type. Go ahead and run the updated query in the playground! Since the data is randomly generated, you might get a response back with only Blend, only SingleOrigin or a mix of both! Super cool! šŸ˜Ž

One Last Detail

You might have noticed a warning in the console:

Type "Coffee" is missing a "__resolveType" resolver. Pass false into "resolverValidationOptions.requireResolversFo rResolveType" to disable this warning.

Whenever you are working with an interface, our resolvers need a little bit of assistance identifying which type is what (I donā€™t know about you but, even a connoisseur like me canā€™t taste the difference between a blend and a single origin coffee.) We only included mock resolvers, so, to fix this error we need to create a new object literal called resolvers and add a property Coffee on to it, along with this __resolveType function.

+  const resolvers = {
+    Coffee: {
+      __resolveType: coffee => (coffee.countries ? 'Blend' : 'SingleOrigin')
+    }
+  }
+
 const server = new ApolloServer({
   typeDefs,
   mocks,
+  resolvers
 });

The first argument, coffee of __resolveType refers to the coffee currently being resolved. If the coffee has property countries we can assume it is a Blend otherwise, it is a SingleOrigin. Now if you re-run the server, the warning should be gone! šŸš€

Final Words

Getting a GraphQL server up and running is so simple with apollo-server. You donā€™t even need to have to write resolvers, let alone have a database or micro-services to get data from. You can define a schema and mock all of your data until you are ready to connect to an actual source. Lastly, I want to make a special shout out to Eve Porcello and Alex Banks for giving me an opportunity to attend one of their GraphQL Workshops. Keep an eye out for any workshops in your area. I highly recommend it! Also, check out their Learning GraphQL Oā€™Reily book - a great place to start to learn the fundamentals of building Full Stack GraphQL Applications. āœŒļø


a not so professional head shot.

Satoshi

šŸ‘‹ Hello, thanks for the read! If you found my work helpful, have constructive feedback, or just want to say hello, connect with me on social media. Thanks in advance!

Ā© 2021 Made by Scott Iwako