In our prior articles on GraphQL, we looked at an overview of GraphQL and how to write queries to get data. We also looked at how to stand up a GraphQL server running on Express, to return mocked data for movies and TV shows. It’s time to get into API.

In this article, we’ll look at hooking up our GraphQL server to an actual API in order to get real data. The API we’ll use is provided by The Movie Database (also known as TMDb), a free source of community-sourced media information.

If you want to follow along with this tutorial and implement it as you go, you’ll need to sign up at The Movie Database (TMDb). When you go to the website, click on the API link and you’ll be taken to an API overview page. Then, click the link for the API documentation, where you’ll see an introduction to the API, as well as instructions for signing up, which go as follows: when you create your user account for TMDb, you go to your user settings page, then to the API section of your user settings, and then from there you can create your API credentials. This step will give you an API Key, which you’ll need in order for your GraphQL server to make requests to the TMDb API.

Let’s set up our server with a few commands.

On a Mac, open up the Terminal app. (If you are unfamiliar with working with Node, part 2 covers setting up in more depth.) In your terminal:

$ cd ~/Documents 
 $ mkdir _GraphQL 
 $ cd _GraphQL/
 $ mkdir server 
 $ cd server 
 $ mkdir live 
 $ cd live

Let’s create our project.

Run the command to create a Node project and when prompted, name the project “live-server”:

$ npm init

This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields and exactly what they do.

Use `npm install <pkg> –save` afterwards to install a package and save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (live) live-server
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC)

About to write to /Users/chrislivdahl/Documents/_GraphQL/server/live/package.json:

{
  "name": "live-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "apollo-server-express": "^1.3.6",
    "body-parser": "^1.18.3",
    "express": "^4.16.3",
    "graphql": "^0.13.2",
    "graphql-tools": "^3.0.2"
  },
  "devDependencies": {},
  "scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
 }
 Is this ok? (yes) yes

Let’s also install a few packages we’ll need for our project:

$ npm install –save express apollo-server-express graphql-tools graphql express body-parser node-fetch

In addition, we’ll want to use some newer JavaScript syntax, and to do that, we can use the Babel parser:

$ npm install –save babel-preset-env

Also, in your package.json file, let’s add a start script and babel preset:

"scripts": {
 "start": "babel-node index.js --presets env"
 },
"babel": {
 "presets": [
 "env"
 ]
 }

Let’s create the following index.js file:
https://github.globant.com/gist/stay-relevant/ca91f53ef48d5da5088f41247318a7a0

And, let’s create a separate file for our GraphQL schema:
https://github.globant.com/gist/stay-relevant/28858425b4d1bb431ec064751e70643a

If we were to fire up our Express server at this point and query the GraphQL server, we would get no data back. We need to connect (or “resolve,” in GraphQL parlance) to an actual data source.

We’ll need two things
1) resolver functions that resolve requested GraphQL objects to API queries and
2) some API query helpers.

Let’s create our API query helpers in a new file called “tmdbAPI.js”:
https://github.globant.com/gist/stay-relevant/40609d0283dd0204293bd287f3042f04

Here we’ve created a few helper functions to call out to the TMDb API and get data. The TMDb documentation has quite a bit of info one how to fetch movies and TV shows, as well as how to discover lists of media. There are also example API calls that you can run right from the documentation pages, and curl commands that you can copy and try in the command line. (For the sake of brevity, I won’t cover their API much here.)

Now we also need a way for our GraphQL server to call these API helper functions when queries are submitted. What we can do is create some “resolver” functions that perform the API calls, depending on which types are requested in the GraphQL schema.

Let’s create a file called resolvers.js:
https://github.globant.com/gist/stay-relevant/63059c66154e08982caf318dce4d44a9

With this resolvers file, we’ve mapped our GraphQL type names to the functions from our TMDbAPI helper class. When a request is made for a type in our GraphQL schema, then the resolver for that type is called. For example, a request for a Movie will call the tmdbAPI.getMovie(id) function to fetch and return the actual data.

Let’s now run our server in the command line using the start script that we created earlier:

$ npm run start 

Let’s put in the address to our web server now using a browser. Go to http://localhost:3000/graphiql in your browser. Note the spelling of “graphiql”, which will run the GraphiQL project, a project that enables us to enter queries and see our responses right from the server. The GraphiQL tool is super helpful for exploring GraphQL servers.

Let’s create a query that uses the “movies” part of the GraphQL server:

A query for a list of movies

When we make this request, it is received by our GraphQL server (running on Express) and the Apollo server code resolves the data by calling our API helper functions. In this case, our getMovies function in tmdbAPI.js is called. In our schema, we had a top-level query “movies(year: Int, page: Int): Movies”, so the server code knows it needs to return a “Movies” type for this query. Our Movies type looks like this:

 type Movies {
 pages: Int
 results: [Movie]
 }

So the server can return “pages” as a field, which is just a number, and/or the server can return us a “results” field, which is an array/list of Movie objects. The server code then looks at our schema to determine what fields are available and uses our resolver functions to match each field in our query to map to actual data. In this case, in our query, we’ve requested the id, title, popularity and overview of each movie. The schema contains these fields for the

Movie type: 

 type Movie implements Media {
 id: ID! @isUnique
 title: String!
 popularity: Float!
 overview: String
 runtime: Int
 trailers: [Trailer]
 genres: [Genre] @relation(name: "MediaGenres")
 }


The Apollo server code automatically resolves these fields to fields with the same name for data coming from the TMDb API. In the case of trailers, however, we need to do some additional resolving b/c the trailer details need to be fetched from a different TMDb call. Note how our schema specifies that we return a list/array of Trailers when the trailer field is requested for a movie.

trailers: [Trailer]

Thus, when the trailers field is requested in a query, the resolver calls getMovieVideos and returns a list of Trailer objects.
https://github.globant.com/gist/stay-relevant/b6d4b7a3d5951ddaf16ee98a19ef02a8

Let see it in action:

Requesting a list of movies, along with their trailers

Note that now in our query we’re asking for the list of trailers for each movie. The server dutifully calls multiple resolver functions and combines all the data behind the scenes and packages everything up in one response.

Let’s ask for a movie along with it’s genres:

This call uses our movie query, which returns just a movie object of type Movie.

movie(id: ID!): Movie

You can see that we’ve request the genres for the movie this time. And since the TMDb API returns a list of genres, along with their names, the server is able to return us the genre info.

However, note that if we ask for the genres with a multiple movies query, we are calling a different TMDb API endpoint (discover/movies) and this endpoint only returns the id of the genres and not the names. So, we have to do an additional request in our resolver to properly map to the genre data.

Here in our resolver function, we check if the “genre_ids” property is there, which is the case when we call the discover/movies TMDb API endpoint. If this is the case, we then use our getGenresForMovies helper function.
https://github.globant.com/gist/stay-relevant/61d4e465db136f01b0d29f76c0dd9c69

Note that we don’t want to keep calling this getGenresForMovies function for every movie, since the genres don’t change very much, and we’ll be calling that method for each movie. Instead, we can cache the genres TMDb API response in our TMDbAPI object and return the cached values if they are there, otherwise we’ll make our call to get API data.
https://github.globant.com/gist/stay-relevant/fb98217cb885489cd25d7bc20a01ad67

In fact, we can employ any kind of caching or batching strategy on the server-side to take the complexities away from the client-side. It minimizes the need for client-side developers to figure out the nuances of whatever source API is being called. The APIs are effectively normalized to be more friendly and usable for front-end development.

Let’s also touch on the ability to fetch a mixed list of movies and TV shows by genre. In order to do that, we’ve created an “interface” type in our schema:

 interface Media {
 id: ID! @isUnique
 title: String
 overview: String
 }

This interface specifies that any type that implements this interface needs to provide an id, title, and an overview. We then specify that the Movie and TVShow types implement the interface:

 type Movie implements Media { … }
 type TVShow implements Media { … }

In our schema, we’ve added a query where a list of Media can be queried by genre:

genres(year: Int, page: Int, genreIDs: [ID!]): [Media]

We can modify our resolver so that it can fetch both movies and TV shows, using the passed in array of genre IDs.
https://github.globant.com/gist/stay-relevant/3a2473b6c4033bd2a9eb77f23941a581

And we can create a helper for fetching TV shows in our tmdbAPI.js file:

 async getTVShows(year, page, genreIDs = null) {
 const relativeURL = `/discover/tv`
 console.log(`fetch TV shows with year: ${year}, page: ${page}, genreIDs: ${genreIDs}`)
 var query = &sort_by=popularity.desc&timezone=America%2FNew_York&include_null_first_air_dates=false&page=${page}&include_video=false&language=en-US`
 if (genreIDs) {
  query = encodeURIComponent(`${query}&with_genres=${genreIDs.join(',')}`)
 }
   return this.getAPIData(relativeURL, query)
 }

Here we’re combining the results into one array. Now, the question is, how will the server know which type to return for each result in the combined array? Well, we can specify a __resolveType function for our Media type and provide a way for the function to know if an object is a Movie or a TVShow. The TMDb API conveniently returns a “name” field for TV shows and a “title” field for movies, which helps us spot the difference between objects:

 Media: {
  __resolveType(obj, context, info) {
    // For the TMDb API, if the object has a title field, then it is a movie, whereas the TV shows have a name field.
   // console.log('resolve object: ', obj)
     if (obj.title) {
       return 'Movie'
     } else if (obj.name) {
       return 'TVShow'
     }
     return null
   }
 },

In our resolvers.js, we’ve also converted the return value for the “name” field on a TV show to “title.”

 TVShow: {
   title: (tvShow) => {
     return tvShow.name
   }
 },

The server automatically calls the __resolveType function for each object in the genres resolver and then creates an object of type Movie or TVShow accordingly. Let’s try it by restarting our server and refreshing the web page.

Fetching movies and TV shows by genre

Our results contain both movies and TV shows, from the sci-fi and action genres. There’s not much attempt at sorting these results, but we could certainly do that on the server side if we wanted to. But at least we have a list of Media types, and using this interface we’ve combined different types into one list. This technique is useful for implementing things like search and search results, or users that have different roles, etc.

Outro

Well, there you have it. We’ve wrapped the TMDb API in a GraphQL server. Client applications can make calls for the data they need regarding movies, TV shows, genres, depending on the needs of the UI.

What’s more is that we could wrap more than just one API, and it would be seamless to the client as to which API is returning data. Each resolving function can call one or more APIs to piece together data. The schema for the GraphQL server would still just represent the overall types of data that can be queried.

You can see the simplicity here for any client application that may be making requests. You only need to list the fields that are relevant for a piece of UI that you may be working on. Here, if we’re only concerned with getting a list of top movies and links to their trailers for a movie list summary page, then that is exactly what we get back. Contrast that to traditional REST APIs where we might get back a whole bunch of other fields that we may not even need. Or, we have to make multiple calls to various API endpoints to piece together the info we need for our UI component.

Hopefully, this tutorial has given you a good basic knowledge of how to implement a GraphQL server, as well as a knowledge as to how to wrap other APIs. By now your wheels may be spinning as to the implications of how it works in the wild and on actual servers, and with huge data sets, connection to databases, etc. We may touch on these in future articles. As of the writing of this article, there has been rapid industry movement, such as the release of hosted GraphQL services like graph.cool, and there are big companies moving into the space as well, such as Amazon’s AWS AppSync for hosting GraphQL endpoints and hooking it into the rest of the AWS ecosystem. (The graph.cool folks have also released a generic GraphQL platform called Prisma that can be deployed in the AWS ecosystem or others.)

Facebooktwitterredditlinkedinby feather

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>