DEV Community

Cover image for Create a Book Rating System with the Goodreads API and Slash GraphQL
Michael Bogan
Michael Bogan

Posted on • Updated on

Create a Book Rating System with the Goodreads API and Slash GraphQL

GraphQL is a relatively new way to build user interfaces and APIs for consumers. It's essentially a querying language backed by a strongly typed schema, which makes it easy to write human-readable requests to get exactly the data you need. Compared to REST, whose responses are dictated by the server, GraphQL queries place the power squarely in the hands of the client.

However, GraphQL is really just a spec that describes an API. Many features one might expect from an API, like authorization/authentication logic, bi-directional communication, or rate limiting, still need to be implemented. Recently launched, Slash GraphQL is a fully managed GraphQL backend that takes care of all the hard work of setting up a GraphQL API, letting you concentrate on building your app.

In this article, we'll be building an e-commerce bookstore that relies on Slash GraphQL. To demonstrate the flexibility of the service, we'll also be fetching data from another source—the Goodreads API—and integrate it into our responses.

We'll be building our app using Node.js, so you should have at least version 12 installed on your machine. Create a directory called slashql_bookstore and navigate into it from the command line.

Creating the GraphQL backend

Setting up Slash GraphQL

Setting up an account on Slash GraphQL is easy—you can instantly sign-in with your GitHub account. The platform offers a free trial which will work for this article (then moves to a $9.99/mo flat fee for up to 5GB data).

goodreads1

After you've created an account, you'll need to create a new backend:

  1. Click on "Launch a New Backend".

  2. Name the backend "Bookstore".

  3. Click "Launch".

That's it! You now have a server spun up for you in an AWS region.

Defining the GraphQL schema

Every GraphQL server has a strongly typed schema, which defines the objects and fields available to consumers.

Click on "Schema", and paste the following lines:

type Book {
  id: Int!
  isbn: Int!
  title: String! @search(by: [fulltext])
  author: Author!
}

type Author {
  id: Int!
  firstname: String!
  lastname: String!
}
Enter fullscreen mode Exit fullscreen mode

Here we've defined two types—Book and Author—which will represent the data in our API. Of the many niceties Slash GraphQL provides, its support for custom directives is one of the most convenient. Here, for example, by annotating the title field with @search, we're able to provide a fuzzy text with no additional work.

Click "Deploy" to send this schema to production.

Setting up the data

Let's move on to inserting data. We can use the API Explorer to not only add data, but query for it, too. In order to populate our GraphQL backend, we'll define a list of books and their authors through a mutation that Slash GraphQL conveniently provides for us, based on our schema:

mutation AddData {
  addBook(input: [
    {id: 1, title: "The Sound and The Fury", isbn: 9780394747743, author: {id:1, firstname: "William", lastname: "Faulkner"}},
    {id: 2, title: "Light in August", isbn: 9780394711898, author: {id:1, firstname: "William", lastname: "Faulkner"}},
    {id: 3, title: "Giovanni's Room", isbn: 9780141032948, author: {id:2, firstname: "James", lastname: "Baldwin"}},
    {id: 4, title: "The Fire Next Time", isbn: 9780140182750, author: {id:2, firstname: "James", lastname: "Baldwin"}},
    {id: 5, title: "To The Lighthouse", isbn: 9780140274165, author: {id:3, firstname: "Virginia", lastname: "Woolf"}},
    {id: 6, title: "Orlando", isbn: 9780241284643, author: {id:3, firstname: "Virginia", lastname: "Woolf"}}
    ]) {
  numUids
  }
}
Enter fullscreen mode Exit fullscreen mode

Click the giant Play button to have this data added to the backend.

goodreads2

Now, let's run a query to verify that this data is present:

query GetBooks {
  queryBook {
    title
    isbn
    author {
      firstname
      lastname
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

You should see a response back that shows all of the data you previously inserted.

Building the server

We'll now use Express to set up a basic API server that will provide users access to this Slash GraphQL source. Our Express server acts as little more than an interface to hand requests off to Slash GraphQL. Its most important lines are these:

const BACKEND_URL = "https://rampant-arm.us-west-2.aws.cloud.dgraph.io/graphql"

async function fetchGraphQL(operationsDoc, variables) {
  const result = await axios({
    method: 'POST',
    url: BACKEND_URL,
    headers: {
      'Content-Type': 'application/json',
    },
    data: JSON.stringify({
      query: operationsDoc,
      variables,
    }),
  })

  return result.data
}

const query = `
query {
  queryBook {
    title
    isbn
    author {
      firstname
      lastname
    }
  }
}
`
app.get('/', jsonParser, async (req, res) => {
  let gqlResponseData = await fetchGraphQL(query,  {})

  res.render('index', { books: gqlResponseData.data.queryBook })
})
Enter fullscreen mode Exit fullscreen mode

Here, we've defined a helper function, fetchGraphQL, which sets up the necessary headers and data to send over to Slash GraphQL. Keep in mind that you'll need to change BACKEND_URL to match the URL which Slash GraphQL gives you. Then, we write a GraphQL query that defines the data that we want. Finally, we set up a route to fetch that data, and then render it in our view:

<div class="container">
  {{#each books}}
  <div class="row">
    <div class="col-md-4">
      <h2>{{title}}</h2>
      <h4>{{author.firstname}} {{author.lastname}}</h4>
      <p><strong>ISBN:</strong> {{isbn}}</p>
    </div>
  </div>
  <hr>
  {{/each}}
</div>
Enter fullscreen mode Exit fullscreen mode

Our view is backed by handlebars, which is a popular templating library. We can feed our JSON data into an HTML fragment and iterate over the keys and values, which makes it much easier to display the information. Here we're using an #each loop to go over every book returned by the GraphQL API, and then plugging the data into HTML tags. You'll notice that we have placeholders that match the names of the JSON keys; these are replaced by the real data when the page is viewed.

After you npm install all the dependencies, run the server with node server.js. Then, head on over to localhost:3000 in your browser:

goodreads3

You should get back the exact same response that Slash GraphQL's API Explorer did.

Extend the server

As a final step, we want to expose some reviewer data about these books. But we don't want to create dummy data here; instead, we'll use the Goodreads API. Make sure you set up an account there and get an API key before continuing.

One of the many things that Slash GraphQL excels at is the ability to stitch together disparate sources of data into one GraphQL interface. Perhaps you'll want to make calls to other API endpoints that are under your control. To support this, Slash GraphQL provides the @custom directive, which allows you to implement custom behavior for GraphQL fields and resolvers.

First, let's add our rating information to the existing schema:

type Rating @remote {
  ratings_count: Int!
  average_rating: Float!
}
Enter fullscreen mode Exit fullscreen mode

Our Rating type defines field names which we expect from the book.show_by_isbn API. We don't need to define every single field that their REST API provides, only the ones we're interested in. Slash GraphQL will know how to transform that response into a GraphQL one.

On the field side of things, we'll need to specify the URL we want to call out to, along with the HTTP method:

type Book {
  # ...
  rating: Rating @custom(http: {
  url: "https://goodreads-api-munger.glitch.me?isbn=$isbn",
  method: GET
  })
  # ...
}
Enter fullscreen mode Exit fullscreen mode

You'll notice here that we're actually calling out to a Glitch project. The Goodreads API doesn't return JSON data in a nice, neat format. We'll be calling out to an intermediary server which will munge the data into a more legible format. The details of this server aren't all that important (but you can check it out if you want to). What is important is noting that this URL can stand in for anything, which can be super helpful if you're interacting with data that you own.

The other amazing thing is that Slash GraphQL will substitute data for you. See that part of the URL that says $isbn? Depending on the object returned by our query, Slash GraphQL will substitute in the real field value for that variable. This keeps your URL requests flexible and easy to manage.

That's all that we need to do to bring in some custom data. Our GraphQL query can now simply add this rating field to bring in that external data. Let's replace the query in our server.js with this one:

const query = `
query {
  queryBook {
    title
    isbn
    rating {
      ratings_count
      average_rating
    }
    author {
      firstname
      lastname
    }
  }
}
`
Enter fullscreen mode Exit fullscreen mode

And, let's update the view to show this information:

<div class="container">
  {{#each books}}
    <div class="row">
      <div class="col-md-4">
      <h2>{{title}}</h2>
      <h4>{{author.firstname}} {{author.lastname}}</h4>
      <p><strong>ISBN:</strong> {{isbn}}</p>
      <p>Rating: {{rating.average_rating}} (out of {{rating.ratings_count}} votes)</p>
    </div>
  </div>
  <hr>
  {{/each}}
</div>
Enter fullscreen mode Exit fullscreen mode

If you restart the server and head back to localhost:3000, you should now see the ratings populated as well:

goodreads4

Conclusion

Slash GraphQL makes it incredibly easy to set up a production-grade GraphQL API, but we've only scratched the surface. In addition to being able to combine disparate data locations, it also supports authorization, subscriptions, and even encryption.

You can grab the complete source code for this tutorial over at GitHub. If you haven’t already, be sure to sign up for Slash GraphQL - feel free to share your creations with the rest of the Dgraph community.

Top comments (0)