Beginning with the End in Mind
- Describe REST and list the various routes
- Create an Index route
- Install JSONView to make viewing JSON easier
- Create a Show route
- Enhance the data in your data array
Explanation
Explanation
Remember where we are going
What is CRUD?
CRUD stands for Create, Read, Update and Delete. Why and how is this important you ask? Well CRUD is in everything you interact with online on a daily basis. Browsing Instagram and looking at posts? Thats Read. Are you posting something? Well that's Create. Didn't like that picture you posted? Delete to the rescue and finally, liking pictures of adorable animals? Hello Update!
So now that we have a reference point, how does that relate to code?
Below you'll find a table that relates normal HTTP actions to our CRUD operators.
| HTTP Action | CRUD Operator |
|---|---|
| GET | Read |
| POST | Create |
| PUT | Update |
| DELETE | Delete |
Breaking It Down
Create
A Create or POST creates a new entry in a database that either you or someone else created. When we perform a POST request, we send some amount of information to our data source, usually data from a form; and we create a new entry in the data source.
Read
The Read or GET operation is something that you interact with every day. Whenever you pull information from an api, you are performing a GET request. GET requests only allow users to view or read from a data source. This can be something as simple as you checking your email in the morning. We perform a GET request to a server and if everything goes OK we get some type of information or data back.
Update
Update or PUT works very similar to a POST request. We are still sending along some sort of data to our data source, but instead of creating a new entry, we are actually updating an existing entry. In order to this, we need to target that entry specifically, most of the times using an id. This allows us to send some new information to to our existing piece of data and overwrite it's existing attributes.
Delete
Delete does just as the name implies, it DELETE's something from our data source. We target the specific piece of information in the same way we did with our PUT request. This sends a request to the server to DELETE this item.
Demonstration
Describe REST and list the various routes
- REST stands for Representational state transfer
- It's just a set of principles that describe how networked resources are accessed and manipulated
- We have 7 RESTful routes that allow us basic operations for reading and manipulating a collection of data:
| URL | HTTP Verb | Action | Used For |
|---|---|---|---|
| /fruits/ | GET | index | Displaying a list of all fruits |
| /fruits/new | GET | new | Display HTML form for creating a new fruit |
| /fruits | POST | create | Create a new fruit |
| /fruits/:id | GET | show | Display a specific fruit |
| /fruits/:id/edit | GET | edit | Return an HTML form for editing a fruit |
| /fruits/:id | PATCH/PUT | update | Update a specific fruit |
| /fruits/:id | DELETE | destroy | Delete a specific photo |
What is Index and Show
Imitation
Create an Index route
Let's have a set of resources which is just a javascript array. To create an index route, we'd do the following:
const express = require('express');
const app = express();
const fs = require('fs') // this engine requires the fs module like we did Saturday
app.engine('evelyn', (filePath, options, callback) => { // define the view engine called hypatia
fs.readFile(filePath, (err, content) => {
if (err) return callback(err)
// this is an extremely simple view engine we'll be more complex later
const rendered = content.toString()
.replace('#title#', `<title>${options.title}</title>`)
.replace('#message#', `<h1>${options.message}</h1>`).replace('#content#',`<div>${Array.isArray(options.content)? options.content.map(item => `<li>${item}</li>`) : options.content }</div>` )
return callback(null, rendered)
})
})
app.set('views', './views') // specify the views directory
app.set('view engine', 'evelyn') // register the evelyn view engine
const fruits = ['apple', 'banana', 'pear'];
app.get('/fruits/', (req, res) => {
res.send(fruits);
});
app.listen(3000, () => {
console.log('listening');
});Now go to http://localhost:3000/fruits/
Create a Show route
To create a show route, we'd do this:
const express = require('express');
const app = express();
const fs = require('fs') // this engine requires the fs module like we did Saturday
app.engine('evelyn', (filePath, options, callback) => { // define the view engine called hypatia
fs.readFile(filePath, (err, content) => {
if (err) return callback(err)
// this is an extremely simple view engine we'll be more complex later
const rendered = content.toString()
.replace('#title#', `<title>${options.title}</title>`)
.replace('#message#', `<h1>${options.message}</h1>`).replace('#content#',`<div>${Array.isArray(options.content)? options.content.map(item => `<li>${item}</li>`) : options.content }</div>` )
return callback(null, rendered)
})
})
app.set('views', './views') // specify the views directory
app.set('view engine', 'evelyn') // register the evelyn view engine
const fruits = ['apple', 'banana', 'pear'];
app.get('/fruits/', (req, res) => {
res.send(fruits);
});
//add show route
app.get('/fruits/:indexOfFruitsArray', (req, res) => {
res.send(fruits[req.params.indexOfFruitsArray]);
});
app.listen(3000,() => {
console.log('listening');
});Now go to http://localhost:3000/fruits/1
Enhance the data in your data array
- Right now are data array
fruitsis just an array of strings - We can store anything in the array, though.
- Let's enhance our data a bit:
const express = require('express');
const app = express();
const fs = require('fs') // this engine requires the fs module like we did Saturday
app.engine('evelyn', (filePath, options, callback) => { // define the view engine called hypatia
fs.readFile(filePath, (err, content) => {
if (err) return callback(err)
// this is an extremely simple view engine we'll be more complex later
const rendered = content.toString()
.replace('#title#', `<title>${options.title}</title>`)
.replace('#message#', `<h1>${options.message}</h1>`).replace('#content#',`<div>${Array.isArray(options.content)? options.content.map(item => `<li>${item}</li>`) : options.content }</div>` )
return callback(null, rendered)
})
})
app.set('views', './views') // specify the views directory
app.set('view engine', 'evelyn') // register the evelyn view engine
const fruits = [
{
name:'apple',
color: 'red',
readyToEat: true
},
{
name:'pear',
color: 'green',
readyToEat: false
},
{
name:'banana',
color: 'yellow',
readyToEat: true
}
];
app.get('/fruits/', (req, res) => {
res.send(fruits);
});
app.get('/fruits/:indexOfFruitsArray', (req, res) => {
res.send(fruits[req.params.indexOfFruitsArray]);
});
app.listen(3000, () => {
console.log('listening');
});Start up your server in terminal
Now visit http://localhost:3000/0 in your browser
Apple
Now visit http://localhost:3000/2 in your browser
Banana
Note: http://localhost:3000
error cannot GET (we didn't write a route for this)
Let's breakdown the contents of our localhost URL:
http://localhost:3000/2
\___/ \_______/ \__/ \_/
protocol host port path* Path can be a URL or a URL parameter: it will look the same in the browser. The difference will be in the server.
A Common Error
You can only have one response for every request. If you try to send multiple responses you'll get an error. Let's try it!
app.get('/oops/:indexOfFruitsArray', (req, res) => {
res.send(fruits[req.params.indexofFruitsArray]);
// error cannot send more than one response!
res.send('this is the index: ' + req.params.indexofFruitsArray);
});Read URL parameters
Most of the time, we'll use segments in the path section of the URL to modify how our application works.
To do this, we'll use request parameters. To the user, it'll just look like an extension of the url path.
Let's think of Amazon. With 300 million products and counting, hard coding a route for each product and keeping track of it all would be nightmarish.
We'll work with a simplified example. Imagine a store: The Healthy Express that sells a few fruits. Rather than having a dedicated route for each fruit, the fruits are stored as data (in our case an array of fruits). We can access the data by passing in the index as a part of the request URL.
To set URL parameters in your server.js , just add a colon after the forward slash and then a variable name.
'Regular' URL:
/fruits
URL parameter:
/:indexOfFruitsArray
The entire route:
app.get('/:indexOfFruitsArray', (req, res) => {
res.send(fruits[req.params.indexOfFruitsArray]);
});We can access the value of :indexOfFruitsArray with req.params.indexOfFruitsArray
We can, however, have multiple statements if we use our if statements or other program logic correctly:
app.get('/fixed/:indexofFruitsArray', (req, res) => {
if (fruits[req.params.index]) {
res.send(fruits[req.params.indexofFruitsArray]);
} else {
res.send('cannot find anything at this index: ' + req.params.indexofFruitsArray);
}
});Place routes in correct order
- Express starts at the top of your
server.jsfile and attempts to match the url being used by the browser with routes in the order in which they're defined -
URL params (e.g. :foo, :example, :indexofFruitsArray) can be anything, a number, or even a string
- Therefore if you have these routes in this order in your
server.js: /:color/fruits- And you want to get to
/fruits- you'll always hit the/:colorroute because the URL parameter will accept any string, it doesn't know thatfruitsis something specific/special - To fix this, we put the more specific routes first
/fruits/:colorNow, from top to bottom, the more specific route/fruitswill be triggered when the URL hasfruitsand if it doesn't matchfruits, it will go to the next route.
- Therefore if you have these routes in this order in your
Explanation
Remember where we are going
!
Define MVC and explain why it matters
- One of the core tenants of good programming is to compartmentalize your code
-
Already our code is getting a little messy
- we have data, app instantiation (listening), and routes all in one file
-
One way to keep an app from getting messy is to separate it out into three sections
-
Models
- data (javascript variables)
-
Views
- how the data is displayed to the user (HTML)
-
Controllers
- the glue that connects the models with the views
-
-
This allows various developers to divide up a large code base
- minimizes likelihood of developers overwriting each others code
-
allows developers to specialize
- one can focus just on getting good with dealing with data
- one can focus just on getting good with html
- one can focus just on getting good with connecting the two
-
Think of MVC as a restaurant
-
Models are the cook
- prepares food/data
-
Views are the customer
- consumes food/data
-
Controllers are the waiter
- brings food from cook to customer
- has no idea how food/data is prepared
- has no idea how the food/data is consumed
-
All in all
Imitation
Move our data into a separate file
- Create a directory called models inside our app directory
- Inside /models, create your data file (fruits.js)
-
Put your fruits variable in there
const fruits = [ { name:'apple', color: 'red', readyToEat: true }, { name:'pear', color: 'green', readyToEat: false }, { name:'banana', color: 'yellow', readyToEat: true } ]; -
We now require that file in the original server.js
const fruits = require('./models/fruits.js'); //NOTE: it must start with ./ if it's just a file, not an NPM package -
But, we could have multiple variables in our /models/fruits.js file.
- How does javascript know which variable in /models/fruits.js to assign to the fruits const in server.js (the result of the
require()statment)? - We must tell javascript which variable we want to be the result of the
require()statement in server.js
- How does javascript know which variable in /models/fruits.js to assign to the fruits const in server.js (the result of the
//at the bottom of /models/fruits.js
module.exports = fruits;const express = require('express');
const app = express();
const fruits = require('./models/fruits.js')
const fs = require('fs') // this engine requires the fs module like we did Saturday
app.engine('evelyn', (filePath, options, callback) => { // define the view engine called hypatia
fs.readFile(filePath, (err, content) => {
if (err) return callback(err)
// this is an extremely simple view engine we'll be more complex later
const rendered = content.toString()
.replace('#title#', `<title>${options.title}</title>`)
.replace('#message#', `<h1>${options.message}</h1>`).replace('#content#',`<div>${Array.isArray(options.content)? options.content.map(item => `<li>${item}</li>`) : options.content }</div>` )
return callback(null, rendered)
})
})
app.set('views', './views') // specify the views directory
app.set('view engine', 'evelyn') // register the evelyn view engine
app.get('/fruits/', (req, res) => {
res.send(fruits);
});
app.get('/fruits/:indexOfFruitsArray', (req, res) => {
res.send(fruits[req.params.indexOfFruitsArray]);
});
app.listen(3000, () => {
console.log('listening');
});Index Route using Template
- Index Route is for showing all resources in a collection
- Our collection is fruits and each resource is a fruit
- recall our earlier routes using the view engine
app.get('/about-me', (req, res) => {
res.render('template', { title: 'DJ KHALED', message: 'It Breaks My Heart!', content: 'They Ain\'t Believe in Us But God Did' })
})- Remember how are template works
<head>
#title#
</head>
<body>
<header>#message#</header>
#content#
</body>.titlewill go into the head,.messagewill go in the header tag and.contentwill go below the heading- with this understanding lets use the template to display all the fruit
app.get('/fruits', (req, res) => {
res.render('template', { title: 'Fruits Index Page', message: 'Fruits', content: fruits.map(fruit => `${fruit.name} is ${fruit.color} and ${fruit.readyToEat? 'is ready to eat' : 'isn\'t ready to eat'}`) })
})const express = require('express');
const app = express();
const fruits = require('./models/fruits.js')
const fs = require('fs') // this engine requires the fs module like we did Saturday
app.engine('evelyn', (filePath, options, callback) => { // define the view engine called hypatia
fs.readFile(filePath, (err, content) => {
if (err) return callback(err)
// this is an extremely simple view engine we'll be more complex later
const rendered = content.toString()
.replace('#title#', `<title>${options.title}</title>`)
.replace('#message#', `<h1>${options.message}</h1>`).replace('#content#',`<div>${Array.isArray(options.content)? options.content.map(item => `<li>${item}</li>`) : options.content }</div>` )
return callback(null, rendered)
})
})
app.set('views', './views') // specify the views directory
app.set('view engine', 'evelyn') // register the evelyn view engine
app.get('/fruits', (req, res) => {
res.render('template', { title: 'Fruits Index Page', message: 'Fruits', content: fruits.map(fruit => `${fruit.name} is ${fruit.color} and ${fruit.readyToEat? 'is ready to eat' : 'isn\'t ready to eat'}`) })
})
app.get('/fruits/:indexOfFruitsArray', (req, res) => {
res.send(fruits[req.params.indexOfFruitsArray]);
});
app.listen(3000, () => {
console.log('listening');
});Show Route Using Template
- The Show Route should show one individual fruit based on the url paramater
- We can make a route to do that now
app.get('/fruits/:indexOfFruitsArray', (req, res) => {
const fruit = fruits[req.params.indexOfFruitsArray]
res.render('template', {
title: fruit.name ,
message: `The ${fruit.color} ${fruit.name}`,
content:`${fruit.name} is ${fruit.color} and ${fruit.readyToEat? 'is ready to eat' : 'isn\'t ready to eat'}`})
})Make update to view engine add join so we won't see the ',' in the browser
.replace('#content#',`<div>${Array.isArray(options.content)? options.content.map(item => `<li>${item}</li>`).join('') : options.content }</div>` )const express = require('express');
const app = express();
const fruits = require('./models/fruits.js')
const fs = require('fs') // this engine requires the fs module like we did Saturday
app.engine('evelyn', (filePath, options, callback) => { // define the view engine called hypatia
fs.readFile(filePath, (err, content) => {
if (err) return callback(err)
// this is an extremely simple view engine we'll be more complex later
const rendered = content.toString()
.replace('#title#', `<title>${options.title}</title>`)
.replace('#message#', `<h1>${options.message}</h1>`).replace('#content#',`<div>${Array.isArray(options.content)? options.content.map(item => `<li>${item}</li>`).join('') : options.content }</div>` )
return callback(null, rendered)
})
})
app.set('views', './views') // specify the views directory
app.set('view engine', 'evelyn') // register the evelyn view engine
app.get('/fruits', (req, res) => {
res.render('template', {
title: 'Fruits Index Page',
message: 'Fruits',
content: fruits.map(fruit => `${fruit.name} is ${fruit.color} and ${fruit.readyToEat? 'is ready to eat' : 'isn\'t ready to eat'}`) })
})
app.get('/fruits/:indexOfFruitsArray', (req, res) => {
const fruit = fruits[req.params.indexOfFruitsArray]
res.render('template', {
title: fruit.name ,
message: `The ${fruit.color} ${fruit.name}`,
content:`${fruit.name} is ${fruit.color} and ${fruit.readyToEat? 'is ready to eat' : 'isn\'t ready to eat'}`})
})
app.listen(3000, () => {
console.log('listening');
});Notes for your edification
Multiple Params
We can store multiple params in the req.params object:
🔵 Write in (5 min)
app.get('/hello/:firstname/:lastname', (req, res) => {
console.log(req.params);
res.send('hello ' + req.params.firstname + ' ' + req.params.lastname);
});- In your browser, go to
localhost:3000/hello/bob/bobbybob
🔵 Check the req.params console.log in Terminal

- Try entering different firstnames and lastnames in your URL and check the results
req.query
A query is a key-value pair separated with an =, and added to the URL with a ?.
?someKey=someValue
localhost:3000/howdy/bob?title=dukeapp.get('/howdy/:firstName', (req, res) => {
console.log(req.params);
console.log(req.query);
res.send('hello ' + req.query.title + ' ' + req.params.firstName);
});You can add multiple queries
localhost:3000/howdy/bob?title=duke&year=2001Spaces are represented with a %20.