O'Reilly logo

JavaScript Everywhere by Adam D. Scott

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Our First GraphQL API

Presumably, if you are reading this, you are a human being. As a human you have a number of interests and passions. You also have family members, friends, acquaintances, classmates, and colleagues. Each of those people also have their own social relationships, interests, and passions. Some of these relationships and interests overlap, while others do not. All together, for each of us, it builds a connected graph of the people in our lives.

These types of interconnected data are exactly the challenge that GraphQL initially set out to solve in API development. By writing a GraphQL API we are able to efficiently connect data, which reduces the complexity and number of requests while allowing us to serve a client precisely the data they need.

Does that all sound like a bit of overkill for a notes application? Perhaps it does, but as you’ll see, the tools and techniques provided by the GraphQL JavaScript ecosystem both enable and simplify all types of API development.

In this chapter we’ll build a GraphQL API, using the apollo-server-express package. To do so, we’ll explore fundamental GraphQL topics, write a GraphQL schema, develop code to resolve our schema functions, and test our API using the GraphQL Playground user interface.

Turning our server into an API(sort of)

Let’s begin our API development by turning our Express server into a GraphQL server using the apollo-server-express package. Apollo Server is an open-source GraphQL server library that works with a large number of Node.js server frameworks, including Express, Connect, Hapi, and Koa. It enables us to serve data as a GraphQL API from a Node.js application and also provides helpful tooling such as the GraphQL Playground, a visual helper for working with our API in development.

To write our API we’ll be modifying the web application code within that we wrote in the previous chapter. Let’s start by including the apollo-server-express package. Add the following to the top of your src/index.js file:

const { ApolloServer, gql } = require('apollo-server-express');

Now that we’ve imported apollo-server, we’ll set up a basic GraphQL application. GraphQL applications consist of two primary components: a schema of type definitions and resolvers, which resolve the queries and mutations performed against the data. If that all sounds like nonsense, that’s OK. We’ll implement a “Hello World” API response and will further explore these GraphQL topics throughout the development of our API

To begin, let’s construct a basic schema, which we will store in a variable called typeDefs. This schema will describe a single Query with the name of hello which will return a string:

// Construct a schema, using GraphQL schema language
const typeDefs = gql`
  type Query {
    hello: String
  }
`;

Now that we’ve set up our schema, we can add a resolver that will return a value to the user. This will be a simple function that returns the string “Hello world!”:

// Provide resolver functions for our schema fields
const resolvers = {
  Query: {
    hello: () => 'Hello world!'
  }
};

Lastly, we’ll integrate Apollo Server, to serve our GraphQL API. To do so, we’ll add some Apollo Server specific settings and middleware and update our app.listen code:

// Apollo Server setup
const server = new ApolloServer({ typeDefs, resolvers });

// Apply the Apollo GraphQL middleware and set the path to /api
server.applyMiddleware({ app, path: '/api' });

app.listen({ port }, () =>
  console.log(
    `GraphQL Server running at http://localhost:${port}${server.graphqlPath}`
  )
);

Putting it all together, our src/index.js file should now look like this:

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

// Run the server on a port specified in our .env file or port 4000
const port = process.env.PORT || 4000;

// Construct a schema, using GraphQL's schema language
const typeDefs = gql`
  type Query {
    hello: String
  }
`;

// Provide resolver functions for our schema fields
const resolvers = {
  Query: {
    hello: () => 'Hello world!'
  }
};

const app = express();

// Apollo Server setup
const server = new ApolloServer({ typeDefs, resolvers });

// Apply the Apollo GraphQL middleware and set the path to /api
server.applyMiddleware({ app, path: '/api' });

app.listen({ port }, () =>
  console.log(
    `GraphQL Server running at http://localhost:${port}${server.graphqlPath}`
  )
);

If we’ve left our nodemon process running, we can head straight to the browser otherwise we must type npm run dev within our terminal application to start the server. We can then visit http://localhost:4000/api where we’re greeted with the GraphQL Playground. This web app, which comes bundled with Apollo Server, is one of the great benefits of working with GraphQL. From here, we are able to run GraphQL queries and mutations and see the results. We are also able to click the Schema tab to access automatically created documentation for the API.

A screenshot of the GraphQL Playground
Figure 4-1. The GraphQL Playground

We can now write our query against our GraphQL API. To do so, we’ll type the following into the the GraphQL Playground:

query {
  hello
}

When we click the Play button, our query should return the following:

{
  "data": {
    "hello": "Hello world!"
  }
}
The hello query in the GraphQL Playground
Figure 4-2. The hello query

And that’s it! We now have a working GraphQL API that we’ve accessed via the GraphQL Playground. Our API takes a query of hello and returns the string Hello world!. More importantly, we now have the structure in place to build a fully featured API.

GraphQL Basics

In the previous section we dove right in and developed our first API, but let’s take a few moments to step back and look at the different pieces of a GraphQL API. The two primary building blocks of a GraphQL API are schemas and resolvers. By understanding these two components, we can apply them to our API design and development.

Schemas

A schema is a written representation of our data and interactions. By requiring a schema, GraphQL enforces us to follow a strict plan for our API. This is because our API can only return data and perform interactions that are defined within our schema.

The fundamental component of GraphQL schemas are object types. In our previous example we created a GraphQL object type of Query with a field of hello, which returned a scalar type of String. GraphQL contains five built-in scalar types:

  • String: A string with UTF-8 character encoding

  • Boolean: A true or false value

  • Int: A 32-bit integer

  • Float: A floating point value

  • ID: A unique identifier

With these basic components we can construct a schema for an API. We do so by first defining the type. Let’s imagine that we were creating an API for a pizza menu. In doing so, we might define a GraphQL schema type of Pizza like so:

type Pizza {
}

Now, each pizza has a unique ID, a size (such as small, medium, and large), a number of slices, and optional toppings. To write this, our Pizza schema might look something like this:

type Pizza {
  id: ID
  size: String
  slices: Int
  toppings: [String]
}

In our schema, some field values are required (such as ID, size, and slices), while others may be optional (such as toppings). We can express that a field must contain a value by using an exclamation mark. Let’s update our schema to represent the required values:

type Pizza {
  id: ID!
  size: String!
  slices: Int!
  toppings: [String]
}

In this book, we’ll be writing a basic schema, which will enable us to perform the vast majority of operations found in a common API. If you’d like to explore all of the GraphQL schema options, I’d encourage you to read the GraphQL schema documentation.

Resolvers

The second piece of our GraphQL API will be resolvers. Resolvers perform exactly the action that they are advertised to do; they resolve the data that the API user has requested. We will write these resolvers by first defining them in our schema and then implementing the logic within our JavaScript code. Our API will contain two types of resolvers: queries and mutations.

Queries

A query requests specific data from our API, in its desired format. In our hypothetical pizza API we may write a query that will return a full list of pizzas on the menu and another that might return detailed information about a single pizza. The query will then return an object, containing the data that the API user has requested. A query never modifies our data, only accesses it.

Mutations

A mutation is used when we want to modify the data in our API. In our pizza example, we may write a mutation that changes the toppings for a given pizza and another that allows us to adjust the number of slices. Similar to a query, a is also expected to return a result in the form of an object, typically the end result of the performed action.

Adapting our API

Now that we have a good understanding of the components of GraphQL, let’s adapt our initial API code for our notes application. To begin, we’ll write some code to read and create notes.

The first thing we’ll need is a little bit of data for our API to work with. Let’s create an array of “note” objects, that we’ll use as the basic data served by our API. As our project evolves, we’ll replace this in memory data representation with a database. For now, we will store our data in a variable named notes. Each note in the array with be an object with three properties, id, content, and author:

let notes = [
  {
    id: '1',
    content: 'This is a note',
    author: 'Adam Scott'
  },
  {
    id: '2',
    content: 'This is another note',
    author: 'Harlow Everly'
  },
  {
    id: '3',
    content: 'Oh hey look, another note!',
    author: 'Riley Harrison'
  }
];

Now that we have some data, we will adapt our GraphQL API to work with that data. Let’s begin by focusing on our schema. Our schema is GraphQL’s representation of our data and how it will be interacted with. We know that we will have notes, which will be queried and mutated. These notes will, for now, contain an ID, content, and an author field. Let’s create a corresponding note type within our typeDefs GraphQL schema. This will represent the properties of a note within our API:

type Note {
  id: ID!
  content: String!
  author: String!
}

Now, let’s add a query that will allow us to retrieve the list of all notes. Let’s update the Query type, to include a notes query, which will return the array of note objects:

type Query {
  hello: String!
  notes: [Note!]!
}

Now, we can update our resolver code to perform the work of returning the array of data. Let’s update our Query code to include the following notes resolver, which returns our raw data object:

Query: {
    hello: () => 'Hello world!',
    notes: () => notes
  },

If we now go to the GraphQL playground, running at http://localhost:4000/api, we can test our notes query. To do so, we can type the following query:

query {
  notes {
    id
    content
    author
  }
}

If we then click the Play button, we should see a data object returned, which contains our data array.

The notes query in the GraphQL Playground
Figure 4-3. The notes query

Now, to try out one of the coolest aspects of GraqhQL, we can remove any of our requested fields, such as id or author When we do so, we see that our API returns precisely the data that we’ve requested. This allows the client that consumes the data to control the amount of data sent within each request and limit that data to exactly what is required.

A note query with only content data requested in the GraphQL Playground
Figure 4-4. A notes query with only content data requested

Now that we can query our full list of notes, let’s write some code that will allow us to query for a single note. We can imagine the usefulness of this from a user interface perspective, for displaying a view that contains a single, specific note. To do so, we’ll want to request a note with a specific id value. This will require us to use an argument in our GraphQL schema. An argument allows the API consumer to pass specific values to our resolver function, providing the necessary information for it to resolve. Let’s go ahead and add a note query, which will take an argument of id, with the type ID. We’ll update our Query object within our typeDefs to the following, which includes our new note query:

type Query {
  hello: String
  notes: [Note!]!
  note(id: ID): Note!
}

Now that we’ve updated our schema, we are able to write a query resolver to return the requested note. To do this, we’ll need to be able to read the API user’s argument values. Helpfully, Apollo Server passes useful parameters to our resolver functions. These are as follows:

  • parent: The result of the parent query, which is useful when nesting queries.

  • args: These are the arguments passed by the user in the query.

  • context: Information passed along from the server application to the resolver functions. This could include things such as the current user or database information.

  • info: Information about the query itself.

We’ll be exploring these further as needed within our code. If you’re curious, you can learn more about these parameters in Apollo Server’s documentation. For now, we’ll only be needing the information contained within the second parameter, args.

Our note query will take the note id as an argument and find it within our array of note objects. To do so, let’s add the following to our query resolver code:

note: (parent, args) => {
  return notes.find(note => note.id === args.id);
}

Our resolver code should now look as follows:

const resolvers = {
  Query: {
    hello: () => 'Hello world!',
    notes: () => notes,
    note: (parent, args) => {
      return notes.find(note => note.id === args.id);
    }
  }
};

To run our query, let’s go back to our web browser and visit the GraphQL Playground at http://localhost:4000/api. We can now query for a note with a specific id as follows:

query {
  note(id: "1") {
    id
    content
    author
  }
}

When we run this query, we should receive the results of a note with the requested id value. If we attempt to query for a note that doesn’t exist, we should receive a result with a value of null. To test this, try changing the id value to return different results.

We can now query our list of notes as well as an individual note. Let’s wrap up our initial API code by introducing the ability to create a new note, using a GraphQL mutation. In that mutation, the user will pass in the note’s content. For now, we’ll hard code the author of the note. Let’s begin by updating our typeDefs schema with a Mutation type, which we will call newNote:

type Mutation {
  newNote(content: String!): Note!
}

We’ll now write a mutation resolver, which will take in the note content as an argument, store the note as an object, and add it in memory to our notes array. To do this, we’ll add a Mutation object to our resolvers. Within the mutation object, we’ll add a function called newNote, with parent and args parameters. Within this function, we’ll take the argument content and create an object with id, content, and author keys. As you may have noticed, this matches the current schema of a note. We will then push this object to our notes array and return the object. Returning the object allows the GraphQL mutation to receive a response in the intended format. Let’s go ahead and write this code as follows:

Mutation: {
  newNote: (parent, args) => {
    let noteValue = {
      id: notes.length + 1
      content: args.content,
      author: 'Adam Scott'
    };
    notes.push(noteValue);
    return noteValue;
  }
}

Our src/index.js will now read as follows:

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

// Run our server on a port specified in our .env file or port 4000
const port = process.env.PORT || 4000;

let notes = [
  {
    id: '1',
    content: 'This is a note',
    author: 'Adam Scott'
  },
  {
    id: '2',
    content: 'This is another note',
    author: 'Harlow Everly'
  },
  {
    id: '3',
    content: 'Oh hey look, another note!',
    author: 'Riley Harrison'
  }
];

// Construct a schema, using GraphQL's schema language
const typeDefs = gql`
  type Note {
    id: ID!
    content: String!
    author: String!
  }

  type Query {
    hello: String
    notes: [Note!]!
    note(id: ID): Note!
  }

  type Mutation {
    newNote(content: String!): Note!
  }
`;

// Provide resolver functions for our schema fields
const resolvers = {
  Query: {
    hello: () => 'Hello world!',
    notes: () => notes,
    note: (parent, args) => {
      return notes.find(note => note.id === args.id);
    }
  },
  Mutation: {
    newNote: (parent, args) => {
      let noteValue = {
        id: notes.length + 1,
        content: args.content,
        author: 'Adam Scott'
      };
      notes.push(noteValue);
      return noteValue;
    }
  }
};

const app = express();

// Apollo Server setup
const server = new ApolloServer({ typeDefs, resolvers });

// Apply the Apollo GraphQL middleware and set the path to /api
server.applyMiddleware({ app, path: '/api' });

app.listen({ port }, () =>
  console.log(
    `GraphQL Server running at http://localhost:${port}${server.graphqlPath}`
  )
);

With our schema and resolver updated to accept a mutation, let’s try it out in GraphQL Playground at http://localhost:4000/api. In the playground, click the + sign to create a new tab and write the mutation as follows:

mutation {
  newNote (content: "This is a mutant note!") {
   content
   id
   author
  }
}

When we click the play button, we should receive a response containing the content, id, and author of our new note. We can also see that our mutation worked by re-running our notes query. To do so, either switch back to the GraphQL Playground tab containing that query, or type the ollowing:

query {
  notes {
    content
    id
    author
  }
}

When this query runs, we should now see four notes, including our recently added one.

Note

We are currently storing our data in memory. This means that anytime we restart our server, we will lose that data. We’ll be persisting our data using a database in the next chapter.

We’ve now successfully implemented our query and mutation resolvers and tested them within the GraphQL Playground user interface.

Conclusion

In this chapter we’ve successfully built a GraphQL API, using the apollo-server-express module. We are now able to run queries and mutations against an in-memory data object. This set up will provide us a solid foundation on which to build any API. In the next chapter we’ll explore the ability to persist data by using a database.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required