Node.js movie app - Receive data from the API

The movie app can now ask questions of the user and display the responses provided. In this part of the series we'll make the first request to the API and write real pieces of movie data to the console. Part 3.

Part 1: Creating the framework

Part 2: Making it interactive

Our app is equipped with the tools which write data to the console in various colours and it can also display our responses by prompting us to answer pre-defined questions.

The next step is to connect to the movie database and receive data according to the responses.

The API

First, we need a database where we can fetch movie data from.

Several good movie databases exist out there and we’ll use the OMDb API.

Getting the API key. The database is free to use but you will need an API key to access its content. Simply click on the link and go to the API Key tab. Choose the free option and enter your email. The API key will be in your mailbox within a few seconds. I haven’t received any spams or unsolicited emails since I subscribed and the API is really good!

Using the API. The API is very easy to use. You will obviously need your key to access the database and have to include either the id or the title of the movie or, you can make a general search on the movies, too.

We’ll use both approaches in the app, but before fetching the first movie, we’ll need to install a cool library that helps us connecting to APIs and receiving data from them.

Fetching data

One of these libraries is request-promise-native.

Installing request

request-promise-native supports the classic CRUD methods, and therefore it’s relevant to our purposes of fetching data. As its name suggests it returns the response wrapped in a Promise and uses the native ES6 promises.

Installation. The library depends on the original module called request, so we have to install both.

If you are not in the project folder, navigate to it and type npm install --save request request-promise-native.

Adding a request

Creating a new file. We have started to write features in separate modules by extracting prompts to their own file to keep index.js clean. Let’s continue this by creating a requests.js file in the modules library, so type touch modules/requests.js in the terminal.

Open the newly created requests.js file and require request-promise-native at the top of the file:

const rp = require('request-promise-native');

Adding the API key. The query to the database must contain a valid API key we received from the maintainer, and we’ll have to include it into the URL.

We won’t include the API in the code in this application. So how can it be added to the URL?

One way to add the key is by using an environment variable.

Environment variables. Environment variables are useful concepts in Node.js. A frequent use case of them is to separate development, test and production environments, but in this case I use them because I don’t want to share my API key with the world. They are only valid for the time the process runs, which means that we have to provide it each time when we run application.

The first request. This means that our request function should contain the API key as a parameter.

The function also has to work with other parameters to receive a useable response from the API.

The first question we ask of the users of our fantastic app is on the title of the movie, and then we prompt them to narrow their search by defining the type (movie, series or episode) of the movie.

We’ll receive the title (or a part of it) and the type in the response object and we’ll also add them as parameters of our request function, which can look like this:

const getMovieDataFromDB = (movieTitle, movieType, apiKey) => {
  const options = {
    method: 'GET',
    uri: `https://www.omdbapi.com/?s=${ movieTitle }&type=${ movieType }&apikey=${ apiKey }`,
    json: true
  };

  return rp(options);
};

getMovieDataFromDB gives back a Promise returned by request-promise-native. We need to feed in some options though.

options object contains few options from the many available. We want to GET data from the database, so we need to specify this in the method property. We’ll also have to declare the uri where the data is fetched from and we want the data in a form of a JSON object.

Export it. The user responses are already available in index.js as movieData, so we’ll need to call getMovieDataFromDB here. In order to do this, we have to export the function:

module.exports = { getMovieDataFromDB }

Require it. As such, we have to require it in index.js at the top of the file:

const { getMovieDataFromDB } = require('./modules/requests');

Getting real data

Now that we have getMovieDataFromDB available in index.js, we can feed in the responses the user gave us.

We know that Inquirer.js saves the answers in an object with properties of movieTitle and movieType. We can extract these properties from the returned object using object destructuring.

In index.js, replace the movieData inside the try block with the following code:

const { movieTitle, movieType } = await getMovieTitleFromUser();

From now we can use the user responses as movieTitle and movieType when making a request to the API:

const movieDataFromDB = await getMovieDataFromDB(movieTitle, movieType);

We call getMovieDataFromDB here with the user’s responses and because the function returns a promise, we await the response. Let’s save the result in the movieDataFromDB variable.

Adding the API key. If you run the app now and try to make a search on a movie, you will get a friendly error message that the API key is invalid. Because this is not far from the truth, let’s add the API key to the list of environment variables as discussed above.

Environment variables can be accessed as a property of the process.env object. Let’s create an apiKey variable outside the vorpal call:

// ...
figlet('Movies', (_, data) => {
  console.log(chalk.cyan(data));

  const apiKey = process.env.API_KEY;

Environment variables are named in upper case by convention and ours will surprisingly called API_KEY (it can be named to anything expressive).

Let’s add apiKey to getMovieDataFromDB as the third argument:

const movieDataFromDB = await getMovieDataFromDB(movieTitle, movieType, apiKey);

Run the script. We can now start the script again, but this time with API_KEY provided. First, specify the name of the environment variable (API_KEY) and then add the key you received in email.

API_KEY=yourapikey npm start

If you run the following command, then type search, enter the title of the movie and specify the type, you should receive an object with one or more movies matching the title and type you entered.

For example, upon entering Jason Bourne and selecting Movie, we’ll receive quite a few objects similar to this:

 {
  "Title": "Jason Bourne",
  "Year": "2016",
  "imdbID": "tt4196776",
  "Type": "movie",
  "Poster": "https://m.media-amazon.com/images/M/MV5BNGJlYjVkMjQt[email protected]._V1_SX300.jpg"
}

The script is working!

Adding API error message

We declared the apiKey outside the vorpal body because we might not want to start the script if the API key is not entered.

We can therefore add a quick check if the API key is provided right under the apiKey variable declaration:

if (!apiKey) {
  console.log(chalk.red('Please enter your API key!'));
  return;
}

It’s fairly straightforward; if the apiKey doesn’t exist, we display a gentle message and simply return, so the script won’t start.

Invalid API keys are handled by the API itself and those issues will directed to the catch block.

The modified and new files should look like this:

// index.js

const chalk = require('chalk');
const figlet = require('figlet');
const vorpal = require('vorpal')();

const { getMovieDataFromDB } = require('./modules/requests');
const { getMovieTitleFromUser } = require('./modules/user-inputs');

figlet('Movies', (_, data) => {
  console.log(chalk.cyan(data));

  const apiKey = process.env.API_KEY;
  if (!apiKey) {
    console.log(chalk.red('Please enter your API key!'));
    return;
  }

  vorpal
    .command('search', 'Start searching movies')
    .action(async () => {
      try {
        const { movieTitle, movieType } = await getMovieTitleFromUser();
        const movieDataFromDB = await getMovieDataFromDB(movieTitle, movieType, apiKey);
        vorpal.log(chalk.green(JSON.stringify(movieDataFromDB, null, 2)));
      } catch (error) {
        vorpal.log(chalk.red(error));
      }
    });

  vorpal
    .delimiter('Movies$')
    .show();
});

and

// requests.js

const rp = require('request-promise-native');

const getMovieDataFromDB = (movieTitle, movieType, apiKey) => {
  const options = {
    method: 'GET',
    uri: `https://www.omdbapi.com/?s=${ movieTitle }&type=${ movieType }&apikey=${ apiKey }`,
    json: true
  };

  return rp(options);
};

module.exports = { getMovieDataFromDB }

We haven’t touched user-inputs.js this time, so it remained the same as it was in Part 2.

Conclusion

The app can now receive real data from the movie database and it’s really cool!

In the next part we’ll select the movie we like from those the database gave us and will receive more detailed information on the selected movie.

Thanks for reading and see you next time.