
Welcome!
Everything is fine.
EJS View Engine, GET vs POST¶
Today, we'll learn about:
- EJS
- static files
- GET versus POST
- Plan
- Announcements
- Big Picture
- What is a Database?
- Where is the Database? What does it Look Like?
- What's the Difference between a Database and a Website?
- Database Representation
- Express Apps
- Express Handlers
- EJS Substitutions
- Templating in General
- Compared to Browser-Side Dynamic Values
- EJS Conditionals
- EJS Lists
- Partials
- App Structure
- GET vs POST
- GET vs POST Facts
- Quiz Questions
- Examples
- Running an App
- SSH Tunnel
- What Routes are Available?
- Running the App
- Exercise 0: Explore
- Exercise 1: Displaying a Dictionary
- Exercise 2: Pre-filling a form
- Exercise 3: Send the Data to your Express App
- Extra Credit: Pre-select from a SELECT Menu
- Discussion
Plan¶
- Announcements
- The Big Picture
- Recap EJS
- Recap GET v POST
- Quiz Questions
- Exercises
Announcements¶
- oops I forgot to send out the second quiz about GET vs POST; sorry
- Grading Status.
- People did really well on Wordle. Bravo!
- I'll catch up on the others this weekend
- Twenty Questions is optional, but let me know if you want to complete it anyhow
Big Picture¶
Let's look again at the big picture.
What is a Database?¶
Very generally, a database stores data, but that definition is so generally as to be useless. So, let's be more specific:
A database
- stores data
- the data is in machine-friendly form
- the representation is easy and efficient to search. For example, hashtables (dictionaries) or b-trees.
- the database may have additional indexes to speed up certain kinds of searches.
- the representation is easy and efficient to insert, update and delete
For the most part, we trust that the DBMS — Database Management System does all these things correctly and efficiently. So, we will treat this as an "opaque box" or abstraction barrier, and we won't worry about how the DBMS is implemented. (That would be a different course.)
Where is the Database? What does it Look Like?¶
It's in the cloud, meaning servers at MongoDB data centers.
It doesn't really look like anything. Well, a seething mass of bits.
But we work with data all the time without knowing what it looks like:
- Google Docs
- Facebook, Yelp, Instagram, TikTok
All of these are essentially interfaces to databases. We don't know what the database looks like, but we can see its effects.
What's the Difference between a Database and a Website?¶
Interesting question! They are similar in that they
- are sources of information
- we look stuff up in them
- we can store/post/upload stuff into them
But lots of websites are static or at least not updated by the user:
- Online newspapers (NY Times, etc)
- Government offices
- College website
- our personal websites (Assignment 2)
- games and such (Assignments 3, 4, 5)
- etc.
Theses are all "Web 1.0" while "Web 2.0" allows user-contributed data. This data is put into a database and rendered to (other) users when they search the database.
In fact, that's what this course is about: building web interfaces to databases
We haven't gotten there yet, but we're getting closer.
- H7 Lookup allows the user to search a MongoDB database, namely WMDB
- H8 CRUD allows the user to insert, update and delete from a copy of the WMDB.
- H9 Ajax implements Ajax interactions to a copy of the WMDB
Database Representation¶
Aren't databases always like, rows and columns? Why can't I read MongoDB like that?
- If you Google for databases, you'll probably find this. Those are relational databases.
- MongoDB is a non-relational database, which means that it's not organized around rows and columns.
- Instead, each collection is a set of documents
- Each document is roughly equivalent to a row, and each property is like a column, but a document can have lists and subdocuments and such, which is more flexible than the rigid row/column format.
So, you can think about MongoDB collections as rows, but I don't know that it's helpful.
The best way to think about the collection is the results we get from using Mongosh and a "find-all":
>>> use wmdb;
>>> db.movies.find().pretty()
The result is a list of documents.
Express Apps¶
Express Apps might do anything; we will use them as a user-friendly web-browser interface to the MongoDB database.
- the web browser is the first tier
- MongoDB is the third tier
- Express is the middle tier: the intermediary between the user and the database
Express Handlers¶
Last time, we imagined handlers like this:
// This is our simple Hello World example
app.get('/', (req, res) => {
return res.send('Hello World!');
});
Now, with EJS, our handlers will be like this:
var visitCounter = 0;
// This is our simple Hello World example, this time, rendering a template
app.get('/', (req, res) => {
let now = new Date();
visitCounter ++;
return res.render('index.ejs',
{visits: visitCounter,
time: now.toLocaleTimeString()});
});
That render
hides a bunch of HTML in the .ejs
file, combining it
with the dynamic data that is its second argument.
EJS Substitutions¶
We can substitute values into the EJS file using a special tag-like syntax:
<p>This visit is at <%= time %>.</p>
<p>There have been <%= visits %> visits since the app started.</p>
After the rendering, before the HTML goes to the browser, the EJS has turned into normal HTML:
<p>This visit is at 4:18pm.</p>
<p>There have been 17 visits since the app started.</p>
Templating in General¶
You've almost certainly used F-strings in Python. That's templating:
name='Hermione'
grade='Outstanding'
msg=f'Student {name} got a {grade}'
print(msg)
There are also templates in JavaScript, which many of you know about already:
let name='Hermione'
let grade='Outstanding'
let msg=`Student ${name} got a ${grade} on their OWL`
console.log(msg)
These are great, but they don't have conditionals or loops or other cool things that a templating language like EJS has.
Compared to Browser-Side Dynamic Values¶
This is similar in effect to stuff we did earlier in the course:
<p>This visit is at <span id="the_time"></span>.</p>
<p>There have been <span id="the_visits"></span> visits since the app started.</p>
with JS like this:
$("#the_time").text("4:18pm");
$("#the_visits").text("17");
except that this happens in the browser, rather than on the server.
There are some advantages to doing the rendering server-side:
- client might not support JS, such as a web scraper or screen reader
- web crawlers don't always support JS, so better for SEO
- more efficient for under-powered clients
On the other hand, there can be advantages to doing it browser-side:
- dynamic update of the page
- less data being sent
EJS Conditionals¶
Essentially, we are switching languages (from HTML to JS) within the
EJS file, by using the special <% ... %>
tag. The example from the
reading:
<% if( userId === null ) { %>
<%# login form, because user not logged in %>
<form action="/login" method="post">
<label>username <input type="text" name="username"></label>
<label>password <input type="password" name="password"></label>
<input type="submit" value="login">
</form>
<% } else { %>
<%# logout button %>
<form action="/logout" method="post">
<button>logout <%= userId %></button>
</form>
<% } %>
EJS Lists¶
We can also iterate over a list, either using a loop or .forEach
:
<h1>List of <%= listDescription %></h1>
<ul>
<% list.forEach( (elt) => {%>
<li><%= elt %></li>
<% }) %>
</ul>
The render just provides a list:
const ourHeroes = ['Harry', 'Ron', 'Hermione', 'Fred', 'George'];
app.get('/heroes', (req, res) => {
return res.render('list.ejs',
{listDescription: 'Some Heroes of the Harry Potter Saga',
items: ourHeroes});
});
Note that there's nothing special about the word list
. It could just
as easily be a different word:
<h1>List of <%= listDescription %></h1>
<ul>
<% items.forEach( (item) => {%>
<li><%= item %></li>
<% }) %>
</ul>
The render just provides the items
. That's the dynamic
data. (Well, here it's not dynamic, but it could be.)
const ourHeroes = ['Harry', 'Ron', 'Hermione', 'Fred', 'George'];
app.get('/heroes', (req, res) => {
return res.render('list.ejs',
{listDescription: 'Some Heroes of the Harry Potter Saga',
list: ourHeroes});
});
Partials¶
You can save some EJS in a file and "load" that file into the main template:
<%- include("partials/navbar.ejs") %>
And the partials/navbar.ejs
file then contains just the stuff we want to insert:
<nav>
<ul>
<li><a href="/home">Home</a></li>
<li><a href="/about">About Us</a></li>
<li><a href="/contact">Contact information</a></li>
</ul>
</nav>
App Structure¶
We'll set up our apps like this:
app/
connection.js -> ../connection.js
cs304.js -> ../cs304.js
node_modules -> ../node_modules
public/
styles.css
server.js
views/
index.ejs
partials/
navbar.ejs
You can copy that, recursively with symlinks, from
~cs304node/apps/starter/
or other examples. I'll usually guide you
to one.
GET vs POST¶
- most obvious in
<form method="GET/POST">
but other places as well - different in format and usage
Let's demo the differences:
GET vs POST Facts¶
Let's review these facts:
- GET requests can be cached
- GET requests remain in the browser history
- GET requests can be bookmarked
- GET requests should never be used when dealing with sensitive data
- GET requests have length restrictions
- GET requests are only used to request data (not modify)
and in contrast:
- POST requests are never cached
- POST requests do not remain in the browser history
- POST requests cannot be bookmarked
- POST requests have no restrictions on data length
Quiz Questions¶
How did people feel about EJS?
- 0
- 0
- 5
- 10
- 0
Let's look at your quiz questions on EJS
Examples¶
For these examples, we're going to first copy the EJS example app from the course account, and then you can run and modify your copy.
cd ~/cs304/apps
cp -rd ~cs304node/apps/ejs ejs
cd ejs
ls -l
Notice the files there. In fact, let's list the subfolders, too
ls
ls public
ls views
ls views/partials
Yikes, that's a lot of files!
Running an App¶
To run an app, we use Node.js:
node server.js
SSH Tunnel¶
You'll probably need to set up an SSH tunnel. Adapt the following and run it on your laptop, not on the server:
ssh -L 8080:localhost:PORT cs304guest@cs.wellesley.edu
Note that you need to replace the PORT with the four-digit port number that your app runs on.
Run the App¶
From now on in this course, our server-side main file will (almost)
always be called server.js
. That's not required, but it's convenient
to know where to start.
Thanks to the SSH tunnel, you can access your app on localhost:
What Routes are Available?¶
When running an app, I often want to know what routes are available. Here's a nice incantation:
grep "^app" server.js
That uses regular expressions to find all lines that begin with
the word app
. Very helpful!
grep — globally search for regular expression and print is super important by itself and as a milestone in the history and philosophy of Unix.
Learning more:
Running the App¶
Exit by typing ^C (control-C: Hold down the control
key and press c
.)
Now, let's run the EJS example:
node server.js
Click on the URL. Try some of these:
- localhost:8080/
- localhost:8080/home
- localhost:8080/heroes
- localhost:8080/heroes2
- localhost:8080/main
- localhost:8080/contact
Exercise 0: Explore¶
Do the following:
- Dig into the code for each, starting in
server.js
to see what endpoint handles it, then - Look at the EJS file that is rendered.
- Figure out where the CSS is coming from
Exercise 1: Displaying a Dictionary¶
We're going to work up to providing a pre-filled form. First, let's just display some data. Feel free to modify the data
var formData = {
customer: "Hermione",
phone: "3249",
email: "hgranger@hogwarts.ac.uk",
size: "small",
crust: "thin",
due: "20:00",
instructions: "deliver by owl"
};
To do:
- Create an EJS file to render this data into
- Add a route to render the file with the data above
- Test it
Exercise 2: Pre-filling a form¶
Now, we're going to create a form and use the data above to pre-fill it
To do:
- Modify your EJS file to create a form like our pizza form
- Don't use a
<select>
menu for the size; just use a text input. - Use EJS to insert the dynamic data as the
value
for each form input. see below - Render the form
- Test that it works.
You can specify the value of an input using the value
attribute:
<input type="text" name="college" value="Wellesley">
which looks like:
Exercise 3: Send the Data to your Express App¶
Express will parse the query string and put the data in a dictionary
called query
. So you can get the data from req.query
.
For now, just print the submitted data:
app.get('/order', (req, res) => {
const queryData = req.query;
console.log(queryData);
});
- Add that route to your app.
- Modify the form to submit the data to the
/order
route - Test this.
- Modify the route to
console.log
the email, size and instructions - Modify to re-render the form with the updated info
Extra Credit: Pre-select from a SELECT Menu¶
By default, the first option on a <select>
menu is selected, but you
can select other options by adding a selected
attribute to the menu,
like this:
<select name="grad_year">
<option>choose one</option>
<option>2020</option>
<option>2021</option>
<option>2022</option>
<option selected>2023</option>
<option>2024</option>
<option>2025</option>
<option>2026</option>
</select>
Which looks like:
For extra credit bragging rights, convert the pizza size back to a
<select>
menu and pre-select the correct size.
Discussion¶
Here's my solution:
var formData = {
customer: "Hermione",
phone: "3249",
addr: "hgranger@hogwarts.ac.uk",
size: "small",
due: "20:00",
instructions: "deliver by owl"
};
app.get('/form1', (req, res) => {
return res.render('pizza.ejs', {data: formData});
})
var orderCounter = 0;
app.get('/order', (req, res) => {
let data = req.query;
orderCounter ++;
console.log(data.addr, data.size, data.instructions);
return res.render('pizza.ejs',
{counter: orderCounter,
data: data});
})
and form:
<h2>Order Counter: <%= counter %></h2>
<form class="example"
id="pizza-form"
method="GET"
action="/order">
<p><label>Customer name: <input name="customer" value="<%= data.customer %>"></label></p>
<p><label>Telephone: <input type=tel name="phone" value="<%= data.phone %>"></label></p>
<p><label>E-mail address: <input type=email name="addr" value="<%= data.addr %>"></label></p>
<p><label>Size:
<select name="size">
<option value="">choose size</option>
<option value="small" <%= data.size=="small" ? "selected" : "" %> >small (10-inch)</option>
<option value="medium" <%= data.size=="medium" ? "selected" : "" %> >medium (12-inch)</option>
<option value="large" <%= data.size=="large" ? "selected" : "" %> >large (14-inch)</option>
</select>
</label></p>
<p><label>Preferred delivery time:
<input name="due" type=time min="11:00" max="21:00" step="900" value="<%= data.due %>">
</label></p>
<p><label>Delivery instructions:
<textarea rows="3" cols="30" name="instructions"><%= data.instructions %></textarea>
</label></p>
<p><button type="submit">Submit order</button></p>
</form>
- What are your questions?
- What mistake did we make above?
We used GET for a pizza order. We should use POST instead.