Video Watchlist Code-Along Activity
Follow the instructions below to create an Express web app that holds a list of video links. These video links should persist on the server by way of the Repl.it Database.
Getting Started
First, get a basic Node.js app up and running.
- Create a new Node.js Repl project
- Name it "Video Watchlist"
- Create a new file named app.js in the current directory
- For test purposes, add a
console.log('hello')statement to app.js - Create another new file, this one named .replit
- In the .replit file, add
run = "node app.js" - Click the "Run" button to run the program, and make sure it works so far
A Basic Express/EJS App
Next, create a basic web server using Express and EJS.
EJS Page
In the Repl project, start by creating a new folder named views. Then, within that folder, create a new file named index.ejs. Open the index.ejs file, and add the following code:
<html>
<body>
<h1>Video Watchlist</h1>
<p>This is a place to store all the videos you want to watch.</p>
</body>
</html>
App Code - Setup
Now, fill out the basic Express/EJS setup code in the app.js file. Start by removing all existing code from the file, then follow the steps below.
- At the top, create a
const expressvariable set torequire("express") - Next, create a
const ejsvariable set torequire("ejs") - Under that, create two
constvariables forhostnameandport"0.0.0.0"and8080respectively
- Next, initialize an
appvariable by callingexpress() - Under that, use
app.setto set the"view engine"to"ejs"
const express = require("express");
const ejs = require("ejs");
const hostname = "0.0.0.0";
const port = 8080;
let app = express();
app.set("view engine", "ejs");
App Code - Homepage
Now that the introductory part is complete, it's time to create a homepage.
- Define a new function named
homePage - Give the function two parameters:
requestandresponse - In the body of the function, call
response.renderand pass in"index" - Under the
homePagedefinition, make some space - Call
app.getand pass in"/"andhomePage- This makes the
homePagefunction run when the root URL is hit
- This makes the
- Under that, define the basic
listenCallbackfunction - Finally, call
app.listen, passing inport,hostname, andlistenCallback
Run the program, and verify that the index.ejs code is properly rendered on the homepage!
function homePage(request, response) {
response.render("index");
}
app.get("/", homePage);
function listenCallback() {
console.log("Server Running");
}
app.listen(port, hostname, listenCallback);
Adding Videos
Obviously, there are no videos yet. Before viewing videos, add the ability for the user of the site to add videos to the list.
A Form in EJS
First, open the index.ejs file, and add a <form></form> element under the existing <p></p> in the <body></body>. It should look like this:
<form action="/add">
<p>Add a new video:</p>
<input type="text" name="link" placeholder="Video Link">
<input type="text" name="note" placeholder="Video Note">
<input type="submit">
</form>
Notice the following parts of the HTML:
- The
actionattribute set to"/add"- It will be necessary to create this endpoint in the app.js code
- The
nameattributes on the<input>elements- These will correspond to the video data in the database
Run the program again, and verify that the form appears on the homepage. Nothing should happen on submit yet... that comes later.
More Setup
To prepare the code to handle someone submitting the form, start with a few more setup steps. Open up the app.js file, and follow the steps in the top section.
- Create a
const urlvariable set torequire("url")- This module will be used to parse form results
- Create a
const uuidvariable set torequire("uuid")- This module will be used to generate unique IDs
- Create a
const Databasevariable set torequire("@replit/database")- This module will allow the code to connect to the Repl.it database
- Create a new variable named
dband set it tonew Database()- The
dbvariable will be the connection!
- The
const url = require("url");
const uuid = require("uuid");
const Database = require("@replit/database");
let db = new Database();
The /add Endpoint
Now that the database exists, it's time to add some stuff to it. Every time a user submits the form, it should take that data, generate a unique ID for it, and create a new video object in the database.
Note: Using an HTTP
GETrequest to add data is kind of cheating - it should be aPOSTrequest, butGETis a little easier to use for now
The addVideo Function - Basics
- Under the
homePagefunction definition, create some space - Define a new function named
addVideo - Give the
addVideofunction two parameters:requestandresponse - Make the
addVideofunction asynchronous by addingasyncin front of thefunctionkeyword
The addVideo Function - Body
- In the body of the
addVideofunction, create a variable namedparsedUrl - Set
parsedUrltourl.parse(request.url, true)- This contains the data from the form
- Under that, create a new variable named
newKey - Set
newKeyto"video_" + uuid.v4()- This will create a new unique ID prefixed with
video_
- This will create a new unique ID prefixed with
- Next, call the
db.setfunction - Pass in
newKeyfor the first argument- This is the unique key for the created movie object
- Pass in
parsedUrl.queryfor the second argument- This contains all the data from the form:
linkandnote
- This contains all the data from the form:
- Add the
awaitkeyword in front of thedb.setcall- This makes sure the function will not return until the value has been set
- Finally, call
response.redirectand pass in"/"
Hooking Up the addVideo Function
- Under the
addVideodefinition, callapp.get - Pass in
"/add"as the first argument - Pass in
addVideoas the second argument
Now, it should actually be possible to add some data to the database! Run the program, and verify that no errors occur when submitting the form. Go to YouTube to find some video links. For testing purposes, it may be beneficial to use console.log in the body of the addVideo function, to make sure the code is running properly.
async function addVideo(request, response) {
let parsedUrl = url.parse(request.url, true);
let newKey = "video_" + uuid.v4();
await db.set(newKey, parsedUrl.query);
response.redirect("/");
}
app.get("/add", addVideo);
Viewing Videos
Now there may be some videos in the database, but what good are they if no one can see them? Update the homepage so that it displays the list of videos.
Defining the getVideos Function
First, define a function to retrieve data from the database, and put it in a nice manageable format.
- Above the
homePagefunction, define a new function namedgetVideos - Make the
getVideosfunctionasync - In the body of the function, create a new variable named
allVideoKeys - Use
db.listto get all keys prefixed with"video_"- This will retrieve all the video objects added through
addVideo
- This will retrieve all the video objects added through
- Add an
awaitbeforedb.listso that the code does not continue until the keys are retrieved - Under that, create a new empty list variable named
allVideos- This will store the nicely formed video objects
- Create a
forloop structure looping throughallVideoKeys - In the body of the
forloop, get the current key ascurrentKey - Retrieve the value for the current key using
await db.get- Store that in a variable named
video
- Store that in a variable named
- Set the
"id"property of thevideoobject tocurrentKey- This will come in handy later
pushthevideoobject to theallVideoslist- Outside of the
forloop, return theallVideoslist
Now, the getVideos should properly retrieve every video in the database! Next up, it's time to use the function.
async function getVideos() {
let allVideoKeys = await db.list("video_");
let allVideos = [];
for (let i = 0; i < allVideoKeys.length; i++) {
let currentKey = allVideoKeys[i];
let video = await db.get(currentKey);
video["id"] = currentKey;
allVideos.push(video);
}
return allVideos;
}
Updating the homePage Function
The homePage function should send along some data to the "index" template. This data should be retrieved using the getVideos function!
- Find the the
homePagefunction definition - Make the
homePagefunctionasync- This is necessary because it will need to
awaitthegetVideosfunction
- This is necessary because it will need to
- At the top of the body of the function, create a variable named
allVideos - Set
allVideostoawait getVideos() - Create a new object named
renderData - Set the
videosproperty ofrenderDatato beallVideos - Pass in
renderDatato the call toresponse.render
Now, the index.ejs template should have access to all the video data!
async function homePage(request, response) {
let allVideos = await getVideos();
let renderData = {
videos: allVideos
};
response.render("index", renderData);
}
Updating the index.ejs Template
All that's left is to update the index.ejs file so that it can handle the new data it has been given. Open it up and follow the steps below.
- Make some space above the
<form></form>element in the HTMLbody - Create a
<ul></ul>element - Between the
ultags, create aforloop structure with EJS- This should loop through the
videoslist - Make sure to add the
{and}
- This should loop through the
- In the body of the
forloop, create anlielement - In the body of the
lielement, create anaelement - Set the
hrefof theato point to the current video link<%= videos[i].link %>
- Set the content of the
ato be the current video note<%= videos[i].note %>
- Under the
</ul>, create anifstatement structure - For the
ifcondition, check if thevideoslist is empty - In the body of the
if, add apsaying no videos have been added
Run the program, and verify that some videos appear! Try adding a few more, and making sure the page updates automatically to view them. This web server is successfully storing data and surfacing it!
<ul>
<% for (let i = 0; i < videos.length; i++) { %>
<li>
<a href="<%= videos[i].link %>"><%= videos[i].note %></a>
</li>
<% } %>
</ul>
<% if (videos.length < 1) { %>
<p>No videos have been added to the list 😔</p>
<% } %>
Removing Videos
Now, to make the video watchlist functional, add the ability to remove videos. That way, if a user watches a video on their watchlist, they can actually remove it from the list.
The /delete/:id Endpoint in JavaScript
Start by working in the app.js file to create a /delete/:id endpoint.
- Under the
addVideofunction, define a new function nameddeleteVideo - Make the function
async - Give the function two parameters:
requestandresponse - Outside the function, nder the other
app.getcalls, create another one - Pass in
/delete/:idanddeleteVideo- This will call the
deleteVideofunction whenever the user goes to/delete/<some id>
- This will call the
- In the body of the
deleteVideofunction, create a variable namedvideoId - Set
videoIdto berequest.params.id- This will be whatever comes after the slash; the
:idin/delete/:id
- This will be whatever comes after the slash; the
- Call
db.delete, passing in thevideoIdvariable - Add the
awaitkeyword in front ofdb.delete - Use
response.redirectto send the user back to the root:"/"
Try going to a /delete/ url with a valid id appended on the end. The object should disappear from the list!
async function deleteVideo(request, response) {
let videoId = request.params.id;
await db.delete(videoId);
response.redirect("/");
}
app.get('/delete/:id', deleteVideo);
Adding a Delete Link in EJS
Now it is possible to directly delete data, but it would be nice if there were a way to do it without having to copy and paste a URL. Follow the instructions below to create a delete link that will appear next to each video. Open the index.ejs file to get started.
Styling
First, for simplicity, copy the following code into the above the <html></html> element, above the <body>:
<head>
<style>
a.del {
color: pink;
text-decoration: none;
}
a.del:hover {
color: red;
}
</style>
</head>
This will make things look a little nicer.
Adding the Link
Next, add the actual link. For a given video, the link should go to /delete/ with the video's id tacked onto the end.
- Find the
awithin theliin theforloop - Under that, create another
aelement - Add a
classattribute ofdel(for styling purposes) - In the content for the
a, paste in this times character:✕ - Create an
hrefattribute on thea - Set it to
"/delete/" - After the slash, add EJS code to get the
idof the current video<%= videos[i].id %>
Run the code again, and verify that a little "X" appears next to all the videos! Clicking the "X" should make the video disappear.
<a class="del" href="/delete/<%= videos[i].id %>">✕</a>
The video watchlist is fully functional!
Final Code
views/index.ejs
<html>
<head>
<style>
a.del {
color: pink;
text-decoration: none;
}
a.del:hover {
color: red;
}
</style>
</head>
<body>
<h1>Video Watchlist</h1>
<p>This is a place to store all the videos you want to watch.</p>
<ul>
<% for (let i = 0; i < videos.length; i++) { %>
<li>
<a href="<%= videos[i].link %>"><%= videos[i].note %></a>
<a class="del" href="/delete/<%= videos[i].id %>">✕</a>
</li>
<% } %>
</ul>
<% if (videos.length < 1) { %>
<p>No videos have been added to the list 😔</p>
<% } %>
<form action="/add">
<p>Add a new video:</p>
<input type="text" name="link" placeholder="Video Link">
<input type="text" name="note" placeholder="Video Note">
<input type="submit">
</form>
</body>
</html>
app.js
const express = require("express");
const ejs = require("ejs");
const url = require("url");
const uuid = require("uuid");
const Database = require("@replit/database");
const hostname = "0.0.0.0";
const port = 8080;
let db = new Database();
let app = express();
app.set("view engine", "ejs");
async function getVideos() {
let allVideoKeys = await db.list("video_");
let allVideos = [];
for (let i = 0; i < allVideoKeys.length; i++) {
let currentKey = allVideoKeys[i];
let video = await db.get(currentKey);
video["id"] = currentKey;
allVideos.push(video);
}
return allVideos;
}
async function homePage(request, response) {
let allVideos = await getVideos();
let renderData = {
videos: allVideos
};
response.render("index", renderData);
}
async function addVideo(request, response) {
let parsedUrl = url.parse(request.url, true);
let newKey = "video_" + uuid.v4();
await db.set(newKey, parsedUrl.query);
response.redirect("/");
}
async function deleteVideo(request, response) {
let videoId = request.params.id;
await db.delete(videoId);
response.redirect("/");
}
app.get("/", homePage);
app.get("/add", addVideo);
app.get('/delete/:id', deleteVideo);
function listenCallback() {
console.log("Server Running");
}
app.listen(port, hostname, listenCallback);
Individual Exercises
After the code-along project is complete, go to this page to work on some individual exercises.