CS 312 - Software Development

CS 312 - Practical Three

Goals

  • Convert Film Explorer to use a RESTful API to fetch data
  • Practice using fetch, await and async \

Today, you will be converting the standalone version of Film Explorer to use fetch to load its data.

Prerequisites

  1. Click through to the GitHub classroom assignment to create your private repository. Then clone that newly created repository to your local computer as you have done previously.

  2. Install the package dependencies by running npm install inside the root directory of the newly cloned repository.

  3. Before you make any changes, create a feature branch named "use-fetch" to segregate your modifications from the master branch:

git checkout -b use-fetch

Fetching Data with AJAX

This version of Film Explorer, much like Simplepedia is making use of webpack to load the data via an import statement.

import filmData from './films.json';

While this works, it is atypical. It can cause long initial load times and worse, it means the user only works with a local version of the data, so changes don't persist (try rating a few films and then reloading the page). Your task today is to adapt the standalone Film Explorer to fetch its data from the Film Explorer API and persist the ratings.

The API server

You will be communicating with a server running on basin at http://basin.cs.middlebury.edu:3042. The REST interface can be found at /api/films, which can be optionally followed by an id. The full API is:

EndpointMethodAction
/api/filmsGETFetch the entire movie collection as a JSON array
/api/films/idGETFetch the movie with id as JSON
/api/films/idPUTUpdate the movie with id (the new JSON-serialized movie object should be in the request body)

You can interact with this API directly in your browser by visiting http://basin.cs.middlebury.edu:3042/api/films, to see a full list of the movies, or http://basin.cs.middlebury.edu:3042/api/films/59967 to see the movie with an id of 59967 (Looper).

Fetching the Films

Following the example in class, adapt the FilmExplorer component (in FilmExplorer.js) to fetch its data from the server API. Specifically:

Step one: Remove the line import filmData from '../../data/films.json'; as we will no longer load the movies directly.

Step two: Replace the simple call to setFilms with the fetch call shown in lecture.

const getData = async () => {
  const response = await fetch('http://basin.cs.middlebury.edu:3042/api/films');

  if (!response.ok) {
    throw new Error(response.statusText);
  }

  const filmData = await response.json();

  setFilms(filmData);
};

getData();

Launch the development server with npm run dev. Make sure you can successfully load the movie data from the server.

Updating the Movie Rating

In the standalone Film Explorer the setRating method in the FilmExplorer component uses map() to create a new list in which the film object being rated is replaced with a new one with a new rating. Your next task will be to modify this function to persist changes back to the server.

Rather than starting by updating the objects, we will first create a modified version of the film, and send it to the server. The server will update its record and send the film's data back. Once the response has been received, you can modify the state with the new record. By using the data returned from the server, we are giving the server the opportunity to validate and possibly further modify the data (in this case, the data is not being further modified, but we will see instances when this is important later).

The steps in your new setRating method (replacing all of the existing code):

Step one: Comment out the body of the setRating function. We will want to refer back to this later.

Step two: Find the full record for the film being modified. Since the setRating method only has the movie's id, you will need to use the find method on films to obtain the complete film object. Check out the documentation for Array.find.

Step three: Create an updated film object with the new rating. Recall that you don't want to modify state, so make a copy of the movie using the spread pattern, i.e.

const newFilm = { ...oldFilm, rating };

Step four: Construct your PUT request to `/api/films/${filmid}` (URL created with string interpolation). By default fetch uses GET. To create a PUT request, supply an optional init argument specifying the method, body (serialized JSON representation of the new movie object) and headers specifying that the client is sending JSON (docs):

fetch(`http://basin.cs.middlebury.edu:3042/api/films/${filmid}`, {
  method: 'PUT',
  body: JSON.stringify(newFilm),
  headers: new Headers({ 'Content-type': 'application/json' }),
});

Step five: Handle the fetch response. You can basically set up the sequence of actions exactly the same as you did for fetching the entire collection. Add const response = await in front of your fetch call. Then check the response and see if it is "ok", parse the json to get the new copy of the film back.

Step six: Update the state with the new data. Use the map pattern from the commented out code to create a new Array, replacing the old film record with the new one. You do not need to use the spread syntax, just return the new film record at the appropriate moment. Then call setFilms with the new array.

Step seven: Try it out. Rate some things. You should see that they will persist when you reload the page now.

Note that you are all sharing the same server now, so when you make a change, you are actually changing the rating for everyone. However, since we don't poll for changes, and we only receive data about films that we just changed, you won't see the effect of this unless you reload the page.

Finishing Up

To finish up, we are going to merge the changes you made back into the master branch. Rather than doing this directly on the command line, we are again going to make use of GitHub's "pull request" (PR) mechanism. This is about building habits that will be useful when we get to working on teams. The PR signals that you are ready to merge into the master branch and gives other team members an opportunity to review your changes before master is updated.

Add and commit your changes to your use-fetch branch. Push those changes GitHub on the use-fetch branch.

git push origin use-fetch

Open your repository at GitHub and create a pull request (PR) to merge the changes from your newly pushed use-fetch branch onto the master branch. You should be able to create the PR on the main page of your repository or alternately you can create a PR from the "Pull request" tab.

Navigate to the page for your PR. Here you will see some notification indicating if the branch can be cleanly merged into the master branch. Make sure it indicates that a good merge can be made. press the Merge button to merge your changes. Once you have merged the changes you can delete the feature branch on GitHub using the button that replaces the 'Merge' button.

Clean up your local repository after the merge. Return to the master branch on your local computer, pull the changes from GitHub pruning deleted remote tracking references. Then delete your local branch. To avoid mistakes when deleting branches, add the --dry-run option to the --prune option, e.g. git pull --prune --dry-run to double check before actually pruning anything. The command sequence is:

git checkout master
git pull --prune
git branch -d use-fetch

Finally, submit your repository on Gradescope.