TIL/2021–01–26&27

Day 46 and 47: Treehouse Full Stack JavaScript Techdegree

Express Basics Continuation

Modular Routes

Learning how to modularize our app will keep our code well organized and easy to maintain over time — we’ll see routes in a separate file in production express apps

First: create a folder that holds my routes file

Second: Inside the folder, create an index.js file (remember when requiring a file in node.js, we can just refer to the folder name and if there is a file names index.js it will be loaded by default)

  • Relying on this convention will make our code simpler and easier to read

Third: require the express app

Fourth: create a router constructor for the express app

A router is kind of like a mini express app — we can add middleware and apps to it

Fifth: cut all the routes in the app.js file and copy them to the index.js file

Six: declare all of the apps with the router instead of the app (ex: router.get)

Seven: export the router so we can exploit it in our app.js file (module.exports = router)

Eight: to let app.js use the router, we have to import it (const routes = require(‘./routes’))

We can now use the routes variable to make middleware now

Nine: app.use(routes) — we are getting all the routes from the routes/index.html and adding it to the app.js

Index.js

const express = require(“express”);

const router = express.Router();

router.get(“/”, (req, res) => {

const name = req.cookies.username;

if (name) {

res.render(“index”, { name });

} else {

res.redirect(“/hello”);

}

});

router.get(“/cards”, (req, res) => {

res.render(“card”, {

prompt: “Who’s buried in grant’s tomb?”,

});

});

router.get(“/hello”, (req, res) => {

const cookies = req.cookies.username;

if (!cookies) {

res.render(“hello”); //reads the cookies saved on the browser and returns it to the template

} else {

res.redirect(“/”);

}

});

router.post(“/hello”, (req, res) => {

res.cookie(“username”, req.body.username); //Will send a cookie to the browser after we submit the form

res.redirect(“/”); //redirects the user to the root / welcome page

});

router.post(“/goodbye”, (req, res) => {

res.clearCookie(“username”); //clears the “username” cookie

res.redirect(“/hello”); //redirects back to the /hello route

});

module.exports = router;

App.js

const express = require(“express”);

const bodyParser = require(“body-parser”); // returns “body” object to the browser — to access req.body.__

const cookieParser = require(“cookie-parser”); // returns an “cookies” object to the browser — to access req.cookies.___

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));

app.use(cookieParser());

app.set(“view engine”, “pug”);

app.use((req, res, next) => {

req.message = “This message came from the first middleware”;

next();

});

app.use((req, res, next) => {

console.log(req.message);

next();

});

const routes = require(“./routes”);

app.use(routes);

app.use((req, res, next) => {

const err = new Error(“Page not found…”);

err.status = 404;

next(err);

});

app.use((err, req, res, next) => {

res.locals.error = err;

res.status(err.status);

res.render(“error”);

});

app.listen(3000, () => {

console.log(“The application is running on localhost:3000”);

});

We can have multiple routes — let’s differentiate our routes by adding a main routes and card routes

Since we are directing traffic through the /cards route, everything in out index

On our app.js

const mainRoutes = require(“./routes”);

const cardRoutes = require(“./routes/cards”);

app.use(mainRoutes);

app.use(“/cards”, cardRoutes);

Then on our cards.js

const express = require(“express”);

const router = express.Router();

router.get(“/”, (req, res) => {

res.render(“card”, {

prompt: “Who’s buried in grant’s tomb?”,

});

});

module.exports = router;

It is only showing the root / because we are directing the traffic through using /cards in our app.js and calling cardRoutes as a second parameter — so every route in the cards.js would start with cards

— —

Using data and route parameters

Using and maintaining data is normally handled by connecting the app to the database

In our app, the database would store the text for our prompts, answers and hints for each flash card

We can add our flash card data in what is called a flat data file

We would put the data that the database would store into that file and load it

Create a flat data file

  1. Create a data folder in the root of our directory
  2. Create a new file called flashCardData.json
  3. Paste in the flashcard data

{

“data”: {

“title”: “JavaScript Flashcards”,

“cards”: [

{

“question”: “What language are Express apps written in?”,

“hint”: “It starts with a \”J\””,

“answer”: “JavaScript”

},

{

“question”: “What is one way a website can store data in a user’s browser?”,

“hint”: “They are delicious with milk”,

“answer”: “Cookies”

},

{

“question”: “What is a common way to shorten the response object’s name inside middleware?”,

“hint”: “It has the same abbreviation as \”resolution\””,

“answer”: “res”

},

{

“question”: “How many different values can booleans have?”,

“hint”: “Think: binary”,

“answer”: “2”

},

{

“question”: “Which HTML element can contain JavaScript?”,

“hint”: “It starts with an \”s\””,

“answer”: “<script>”

}

]

}

}

  1. Require the json data in the cards routes file
  2. Store the json data required into a data object { data }
  • const { data } = require(‘../data/flashCardData.json) is equivalent to const data = require(‘../data/flashCardData.json’)
  1. Store the cards data in cards like this { cards }
  • const { cards } = data is equivalent to saying const cards = data.cards
  • This is one example of the new ES6 syntax
  • Doing { cards } is just a shorthand because since we are accessing the cards property on the json object, we are naming the variable as cards and basically saying data.cards. If we change the name to { potato } it wont work because there is no potato property on the data json. Only cards, so it is basically a shorthand of saying data.cards by doing const { cards } = data;

const { data } = require(“../data/flashCardData.json”);

const { cards } = data;

If we console.log(data) we will see the array of objects from flashCardData.json

Note: we can also include JSON directly in node — it reads the text file and parses the text and converts it to a JSON object so we don’t have to call JSON.parse()

  1. Connect the template to the first question to make sure it is loading correctly — let’s connect our prompt with the first question to make sure it is working
  • And it works!
  • router.get(“/”, (req, res) => {
  • res.render(“card”, {
  • prompt: cards[0].question,
  • });
  • });

And the hints

router.get(“/”, (req, res) => {

res.render(“card”, {

prompt: cards[0].question,

hint: cards[0].hint,

});

});

Now how do we pull up any card that we want on the browser?

We can use a route parameter

A route parameter is a variable that a user can put right into the URL to point to a particular resource — for the current URL we want to place a number at the end, indicating which card to display

For example:

First one: localhost:3000/cards/0

Second: localhost:3000/cards/1

And so on…

Now let’s set up a parameter route to set up like the example above

In express we can declare the route parameter where we declared the URL

We can use a colon : to tell express to treat this part of the URL as a variable

If we put /:id, express will treat this portion of the URL as a variable or a route parameter named id.

The value for the route parameter will from the URL will be stored in the request object’s params property

To access params property of the request object simply put req.params.routeParameter in this case is req.params.id

Let’s use the id parameter to access the elements in the cards array

router.get(“/:id”, (req, res) => {

res.render(“card”, {

prompt: cards[req.params.id].question,

hint: cards[req.params.id].hint,

});

});

And now it works!

— — —

Card template

We can see different questions now by specifying an id in the URL. But how can we view answers to those questions?

One of the ways is to use query strings

A query string goes at the end of a URL and starts with a question mark

After the question mark we can put a key value pair separated by an equal sign

/cards/4?key=value

And we can separate additional key value pairs by an &

/cards/4?key=value&key=value

Let’s use a query string to request either the question or the answer of the flash card

We can use /cards/4?side=question or /cards/4?side=answer

Checking for a query string is similar to checking for a route parameter or a cookie. If a query string is in the request. It will be stored in the request object with the query property

req.query.side

Since question and answer are properties on the json

First we need the id from the card, first lets create a variable the holds the id from the route parameter

router.get(“/:id”, (req, res) => {

const { side } = req.query;

const { id } = req.params;

res.render(“card”, {

prompt: cards[req.params.id].question,

hint: cards[req.params.id].hint,

});

});

Now we can access the values our json object

const express = require(“express”);

const router = express.Router();

const { data } = require(“../data/flashCardData.json”);

const { cards } = data;

router.get(“/:id”, (req, res) => {

const { side } = req.query;

const { id } = req.params;

const text = cards[id][side];

const { hint } = cards[id];

const templateData = { text, hint };

res.render(“card”, templateData);

});

module.exports = router;

On our .pug file

extends layout.pug

block content

section#content

h2= text

if hint

p

i Hint: #{hint}

Now we want to hint to only show up only when the question side is showing

router.get(“/:id”, (req, res) => {

const { side } = req.query;

const { id } = req.params;

const text = cards[id][side];

const { hint } = cards[id];

const templateData = { text, hint, side };

res.render(“card”, templateData);

});

Since the side query string would either show question or answer, we could do an if statement on the card.pug. But before that we needed to find a way to access the side variable on the pug. To do that I had to add side on the templateData object to be able to allows the .pug file to access it.

In the .pug file:

extends layout.pug

block content

section#content

h2= text

if side === “question”

p

i Hint: #{hint}

Which is basically saying to only show hint if the side is === question

And it works! Answer side: no hint

Question side: with hint

— — -

Adding links

Note: you can use JavaScript template literals on pug templates

Template literals become more useful in attributes

router.get(“/:id”, (req, res) => {

const { side } = req.query;

let oppositeSide = “”;

if (side === “question”) {

oppositeSide = “answer”;

} else if (side === “answer”) {

oppositeSide = “question”;

}

const { id } = req.params;

const text = cards[id][side];

const { hint } = cards[id];

const templateData = { text, hint, id, side, oppositeSide };

res.render(“card”, templateData);

});

extends layout.pug

block content

section#content

h2= text

if side === “question”

p

i Hint: #{hint}

a(href=`http://localhost:3000/cards/${}?side=${oppositeSide}`) #{oppositeSide}

— —

How do you get query string information from the url?

req.query

What does a query string contain? Key value pairs

What way besides a route parameter can information be passed to the server? Query string

— — -

Randomize Cards

Randomize the card, whenever we go to the /cards it should always redirect to a random card

router.get(“/”, (req, res) => {

const id = Math.floor(Math.random() * cards.length + 1) — 1;

console.log(id);

res.redirect(`/cards/${id}?side=question`);

});

Had to test the id to make sure that random id would be from 0 to 4 only and nothing less or more because of the object

— —

Linking around the application

Mini Challenges: Go to a specific card in the url — it will be blank — there is no query string auto generated.

Add a route to redirect the users and make the users see the question side by default

I checked the presence of a side:

router.get(“/:id”, (req, res) => {

const { side } = req.query;

let oppositeSide = “”;

if (side === “question”) {

oppositeSide = “answer”;

} else if (side === “answer”) {

oppositeSide = “question”;

}

const { id } = req.params;

const text = cards[id][side];

const { hint } = cards[id];

const templateData = { text, hint, id, side, oppositeSide };

if (!side) {

res.redirect(`/cards/${id}?side=question`);

} else {

res.render(“card”, templateData);

}

});

Add a link to the flashcard that allows the user to move to another randomly selected card

router.get(“/:id”, (req, res) => {

const { side } = req.query;

let oppositeSide = “”;

if (side === “question”) {

oppositeSide = “answer”;

} else if (side === “answer”) {

oppositeSide = “question”;

}

const random = Math.floor(Math.random() * cards.length — 1) + 1;

const { id } = req.params;

const text = cards[id][side];

const { hint } = cards[id];

const templateData = { text, hint, id, side, oppositeSide, random };

if (!side) {

res.redirect(`/cards/${id}?side=question`);

} else {

res.render(“card”, templateData);

}

});

extends layout.pug

block content

section#content

h2= text

if side === “question”

p

i Hint: #{hint}

a(href=`http://localhost:3000/cards/${id}?side=${oppositeSide}`) #{oppositeSide}

div

a(href=`http://localhost:3000/cards/${random}?side=question`) Random

Or even simpler:

Just add another href which goes to the /cards default route which actually generates a random card

extends layout.pug

block content

section#content

h2= text

if side === “question”

p

i Hint: #{hint}

a(href=`http://localhost:3000/cards/${id}?side=${oppositeSide}`) #{oppositeSide}

div

a(href=”/cards”) Next Card

router.get(“/”, (req, res) => {

const id = Math.floor(Math.random() * cards.length + 1) — 1;

res.redirect(`/cards/${id}`);

});

— — — — -

What happens when an object is passed into the ‘next’ function? Execution is passed to the next error handling middleware function

Where are cookies stored? On the client

What are two ways of putting data in the URL to be sent to the server? Route parameter and query strings

How do you get cookies from a client’s request? Req.cookies

— — — -

What are static assets?

Static files like images and style sheets don’t need to be processed by the application. They just need to be delivered to the browser

When the browser makes a get request to the index route of our application, it receives HTML. But there is no file called index.html in our application.

When the app receives the request it has to assemble and render the HTML from templates and data. This allows the application to serve customized web pages to different clients.

The css and client-side JS files will always be the same no matter who requests them.

Our express app and browser app play different roles in what the end-user sees — the static JS files don’t look like JavaScript to express. The files are handed to the browser without being interpreted by express. The browser knows what to do with these files and turns them into user interactions.

To send images in plain CSS and JavaScript files from the server’s file system to the client, express uses a Static Server. Because these files are prebuilt and won’t change on the server, passing them through all of Express’s routing and template rendering would be a waste of time

The static server just sends them straight to the browser, saving express time and effort.

Express doesn’t need to build the static assets — CSS and client-side JavaScript

— —

Adding static assets to the app

To serve static assets, express has a specific method to serve static assets

express.static(root, [options])

The name public is commonly used for the folder containing static assets

On app.js there is only one line of code that we need to add — and that is the static middleware

app.use(express.static(‘folderName’))

app.use(express.static(“public”));

Now let’s see if we can find the CSS file in our browser

And now we can see the css files

To make it more specific we can do

app.use(“/static”,express.static(“public”));

Next, we need to link the HTML the app produces to this file

Since we want all the styles available on all the pages in our application, the best place to link the style sheets is in the layout.pug

Remember, layout.pug contains the head element which is extended throughout the application.

Remember, the route to our stylesheet is from what we previously made in our app.js

/static/stylesheets/style.css

doctype html

html(lang=”en”)

head

title Flash Cards

link(rel=’stylesheet’, type=’text/css’, href=’static/stylesheets/style.css’)

body

include includes/header.pug

if name

h2 Welcome, #{name}!

form(method=”post”, action=”/goodbye”)

button(type=”submit”) Goodbye

block content

include includes/footer.pug

When express renders this template, it sends HTML to the browser, the browser will see the link tag and make a request to the static CSS file

Because we set up the static server, the public folder’s contents are at /static

And it works but we are not yet done!

We need to sync the HTML from our designer

http://html2jade.org/

http://html2jade.aaron-powell.com/

HTML to pug

— -

What overall problem do cookies attempt to solve? How to store state. Cookies are a way to save state while working in a stateless protocol

What problem does templating attempt to solve? How to write and reuse HTML

What overall problem do redirects attempt to solve? How to move a client to a different URL

What problem do query strings attempt to solve? How to pass information to the server

Where are static assets executed? The client executes them.

Express cannot read and execute client-side JavaScript

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store