In this exercise you will create an API to list movie data as JSON. Next you will learn how to fetch JSON data from the server into the browser and display the data in the developer tools console.
You will learn to:
- Create an serverside API endpoint that returns JSON data describing movies
- Output the data to the console in the frontend
📖 Before we start coding we need to plan out the data model for movies.
📖 We want the API to describe the following movie details:
- Title ("The Godfather")
- Release date ("1927-03-14")
- Overview ("Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family.")
- Vote average (from 0.0 to 10.0, eg. 8.7)
- Vote count (number of votes)
- Movie poster (image)
📖 In web API semantics we call different kinds of data resources. Movie is a resource our API will expoose.
📖 We use URLs to organize different resources in the API. We use HTTP metods like GET
, POST
, DELETE
and PUT
combined with the URL to describe different API operations:
- Get all movies:
GET http://localhost:3000/movie/
- Get single movie:
GET http://localhost:3000/movie/{id}/
(where{id}
can be1
, for instance) - Insert a movie:
POST http://localhost:3000/movie/
(The movie data we will insert is in the request's body) - Delete a movie:
DELETE http://localhost:3000/movie/{id}/
- Update a movie:
PUT http://localhost:3000/movie/{id}/
(The movie data we will insert is in the request's body)
📖 In this exercise we are going to concentrate on implementing the first API operation, get all movies.
📖 JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax. It is commonly used for transmitting data in web applications (e.g., sending some data from the server to the client, so it can be displayed on a web page, or vice versa).
📖 A JSON representation of a single movie can look something like this:
{
"id": 1,
"releaseDate": "1972-03-14",
"overview": "Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family.",
"title": "The Godfather",
"voteAverage": 8.7,
"voteCount": 12345,
"posterUrl": "/posters/1.jpg"
}
In JavaScript code a movie would look like:
let movie = {
id: 1,
releaseDate: "1972-03-14",
overview: "Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family.",
title: "The Godfather",
voteAverage: 8.7,
voteCount: 534533,
posterUrl: "/posters/1.jpg"
}
📖 Very similar to JSON as you can see.
📖 A list of movies will look something like this in JSON:
{
"movies": [
{
"id": 1,
"releaseDate": "1972-03-14",
"overview": "Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family. When organized crime family patriarch, Vito Corleone barely survives an attempt on his life, his youngest son, Michael steps in to take care of the would-be killers, launching a campaign of bloody revenge.",
"title": "The Godfather",
"voteAverage": 8.7,
"voteCount": 12345,
"posterUrl": "/posters/1.jpg"
},
{
"id": 2,
"releaseDate": "1994-09-23",
"overview": "Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family. When organized crime family patriarch, Vito Corleone barely survives an attempt on his life, his youngest son, Michael steps in to take care of the would-be killers, launching a campaign of bloody revenge.",
"title": "The Shawshank Redemption",
"voteAverage": 8.6,
"voteCount": 534533,
"posterUrl": "/posters/2.jpg"
}
]
}
📖 We now know what the data model we want to implement looks like. Let's code!
📖 In order to map URLs in our API (eg. /movie
) to a piece of code we need to define a route. A route in Express consists of a method (GET
, POST
, etc.) and a URL (eg. /movie
) and a function that handles a request and a response parameter.
✏️ Open up routes.js
inside the /src/backend
folder.
💡 Notice that we already have a route defined:
router.get('/helloworld', async (req, res) => {
const example = {
message: 'Hello Nerdschool 🎉🎉🎉'
};
res.send(example);
});
router.get
will define a route that responds toGET
requests- The first parameter
'/helloworld'
defines what URL the route will respond to - the arrow function
async (req, res) => {}
handles the HTTP request (req
) and the HTTP response (res
). res.send(someObject)
will convert an object to JSON and return it in the HTTP response to the browser.
✏️ Create a new route using the example above that responds to GET
requests on the URL /movie
. Click on "Show solution" below if you are stuck.
Show solution
router.get('/movie', async (req, res) => {
// movie API code here
});
📖 To start things off we are going to make this new /movie
API endpoint return some hardcoded movie data.
✏️ Inside the route callback function (where you see the comment "movie API code here"), declare a new variable called movies
. Set the value of movies
to a new object with a property called movies
. Set the value of the movies
property to a new array of movie objects. Click on "Show movie data example" below to see an example:
Show movie data example
const movies = {
movies: [
{
id: 1,
releaseDate: "1972-03-14",
overview: "Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family. When organized crime family patriarch, Vito Corleone barely survives an attempt on his life, his youngest son, Michael steps in to take care of the would-be killers, launching a campaign of bloody revenge.",
title: "The Godfather",
voteAverage: 8.7,
voteCount: 12345,
posterUrl: "/posters/1.jpg"
},
{
id: 2,
releaseDate: "1994-09-23",
overview: "Framed in the 1940s for the double murder of his wife and her lover, upstanding banker Andy Dufresne begins a new life at the Shawshank prison, where he puts his accounting skills to work for an amoral warden. During his long stretch in prison, Dufresne comes to be admired by the other inmates -- including an older prisoner named Red -- for his integrity and unquenchable sense of hope.",
title: "The Shawshank Redemption",
voteAverage: 8.6,
voteCount: 534533,
posterUrl: "/posters/2.jpg"
}
]
};
✏️ To make the new API endpoint return the value of movies
as JSON, add res.send(movies);
after the movies
variable.
💡 Why can´t we just return the array instead of a object with a property containing an array? We could, but returning an object with an array inside it makes the API code more extendable.
📖 In order to verify that our new API operation is working, we need make some changes the frontend code located inside the src/frontend
folder.
✏️ Open up main.js
. This file is the main entrypoint for the JavaScript code that runs in the browser.
💡 Notice that main.js
has some code in it already that uses the Fetch browser api to retreive data from the server:
const helloWorldApiResponse = await fetch('/helloworld');
const helloWorldData = await helloWorldApiResponse.json();
✏️ Change this code to instead fetch data from our new API endpoint /movie
. Display the output in the browser dev tools console using console.log()
.
❗ Make sure to also remove the following code below, or the code will throw an error:
- const body = document.querySelector("body");
- var paragraph = document.createElement('p');
- paragraph.innerText = `Message from API: '${message}'`;
- body.appendChild(paragraph);
✏️ To view the output, open Chrome Dev Tools and click the Console tab.
💡 New to Chrome Dev Tools? See official Chrome docs Open Chrome Dev Tools and Console overview to learn more. Inspect network activity is very useful as well.
✏️ You should now see the data you returned from the backend displayed in the console.
📖 Stuck? Click "Show solution" to see what main.js
should look like:
Show solution
const movieApiResponse = await fetch('/movie');
const movieData = await movieApiResponse.json();
const { movies } = movieData;
console.log(movies);
📖 In order to structure the code a bit better, we want to move the fetch-related frontend code into a separate module.
✏️ Create a new file called api.js
inside the /src/frontend
folder.
✏️ Add the following code to api.js
:
export const getMoviesFromApi = async () => {
// Fetch code here
}
✏️ Move the fetch-related code from main.js
to the getMoviesFromApi
function. Make sure to return the movie data from the getMoviesFromApi
function.
📖 Stuck? Click "Show solution" to see what the getMoviesFromApi
function should look like:
Show solution
export const getMoviesFromApi = async () => {
const movieApiResponse = await fetch('/movie');
const movieData = await movieApiResponse.json();
return movieData;
}
✏️ In main.js
, import the function at the top:
import { getMoviesFromApi } from "./api.js";
✏️ Call the getMoviesFromApi
method using await
and assign the result to a variable called getMoviesFromApiResult
.
❗ Remember that the get movies API response has the following format:
{
movies: [ // array of movies ]
}
✏️ Use destructuring (see cheatsheet.js
) or dot notation (getMoviesFromApiResult.movies
) to create a new variable called movies
containing the array of movies from the getMoviesFromApiResult
object. Use console.log
to log the output to verify the data is correct.
📖 Click on the "Expand" triangle icon in front of log messages in the the Chrome Dev Tools Console to view the data inside the log message. See [Chrome Dev Tools docs[(https://developer.chrome.com/docs/devtools/console/log#javascript) for more info.
📖 Stuck? Click "Show solution" to see what main.js
should look like:
Show solution
import { getMoviesFromApi } from "./api.js";
const movieData = await getMoviesFromApi();
const { movies } = movieData;
console.log(movies);
Now that we have succefully returned some data from the serverside API to the browser, we want to make it a bit more realistic.
✏️ Open up movies.json
inside the src/backend/data
folder.
📖 This is file contains a list of movies from themoviedb.org using the data model we defined above.
📖 We want our application to read this data from disk and return it via the list movies API operation we have created.
✏️ Create a new JavaScript module by creating a new file called database.js
inside src/backend/data
with the following contents:
import fs from "fs/promises";
const dataFilePath = "./backend/data/movies.json";
export const getMovies = async () => {
const file = await fs.readFile(dataFilePath, 'utf8');
return JSON.parse(file);
}
export
means "expose this function outside the module"getMovies
is assigned to a function that returns the contents ofmovies.json
- Data is read from disk using the Node.js API
fs.readFile
- The JSON data is converted to an JavaScript object using
JSON.parse
and returned
✏️ Open routes.js
and insert the following code at the top of the file to import the getMovies
function we created from the database.js
module:
import { getMovies } from "./data/database.js";
✏️ Replace your example data returned from the /movie
API endpoint:
router.get('/movie', async (req, res) => {
const movies = await getMovies();
res.send(movies);
});
✏️ Open up the console in Chrome and verify that the new movie data is being logged.
❗ Note: In a "real" application we would use a proper database instead of files on disk, for instance PostgreSQL or MongoDB.
We have a working movie API! Now we need to do something interesting with it.