Building APIs with GraphQL

APIs are a crucial part of modern software development. They allow different applications to communicate with each other, enabling a variety of functionalities such as data sharing, integration, and automation. Recently, GraphQL has gained traction as an alternative to traditional REST APIs. In this tutorial, we will learn how to build APIs with GraphQL and explore its advantages over REST.

What is GraphQL?

GraphQL is a query language developed by Facebook for building APIs. It provides a flexible and efficient mechanism for querying and manipulating data using a single endpoint. With GraphQL, clients can specify exactly what data they need, and the server responds with only that data.

GraphQL also supports real-time updates using subscriptions. Clients can subscribe to certain data and receive updates whenever that data changes on the server. This makes GraphQL ideal for applications that require real-time data such as chat applications and gaming platforms.

Prerequisites

Before we start building our GraphQL API, we need to install some tools and frameworks. Here are the prerequisites for this tutorial:

  • Node.js
  • npm
  • Express
  • graphql
  • nodemon
  • body-parser

You can install Node.js and npm from their website. Once you have installed Node.js and npm, you can install the remaining packages using the following command:

npm install express graphql nodemon body-parser

Getting Started

Let’s start by creating a new directory for our project and initializing a new Node.js project:

mkdir graphql-api
cd graphql-api
npm init -y

This will create a new project with a package.json file that we can use to manage our dependencies. Next, let’s create a new file called server.js and add the following code:

const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const { buildSchema } = require('graphql')
const bodyParser = require('body-parser')

const app = express()

app.use(bodyParser.json())

const schema = buildSchema(`
  type Query {
    hello: String
  }
`)

const root = {
  hello: () => 'Hello World!'
}

app.use('/graphql', graphqlHTTP({
  schema,
  rootValue: root,
  graphiql: true
}))

const port = process.env.PORT || 4000

app.listen(port, () => console.log(`GraphQL API running on port ${port}`))

Let’s break down this code. First, we import the necessary packages: express, express-graphql, graphql, and body-parser. We use express to create a new instance of an Express application. Then, we use body-parser to parse incoming JSON requests.

Next, we define our GraphQL schema using the buildSchema function from the graphql package. Our schema has a single query field called hello that returns a string.

We also define a resolver object called root that maps to the schema. The hello resolver returns the string “Hello World!”.

Finally, we use express-graphql to create a new GraphQL endpoint at /graphql. We pass in our schema and resolver object as options to graphqlHTTP. We also set graphiql to true, which enables the GraphiQL IDE for testing our API.

We start our server on port 4000 using the listen method from our Express application.

To start our server, run nodemon server.js in your terminal. You should see a message that says “GraphQL API running on port 4000”. Open your browser and navigate to `http://localhost:4000/graphql`. You should see the GraphiQL IDE. In the left panel, type:

{
  hello
}

Then, click the play button in the top left corner. You should see the response:

{
  "data": {
    "hello": "Hello World!"
  }
}

Congratulations! You have just created your first GraphQL API.

Adding Data Sources

Now that we have a basic GraphQL API, let’s add some data sources. In this tutorial, we will use fake data to keep things simple. We will create an array of books and authors and expose them through our API.

First, let’s add the data to our server.js file:

const books = [
  {
    id: 'book-1',
    title: 'Harry Potter and the Philosopher's Stone',
    authorId: 'author-1'
  },
  {
    id: 'book-2',
    title: 'Harry Potter and the Chamber of Secrets',
    authorId: 'author-1'
  },
  {
    id: 'book-3',
    title: 'Harry Potter and the Prisoner of Azkaban',
    authorId: 'author-1'
  },
  {
    id: 'book-4',
    title: 'The Hobbit',
    authorId: 'author-2'
  },
  {
    id: 'book-5',
    title: 'The Lord of the Rings',
    authorId: 'author-2'
  }
]

const authors = [
  {
    id: 'author-1',
    name: 'J.K. Rowling'
  },
  {
    id: 'author-2',
    name: 'J.R.R. Tolkien'
  }
]

Next, let’s update our schema and resolver object to include the new data:

const schema = buildSchema(`
  type Query {
    book(id: ID!): Book
    books: [Book]
    author(id: ID!): Author
    authors: [Author]
  }

  type Book {
    id: ID!
    title: String!
    author: Author!
  }

  type Author {
    id: ID!
    name: String!
    books: [Book]
  }
`)

const root = {
  book: ({ id }) => books.find(book => book.id === id),
  books: () => books,
  author: ({ id }) => authors.find(author => author.id === id),
  authors: () => authors,
  Book: {
    author: (book) => authors.find(author => author.id === book.authorId)
  },
  Author: {
    books: (author) => books.filter(book => book.authorId === author.id)
  }
}

We have added four new fields to our schema: book, books, author, and authors. Each of these fields has a corresponding resolver function that returns the appropriate data from our arrays.

We have also updated the Book and Author types to include their relationships to the other type. The author field on Book returns the author object for that book, and the books field on Author returns an array of the books that author has written.

We also need to update our query. In the left panel of the GraphiQL IDE, type:

{
  books {
    id
    title
    author {
      name
    }
  }
}

Then, click the play button. You should see a response that looks like this:

{
  "data": {
    "books": [
      {
        "id": "book-1",
        "title": "Harry Potter and the Philosopher's Stone",
        "author": {
          "name": "J.K. Rowling"
        }
      },
      {
        "id": "book-2",
        "title": "Harry Potter and the Chamber of Secrets",
        "author": {
          "name": "J.K. Rowling"
        }
      },
      {
        "id": "book-3",
        "title": "Harry Potter and the Prisoner of Azkaban",
        "author": {
          "name": "J.K. Rowling"
        }
      },
      {
        "id": "book-4",
        "title": "The Hobbit",
        "author": {
          "name": "J.R.R. Tolkien"
        }
      },
      {
        "id": "book-5",
        "title": "The Lord of the Rings",
        "author": {
          "name": "J.R.R. Tolkien"
        }
      }
    ]
  }
}

Congratulations! You have just added data sources to your GraphQL API.

Mutations

So far, we have only used queries to fetch data from our API. However, GraphQL also provides mutations, which allow us to modify data. In this section, we will learn how to write mutations in GraphQL.

Let’s add a mutation to our schema that allows us to add new books:

const schema = buildSchema(`
  type Query {
    book(id: ID!): Book
    books: [Book]
    author(id: ID!): Author
    authors: [Author]
  }

  type Mutation {
    addBook(title: String!, authorId: ID!): Book
  }

  type Book {
    id: ID!
    title: String!
    author: Author!
  }

  type Author {
    id: ID!
    name: String!
    books: [Book]
  }
`)

We have added a new field to our schema called Mutation. This field has a single resolver function called addBook, which takes in a title and authorId and returns the new book object. We have also updated the Book and Author types to include the id field, which is required for mutations.

Next, let’s update our resolver object to include the addBook function:

const root = {
  book: ({ id }) => books.find(book => book.id === id),
  books: () => books,
  author: ({ id }) => authors.find(author => author.id === id),
  authors: () => authors,
  Book: {
    author: (book) => authors.find(author => author.id === book.authorId)
  },
  Author: {
    books: (author) => books.filter(book => book.authorId === author.id)
  },
  addBook: ({ title, authorId }) => {
    const id = `book-${books.length + 1}`
    const book = { id, title, authorId }
    books.push(book)
    return book
  }
}

Our addBook resolver function takes in title and authorId arguments and generates a new id for the book. It then creates a new object for the book and adds it to the books array. Finally, it returns the new book object.

To test our mutation, in the left panel of the GraphiQL IDE, type:

mutation {
  addBook(title: "The Silmarillion", authorId: "author-2") {
    id
    title
    author {
      name
    }
  }
}

Then, click the play button. You should see a response that looks similar to this:

{
  "data": {
    "addBook": {
      "id": "book-6",
      "title": "The Silmarillion",
      "author": {
        "name": "J.R.R. Tolkien"
      }
    }
  }
}

Congratulations! You have just written your first mutation.

Subscriptions

In addition to queries and mutations, GraphQL also provides subscriptions, which allow clients to subscribe to real-time updates from the server. In this section, we will learn how to write subscriptions in GraphQL.

Let’s add a subscription to our schema that allows clients to receive updates whenever a new book is added:

const schema = buildSchema(`
  type Query {
    book(id: ID!): Book
    books: [Book]
    author(id: ID!): Author
    authors: [Author]
  }

  type Mutation {
    addBook(title: String!, authorId: ID!): Book
  }

  type Subscription {
    newBook: Book
  }

  type Book {
    id: ID!
    title: String!
    author: Author!
  }

  type Author {
    id: ID!
    name: String!
    books: [Book]
  }
`)

We have added a new field to our schema called Subscription. This field has a single resolver function called newBook, which returns the latest book added to our books array.

Next, let’s update our resolver object to include the newBook function:

const root = {
  book: ({ id }) => books.find(book => book.id === id),
  books: () => books,
  author: ({ id }) => authors.find(author => author.id === id),
  authors: () => authors,
  Book: {
    author: (book) => authors.find(author => author.id === book.authorId)
  },
  Author: {
    books: (author) => books.filter(book => book.authorId === author.id)
  },
  addBook: ({ title, authorId }) => {
    const id = `book-${books.length + 1}`
    const book = { id, title, authorId }
    books.push(book)
    return book
  },
  newBook: (args, context) => {
    const latestBook = books[books.length - 1]
    return latestBook
  }
}

Our newBook resolver function returns the latest book added to the books array.

To test our subscription, in the left panel of the GraphiQL IDE, type:

subscription {
  newBook {
    id
    title
    author {
      name
    }
  }
}

Then, click the play button. You should see a message that says “Waiting for data…”. In a new tab, run the mutation we wrote earlier to add a new book:

mutation {
  addBook(title: "The Silmarillion", authorId: "author-2") {
    id
    title
    author {
      name
    }
  }
}

If everything is set up correctly, you should see a new message in the left panel that shows the latest book added to the books array.

Congratulations! You have just written your first subscription.

Conclusion

In this tutorial, we learned how to build a GraphQL API and explored its advantages over REST. We started by creating a basic GraphQL API with a single query field. We then added data sources using arrays of books and authors and updated our schema and resolver object to include these new fields.

We also learned how to write mutations to modify data and subscriptions to receive real-time updates. With these tools, we can create powerful and flexible APIs that can adapt to a variety of use cases.

Thank you for reading this tutorial, and I hope you found it helpful. With the knowledge gained here, you are equipped to build your own GraphQL APIs and take advantage of its powerful features.

Related Post