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
andfaker
. apollo-server
allows you to add mock resolvers for all of your types in your GraphQL Schema.interface
s 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:
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. 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
andFloat
are primitive scalars in GraphQL.[String!]!
looks a little spooky š» at first, but all this means is that theflavorNotes
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 aSingleOrigin
andBlend
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 calledcountry
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:
allCoffee
will return all coffees in our database:Blend
andSingleOrigin
,allSingleOrigin
will return allSingleOrigin
coffees, andallBlends
will return allBlend
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
, andCountry
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 atype
, insteadCoffee
defines a relationship between all types thatimplements
this interface. - How about the
Query
type? TheQuery
type doesnāt need to be mocked since all queries:allCoffee
,allSingleOrigin
, andallBlends
will resolve with our customtypes
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.
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.
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:
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:
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. āļø