The Complete Node - Js Dev Course: A PDF Reference For
The Complete Node - Js Dev Course: A PDF Reference For
Version 1.0 2
Lesson 5: Customizing HTTP Requests......................................................................................28
Lesson 6: An HTTP Request Challenge .....................................................................................29
Lesson 7: Handling Errors ..............................................................................................................29
Lesson 8: The Callback Function ................................................................................................. 30
Lesson 9: Callback Abstraction ..................................................................................................... 31
Lesson 10: Callback Abstraction Challenge .............................................................................. 33
Lesson 11: Callback Chaining ......................................................................................................... 33
Lesson 12: ES6 Aside: Object Property Shorthand and Destructuring .............................. 34
Lesson 13: Destructuring and Property Shorthand Challenge.............................................. 37
Lesson 14: Bonus: HTTP Requests Without a Library ............................................................. 37
Section 7: Web Servers .......................................................................................................... 38
Lesson 1: Section Intro..................................................................................................................... 38
Lesson 2: Hello Express!................................................................................................................. 38
Lesson 3: Serving up HTML and JSON ......................................................................................40
Lesson 4: Serving up Static Assets............................................................................................... 41
Lesson 5: Serving up CSS, JS, Images, and More ................................................................... 42
Lesson 6: Dynamic Pages with Templating............................................................................... 43
Lesson 7: Customizing the Views Directory .............................................................................. 44
Lesson 8: Advanced Templating.................................................................................................. 45
Lesson 9: 404 Pages ....................................................................................................................... 46
Lesson 10: Styling the Application: Part I.................................................................................... 47
Lesson 11: Styling the Application: Part II.................................................................................... 47
Section 7: Accessing API from Browser .............................................................................. 47
Lesson 1: Section Intro..................................................................................................................... 47
Lesson 2: The Query String ........................................................................................................... 47
Lesson 3: Building a JSON HTTP Endpoint............................................................................... 48
Lesson 4: ES6 Aside: Default Function Parameters ................................................................ 48
Lesson 5: Browser HTTP Requests with Fetch......................................................................... 49
Lesson 6: Creating a Search Form...............................................................................................50
Lesson 7: Wiring up the User Interface........................................................................................51
Section 7: Application Deployment...................................................................................... 52
Lesson 1: Section Intro.....................................................................................................................52
Lesson 2: Joining Heroku and GitHub........................................................................................52
Lesson 3: Version Control with Git............................................................................................... 53
Lesson 4: Exploring Git ................................................................................................................... 53
Lesson 5: Integrating Git................................................................................................................. 53
Lesson 6: Setting up SSH Keys.....................................................................................................55
Lesson 7: Pushing Code to GitHub..............................................................................................56
Lesson 8: Deploying Node.js to Heroku.....................................................................................56
Lesson 9: New Feature Deployment Workflow........................................................................ 57
Lesson 10: Avoiding Global Modules .......................................................................................... 57
Section 10: MongoDB and Promises.................................................................................... 58
Lesson 1: Section Intro.....................................................................................................................58
Lesson 2: MongoDB and NoSQL Databases ............................................................................58
Lesson 3: Installing MongoDB on macOS and Linux ..............................................................58
Lesson 4: Installing MongoDB on Windows ..............................................................................59
Lesson 5: Installing Database GUI Viewer.................................................................................59
Lesson 6: Connecting and Inserting Documents .....................................................................60
Lesson 7: Inserting Documents...................................................................................................... 61
Lesson 8: The ObjectID ..................................................................................................................62
Lesson 9: Querying Documents ...................................................................................................62
Lesson 10: Promises......................................................................................................................... 63
Lesson 11: Updating Documents ................................................................................................... 63
Lesson 12: Deleting Documents ...................................................................................................65
Section 11: REST APIs and Mongoose.................................................................................. 66
Lesson 1: Section Intro.....................................................................................................................66
Lesson 2: Setting up Mongoose...................................................................................................66
Lesson 3: Creating a Mongoose Model...................................................................................... 67
Lesson 4: Data Validation and Sanitization: Part I.................................................................... 68
Lesson 5: Data Validation and Sanitization: Part II ..................................................................69
Lesson 6: Structuring a REST API.................................................................................................69
Lesson 7: Installing Postman .........................................................................................................69
Lesson 8: Resource Creation Endpoints: Part I ........................................................................ 70
Lesson 9: Resource Creation Endpoints: Part II ....................................................................... 70
Lesson 10: Resource Reading Endpoints: Part I ........................................................................ 71
Lesson 11: Resource Reading Endpoints: Part II ........................................................................ 71
Lesson 12: Promise Chaining......................................................................................................... 72
Lesson 13: Promise Chaining Challenge .................................................................................... 73
Lesson 14: Async/Await................................................................................................................... 73
Lesson 15: Async/Await: Part II...................................................................................................... 74
Lesson 16: Integrating Async/Await ............................................................................................. 74
Lesson 17: Resource Updating Endpoints: Part I...................................................................... 75
Lesson 18: Resource Updating Endpoints: Part II..................................................................... 76
Lesson 19: Resource Deleting Endpoints................................................................................... 76
Lesson 20: Separate Route Files ..................................................................................................77
Section 12: API Authentication and Security ...................................................................... 78
Lesson 1: Section Intro..................................................................................................................... 78
Lesson 2: Securely Storing Passwords: Part I........................................................................... 78
Hashing Passwords with Bcrypt ................................................................................................... 78
Lesson 3: Securely Storing Passwords: Part II .......................................................................... 79
Lesson 4: Logging in Users............................................................................................................80
Lesson 5: JSON Web Tokens......................................................................................................... 81
Lesson 6: Generating Authentication Tokens .......................................................................... 82
Lesson 7: Express Middleware...................................................................................................... 83
Lesson 8: Accepting Authentication Tokens............................................................................. 84
Lesson 9: Advanced Postman....................................................................................................... 85
Lesson 10: Logging Out .................................................................................................................. 85
Lesson 11: Hiding Private Data....................................................................................................... 86
Lesson 12: Authenticating User Endpoints ................................................................................ 86
Lesson 13: The User/Task Relationship ...................................................................................... 86
Lesson 14: Authenticating Task Endpoints ................................................................................ 87
Lesson 15: Cascade Delete Tasks................................................................................................ 88
Section 13: Sorting, Pagination, and Filtering .................................................................... 88
Lesson 1: Section Intro..................................................................................................................... 88
Lesson 2: Working with Timestamps........................................................................................... 88
Lesson 3: Filtering Data .................................................................................................................. 89
Lesson 4: Paginating Data..............................................................................................................90
Lesson 5: Sorting Data ....................................................................................................................90
Section 14: File Uploads..........................................................................................................91
Lesson 1: Section Intro...................................................................................................................... 91
Lesson 2: Adding Support for File Uploads...............................................................................92
Lesson 3: Validating File Uploads................................................................................................92
Lesson 4: Validation Challenge .................................................................................................... 93
Lesson 5: Handling Express Errors .............................................................................................. 93
Lesson 6: Adding Images to the User Profile............................................................................ 94
Lesson 7: Serving up Files.............................................................................................................. 94
Lesson 8: Auto-Cropping and Image Formatting.....................................................................95
Section 15: Sending Emails ................................................................................................... 96
Lesson 1: Section Intro.....................................................................................................................96
Lesson 2: Exploring SendGrid.......................................................................................................96
Lesson 3: Sending Welcome and Cancelation Emails............................................................ 97
Lesson 4: Environment Variables ................................................................................................. 97
Lesson 5: Creating a Production MongoDB Database........................................................... 98
Lesson 6: Heroku Deployment ..................................................................................................... 98
Section 16: Testing Node.js ................................................................................................... 99
Lesson 1: Section Intro.....................................................................................................................99
Lesson 2: Jest Testing Framework ..............................................................................................99
Lesson 3: Writing Tests and Assertions.................................................................................... 100
Lesson 4: Writing Your Own Tests.............................................................................................. 101
Lesson 5: Testing Asynchronous Code..................................................................................... 101
Lesson 6: Testing an Express Application: Part I ................................................................... 102
Lesson 7: Testing an Express Application: Part II................................................................... 103
Lesson 8: Jest Setup and Teardown......................................................................................... 104
Lesson 9: Testing with Authentication...................................................................................... 105
Lesson 10: Advanced Assertions................................................................................................ 106
Lesson 11: Mocking Libraries........................................................................................................ 106
Lesson 12: Wrapping up User Tests ...........................................................................................107
Lesson 13: Setup Task Test Suite................................................................................................107
Lesson 14: Testing with Task Data ..............................................................................................107
Lesson 15: Bonus: Extra Test Ideas............................................................................................ 108
Section 17: Real-Time Web Applications with Socket.io ................................................. 108
Lesson 1: Section Intro................................................................................................................... 108
Lesson 2: Creating the Chat App Project................................................................................. 108
Lesson 3: WebSockets.................................................................................................................. 108
Lesson 4: Getting Started with Socket.io ................................................................................. 109
Lesson 5: Socket.io Events .............................................................................................................111
Lesson 6: Socket.io Events Challenge....................................................................................... 112
Lesson 7: Broadcasting Events .................................................................................................... 112
Lesson 8: Sharing Your Location..................................................................................................113
Lesson 9: Event Acknowledgements ..........................................................................................114
Lesson 10: Form and Button States ............................................................................................ 115
Lesson 11: Rendering Messages .................................................................................................. 116
Lesson 12: Rendering Location Messages.................................................................................117
Lesson 13: Working with Time .......................................................................................................117
Lesson 14: Timestamps for Location Messages .......................................................................118
Lesson 15: Styling the Chat App ...................................................................................................118
Lesson 16: Join Page ...................................................................................................................... 119
Lesson 17: Socket.io Rooms.......................................................................................................... 119
Lesson 18: Storing Users: Part I................................................................................................... 120
Lesson 19: Storing Users: Part II.................................................................................................. 120
Lesson 20: Tracking Users Joining and Leaving .................................................................... 121
Lesson 21: Sending Messages to Rooms................................................................................. 122
Lesson 22: Rendering User List...................................................................................................123
Lesson 23: Automatic Scrolling ...................................................................................................124
Lesson 24: Deploying the Chat Application ............................................................................ 125
Section 1: Welcome
This first section contains a brief overview of the class. There are no lecture notes for
this first section as it’s an introduction to the rest of the class. This section is still
important though, so make sure to watch the lecture videos to learn how to get the most
out of the class.
Enjoy!
Below are links to both tools. Take a moment to install them before continuing on with
the class.
Links
• Node.js
• Visual Studio Code
This lesson contains a presentation that covers the major advantages of Node.js. There
are no notes for presentation lectures. Please refer to the video for details.
Creating a Script
Node.js scripts are created with the js file extension. Remember that Node.js is not a
programming language. All the code in this course is JavaScript code, which is why the js
extension is used.
console.log('Hello Node.js!')
Running a Script
You can run a Node.js script using the node command. Open up a new terminal window
and navigate to the directory where the script lives. From the terminal, you can use the
node command to provide the path to the script that should run. You can see an
example of this command in the terminal below.
$ node index.js
Hello Node.js!
When a Node.js script calls console.log, the logged values will show up in the terminal.
This is a great way to get output from your Node.js application
Section 3: Node.js Module System
The module system is built around the require function. This function is used to load in a
module and get access to its contents. require is a global variable provided to all your
Node.js scripts, so you can use it anywhere you like!
const fs = require('fs')
The script above uses require to load in the fs module. This is a built-in Node.js module
that provides functions you can use to manipulate the file system. The script uses
writeFileSync to write a message to notes.txt.
After you run the script, you’ll notice a new notes.txt file in your directory. Open it up and
you’ll see, “I live in Philadelphia!”.
Links
• Node.js documentation
• Node.js fs documentation
checkUtils()
The code above uses require to load in a file called utils.js in the src directory. It
stores the module contents in a variable, and then uses the contents in the script.
You can see utils.js below. A function is defined and then assigned to module.exports.
The value stored on module.exports will be the return value for require when the script
is imported. That means other scripts could load in the utilities to access the check
function.
module.exports = check
If you run the original script, you’ll see the message that logged from the check function in
utils.js.
$ node app.js
Doing some work...
Your Node.js scripts don’t share a global score. This means variables created in one
scripts are not accessible in a different script. The only way to share values
between scripts is by using require with module.exports.
Initializing npm
Your Node.js application needs to initialize npm before npm can be used. You can run
npm init from the root of your project to get that done. That command will ask you a
series of questions about the project and it’ll use the information to generate a
package.json file in the root of your project.
Here’s an example.
{
"name": "notes-app",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
}
The command above installs version 10.8.0 of validator. If you want to install the latest
version of a module, you can leave off the version number as shown below.
First, it creates a node_modules directory. npm uses this directory to store all the code for
the npm modules you have installed.
Second, npm adds the module as a dependency by listing it in the dependencies property
in package.json. This allows you to track and manage the module you have installed.
Third, npm creates a package-lock.json file. This includes detailed information about the
modules you’ve installed which helps keep things fast and secure.
The script above uses require to load in validator. The script then uses the isURL
function
provided by validator to check if a given string contains a valid URL.
Links
• npm
• npm: validator
Lesson 5: Printing in Color
There are npm modules for pretty much anything you’d want to do with Node.js. In
this lesson, it’s up to you to install and use a new one!
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Links
• npm: chalk
A globally installed module is not added as a dependency to your project. That means you
won’t see it listed in package.json or package-lock.json. You also won’t find its code
in node_modules. Globally installed modules are located in a special directory in your
machine which is created and managed by npm.
When you install nodemon globally, you get access a new nodemon command from
the
terminal. This can be used to start and Node.js application and then restart the
application any of the app scripts change. This means you won’t need to switch between
the terminal and text editor to restart your application every time you make a change.
P.S. You can stop nodemon by using ctrl + c from the terminal!
Links
• npm: nodemon
That script grabs the third item in process.argv. Since the first two are always provided,
the third item is the first command line argument that was passed in. The script uses
the value of that argument to figure out what it should do. A user could provide add to
add a note or remove to remove a note.
The command below runs the script and provides add as the command line argument.
Links
• process.argv
Setting Up Yargs
First, install Yargs in your project.
Now, yargs can be used to make it easier to work with command line arguments. The
example below shows how this can be done. First, yargs.version is used to set up a
version for the command line tool. Next, yargs.command is used to add support for a
new command.
const yargs = require('yargs')
yargs.version('1.1.0')
yargs.command({
command: 'add',
describe: 'Add a new note',
handler: function () {
console.log('Adding a new note!')
}
})
console.log(yargs.argv)
Now, this command can be triggered by providing its name as a command line argument.
Yargs provides a couple useful commands by default. The first, shown below, lets a user
get the version of the command line tool they’re running.
The second, shown below, shows the user autogenerated documentation that covers how
the tool can be used. This would list out all available commands as well as the available
options for each command.
Links
• npm: yargs
Now, the add command can be used with two options. The first is title which is used for
the title of the note being added. The second is body which is used for the body of the
note being added. Both options are required because demandOption is set to true.
Both are also set up to accept string input because type is set to 'string'.
yargs.command({
command: 'add',
describe: 'Add a new note',
builder: {
title: {
describe: 'Note title',
demandOption: true,
type: 'string'
},
body: {
describe: 'Note body',
demandOption: true,
type: 'string'
}
},
handler: function (argv) {
console.log('Title: ' + argv.title)
console.log('Body: ' + argv.body)
}
})
The add command can now be used with --title and --body.
JavaScript provides two methods for working with JSON. The first is JSON.stringify and
the second is JSON.parse. JSON.stringify converts a JavaScript object into a JSON
string, while JSON.parse converts a JSON string into a JavaScript object.
const book = {
title: 'Ego is the Enemy',
author: 'Ryan Holiday'
}
JSON looks similar to a JavaScript object, but there are some differences. The most
obvious is that all properties are wrapped in double-quotes. Single-quotes can’t be
used here, as JSON only supports double-quotes. You can see this in the example
JSON below.
{"name":"Gunther","planet":"Earth","age":54}
Links
• JSON format
There are no notes for this video, as no new information is covered. The goal is to
give you experience using what was covered in previous lessons.
Arrow Functions
Arrow functions offer up an alternative syntax from the standard ES5 function. The snippet
below shows an example of a standard function and then an arrow function. While the
syntax is obviously different, you still have the two important pieces, an arguments list
and a function body.
Shorthand Syntax
Arrow functions have an optional shorthand syntax. This is useful when you have a
function that immediately returns a value. The example below shows how this can
be used.
Notice that two important things are missing from the function definition. First, the curly
braces wrapping the function body have been removed as well as the return
statement. In place of both is the value to be returned. There’s no need for an explicit
return statement, as the value provide is implicitly returned.
This Binding
Arrow functions don’t bind their own this value. Instead, the this value of the scope
in which it was defined is accessible. This makes arrow functions bad candidates for
methods, as this won’t be a reference to the object the method is defined on.
For methods, ES6 provides a new method definition syntax. You can see this in the
definition of the printGuestList method below. That function is a standard function,
just with a shorthand syntax which allows for the removal of the colon and the function
keyword.
Because arrow functions don’t bind this, they work well for everything except methods.
As shown below, the arrow function passed to forEach is able to access this.name
correctly, as it’s defined as an arrow function and doesn’t have a this binding of its own.
That code wouldn’t work if you swapped out the arrow function for a standard function.
const event = {
name: 'Birthday Party',
guestList: ['Andrew', 'Jen', 'Mike'],
printGuestList() {
console.log('Guest list for ' + this.name)
this.guestList.forEach((guest) => {
console.log(guest + ' is attending ' + this.name)
})
}
}
event.printGuestList()
Links
• Arrow function
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Lesson 10: Listing Notes
In this lesson, you’ll create a new app feature that allows users to list out their notes.
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
The example below shows how find can be used to locate the user whose name is
George Hudson.
const users = [{
name: 'Andrew Mead',
age: 27
},{
name: 'George Hudson',
age: 72
},{
name: 'Clay Klay',
age: 45
}]
Links
• Array find method
Section 5: Debugging Node.js
Console.log
While it’s nice to have advanced debugging tools at the ready, there’s nothing wrong
with using console.log to debug your application. It’s not the fanciest technique, but it
works, and I use it daily.
When in doubt, use a few calls to console.log to figure out what’s going on. It’s great for
dumping a variable to the terminal so you can check its value. It also works for figuring
out what order your code is running in.
Node Debugger
Printing values to the console with console.log is a good start, but there are often times
where we need a more complete debugging solution. For that, Node.js ships with a built-in
debugger. It builds off of the developer tools that Chrome and V8 use when debugging
JavaScript code in the browser.
Next, visit chrome://inspect in the Chrome browser. There, you’ll see a list of all the
Node.js processes that you’re able to debug. Click “inspect” next to your Node.js
process to open up the developer tools. From there, you can click the blue “play” button
near the top-right of the “sources” tab to start up the application.
When running the app in debug mode, you can add breakpoints into your application to
stop it at a specific point in the code. This gives you a chance to explore to the
application state and figure out what’s going wrong.
console.log('Thing one)
debugger // Debug tools will pause here until your click play again
console.log('Thing two)
Documentation Links
• Node.js debugger documentation
Error Messages
Error messages can be daunting to use at first. They contain a lot of useful information,
but only if you know what you’re looking at. Let’s start with a complete error. Below is an
error
I generated by trying to reference a variable that was never defined.
/Users/Andrew/Downloads/n3-04-08-arrow-functions/playground/2-arrow-
function.js:21
console.log(guest + ' is attending ' + eventName)
^
The first few lines of the error contain the most useful information.
The first line contains a path to the exact script where the error was thrown. It also
contains the line number. Using that line, you could tell that the issue is on line 21 of
2- arrow-function.js.
The second line shows the line of code that caused the error.
The third line just below uses the “^” character to point to the specific part of the line
that the error came from.
Everything after the fifth line is part of the stack trace. This shows a list of all the
functions that were running to get to the point where the program crashed. The top of
the stack trace starts with the function which threw the error. Here, we can see that the
error was thrown in a callback function for a forEach method call. If you got down to the
next line, you’ll figure out that the forEach call happened inside of printGuestList.
It’ll take a few tries to get comfortable with error messages. Each error you fix makes
it easier to fix the next one.
Section 6: Asynchronous Node.js
Async 101
When running asynchronous code, your code won’t always execute in the order you might
expect. To get started with asynchronous development, let’s use setTimeout.
setTimeout is a function that allows you to run some code after a specific amount of time
has passed. setTimeout accepts two arguments. The first is a callback function. This
function will run after the specified amount of time has passed. The second argument
is the amount of time in milliseconds to wait.
Here’s an example.
console.log('Starting')
console.log('Stopping')
Run the script and you’ll see the logs in the following order.
$ node app.js
Starting
Stopping
2 Second Timer
Notice that “Stopping” prints before “2 Second Timer”. That’s because setTimeout is
asynchronous and non-blocking. The setTimeout call doesn’t block Node.js from
running other code while it’s waiting for the 2 seconds to pass.
This asynchronous and non-blocking nature makes Node.js ideal for backend
development. Your server can wait for data from a database while also processing
an incoming HTTP request.
Links
• setTimeout
This lesson contains a detailed presentation. Please refer to the video for a recap of
how asynchronous programming works.
npm i [email protected]
Before you use the library in your app, you’ll need to figure out which URL you’re trying to
fetch. To fetch real-time weather data, you’ll need to sign up for a free Dark Sky API
account. You can do that here.
Below is an example URL that responds with forecast data for San Francisco.
https://api.darksky.net/forecast/9d1465c6f3bb7a6c71944bdd8548d026/37.8267,-122.4233
If you visit that URL in the browser, you’ll see that the response is JSON data. This same
data can be fetched by our Node.js app using the request library. The example
below fetches the forecast data and prints the current temperature to the console.
const url =
'https://api.darksky.net/forecast/9d1465c6f3bb7a6c71944bdd8548d026/37.8267,-
122.4233'
Links
• npm: request
Request Options
The request library comes with plenty of options to make your life easier. One is the json
option. Set json to true and request will automatically parse the JSON into a JavaScript
object for you.
const request = require('request')
const url =
'https://api.darksky.net/forecast/9d1465c6f3bb7a6c71944bdd8548d026/37.8267,-
122.4233'
$ node app.js
Mostly cloudy overnight. It is currently 51.49 degrees out. There is a 0%
chance of rain.
There’s a link below where you can explore all available options.
Links
• npm: request options
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Handling Errors
Handling errors is important. It would be nice if we could always provide the user with a
forecast for their location, but that’s not going to happen. When things fail, you should aim
to provide users with clear and useful messages in plain English so they know what’s
going on.
The callback function you pass to request expects an error and response argument
to be provided. Either error or response will have a value, never both. If error has a
value, that means things went wrong. In this case, response will be undefined, as there
is no response. If response has a value, things went well. In this case, error will be
undefined, as no error occurred.
The code below handles two different errors. The if statement first checks if error exists. If
it does, the program prints a message letting the user know it was unable to connect.
The second error occurs if there’s no match for the given address. In that case, the
program prints a message instructing the user to try a different search. Lastly, the
coordinates are printed to the console if neither error occurs.
const geocodeURL =
'https://api.mapbox.com/geocoding/v5/mapbox.places/philadelphia.json?access_t
oken=pk.eyJ1IjoiYW5kcmV3bWVhZDEiLCJhIjoiY2pvOG8ybW90MDFhazNxcnJ4OTYydzJlOSJ9.
njY7HvaalLEVhEOIghPTlw&limit=1'
The example below shows how you can use the callback pattern in your own code. The
geocode function is set up to take in two arguments. The first is the address to geocode.
The second is the callback function to run when the geocoding process is complete. The
example below simulates this request by using setTimeout to make the process
asynchronous.
callback(data)
}, 2000)
}
The call to geocode provides both arguments, the address and the callback function.
Notice that the callback function is expecting a single parameter which it has called
data. This is where the callback function will get access to the results of the
asynchronous operation. You can see where callback is called with the data inside the
geocode function.
You can see an example of this below. The function geocode was created to serve as a
reusable way to geocode an address. It contains all the logic necessary to make the
request and process the response. geocode accepts two arguments. The first is the
address to geocode. The second is a callback function which will run once the geocoding
operation is complete.
module.exports = geocode
Now, geocode can be called as many times as needed from anywhere in your application.
The snippet below imports geocode and calls the function to get the latitude and
longitude for Boston.
const geocode = require('./utils/geocode')
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Callback Chaining
When working with async code, you’ll often find out that you need to use the results from
one async operation as the input for another async operation. This is something we need
to do in the weather application too. Step one is to geocode the address. Step two is to
use the coordinates to fetch the weather forecast. You can’t start step two until step one is
complete.
You can start one operation after another finishes by using callback chaining. You can
see an example of this in the code below.
// Other lines hidden for brevity
console.log(data.location)
console.log(forecastData)
})
})
First up is the call to geocode. The call to geocode provides an address and a callback
function as it did before. It’s the code inside the callback function that looks a bit
different. The callback function calls forecast. This means that forecast won’t get called
until after geocode is complete. The latitude and longitude from the geocoding operation
is also provided as the input for the forecast function call.
Property Shorthand
The property shorthand makes it easier to define properties when creating a new object.
It provides a shortcut for defining a property whose value comes from a variable of the
same name. You can see this in the example below where a user object is created. The
name property gets its value from a variable also called name.
const name = 'Andrew'
const userAge = 27
const user = {
name: name,
age: userAge,
location: 'Philadelphia'
}
The shorthand allows you to remove the colon and the reference to the variable. When
JavaScript sees this, it’ll get the property value from the variable with the same name.
The example below uses the property shorthand to define name on the user object.
const user = {
name,
age: userAge,
location: 'Philadelphia'
}
console.log(user)
Object Destructuring
The second ES6 feature is object destructuring. Object destructuring gives you a
syntax for pulling properties off of objects and into standalone variables. This is useful
when working with the same object properties throughout your code. Instead of writing
user.name a dozen times, you could destructure the property into a name variable.
console.log(age)
console.log(address)
user is destructured on line 8 above. The age property has been destructured and stored
in age. The location property has also been destructured and stored in address.
const product = {
label: 'Red notebook',
price: 3,
stock: 201,
salePrice: undefined,
rating: 4.2
}
transaction('order', product)
Links
• Destructuring
• Property shorthand
Lesson 13: Destructuring and Property Shorthand Challenge
In this video, it’s on you to use the property shorthand and object destructuring syntax in
your Node.js app.
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
The code below uses the https module to fetch the forecast from the Dark Sky API. Notice
there’s a lot more required to get things working. Separate callbacks are required for
incoming chunks of data, the end of the response, and the error for the request. This
means you’ll likely recreate your own function similar to request to make your life easier.
It’s best to stick with a tested and popular library like request.
const https = require('https')
const url =
'https://api.darksky.net/forecast/9d1465c6f3bb7a6c71944bdd8548d026/40,-75'
response.on('end', () => {
const body = JSON.parse(data)
console.log(body)
})
})
request.end()
Links
• Node.js http documentation
• Node.js https documentation
npm i [email protected]
Next, you can require express. You get access to a single function you can call to create a
new Express application.
Now, app can be used to set up the server. Let’s start by showing a message when
someone visits the home page at localhost:3000 and the weather page at
localhost:3000/weather.
The code above uses app.get to set up a handler for an HTTP GET request. The
first
argument is the path to set up the handler for. The second argument is the function to run
when that path is visited. Calling res.send in the route handler allows you to send back
a message as the response. This will get shown in the browser.
The last thing to do is start the server. This is done by calling app.listen with the port you
want to listen on.
If you run the app, you’ll see the message printing letting you know that the server is
running. This process will stay running until you shut it down. You can always use ctrl +
c to terminate the process. Visit localhost:3000 or localhost:3000/weather to view
the messages!
$ node app.js
Server is up on port 3000.
Links
• Express
Documentation Links
• express - res.send
The example below uses Nodes’ path module to generate the absolute path. The call to
path.join allows you to manipulate a path by providing individual path segments. It starts
with dirname which is the directory path for the current script. From there, the second
segment moves out of the src folder and into the public directory.
app.use(express.static(publicDirectoryPath))
app.listen(3000, () => {
console.log('Server is up on port 3000.')
})
Start the server, and the browser will be able to access all assets in the public directory.
Documentation Links
• path
<html>
<head>
<link rel="stylesheet" href="/css/styles.css">
<script src="/js/app.js"></script>
</head>
<body>
<h1>About</h1>
<img src="/img/me.png">
</body>
</html>
Setting up Handlebars
Start by installing Handlebars in your project.
npm i [email protected]
From there, you’ll need to use app.set to set a value for the 'view engine' config option.
The value is the name of the template engine module you installed. That’s 'hbs'.
Below is an example handlebars view in views/index.hbs. This looks like a normal HTML
document with a few new features. Notice {{title}} and {{name}}. This is a
Handlerbars syntax which allows you to inject variables inside of the template. This is
what allows you to generate dynamic pages.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/css/styles.css">
<script src="/js/app.js"></script>
</head>
<body>
<h1>{{title}}</h1>
<p>Created by {{name}}</p>
</body>
</html>
Now, you can render the template. This is done by defining a new route and calling
res.render with the template name. The “.hbs” file extension can be left off. The
second argument is an object that contains all the variables the template should have
access to when rendering. This is where values are provided for title and name.
Documentation Links
• Handlebars documentation
• npm: hbs
Setting up Partials
You can use partials by telling Handlebars where you’d like to store them. This is done
with a call to hbs.registerPartials. It expects to get called with the absolute path to the
partials directory.
Using Partials
Partials are created with the “hbs” file extension. Partials have access to all the same
features as your Handlebars templates. The header partial below renders the title followed
by a list of navigation links which can be shown at the top of every page.
{{!-- header.hbs --}}
<h1>{{title}}</h1>
<div>
<a href="/">Weather</a>
<a href="/about">About</a>
<a href="/help">Help</a>
</div>
The partial can then be rendered on a page using {{>header}} where “header” comes
from the partial file name. If the partial was footer.hbs, it could be rendered using
{{>footer}}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/css/styles.css">
<script src="/js/app.js"></script>
</head>
<body>
{{>header}}
</body>
</html>
The 404 page should be set up just before the call to app.listen. This ensures that
requests for valid pages still get the correct response.
app.get('*', (req, res) => {
res.render('404', { title:
'404',
name: 'Andrew Mead',
errorMessage: 'Page not found.'
})
})
There are no notes for this styling video, as no new Node.js features are covered.
There are no notes for this styling video, as no new Node.js features are covered.
http://localhost:3000/weather?address=boston
Below is one more example where two key/value pairs are set up. The key/value pairs are
separated by &. address equals philadelphia and units equals us.
http://localhost:3000/weather?address=philadelphia&units=us
The Express route handler can access the query string key/value pairs on req.query. The
handler below uses req.query.address to get the value provided for address.
This address can then be used to fetch the weather information.
Documentation Links
• express - req.query
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
You can see this in action for the greeter function below. name will be 'user' if no value
is
provided. age will be undefined if no value is provided.
This syntax can also be used to provide default values when using ES6 destructuring. The
transaction function below shows this off by providing a default value for stock.
transaction('order')
Documentation Links
• mdn: default function parameters
The fetch call below is used to fetch the weather for Boston. An if statement is then used
to either print the forecast or the error message.
fetch('http://localhost:3000/weather?address=Boston').then((response) => {
response.json().then((data) => {
if (data.error) {
console.log(data.error)
} else {
console.log(data.location)
console.log(data.forecast)
}
})
})
Documentation Links
• Fetch API
• Fetch Tutorial
<form>
<input placeholder="Location">
<button>Search</button>
</form>
Using client-side JavaScript, you can set up an event listener that will allow you to run
some code when the form is submitted. What should that code do? It should grab the
address from the text field, send off an HTTP request to the Node server for the data,
and then render the weather data to the screen.
For the moment, the data is logged to the console. That’ll get fixed in the next lesson.
const weatherForm = document.querySelector('form')
const search = document.querySelector('input')
fetch('http://localhost:3000/weather?address=' +
location).then((response) => {
response.json().then((data) => {
if (data.error) {
console.log(data.error)
} else {
console.log(data.location)
console.log(data.forecast)
}
})
})
})
<p id="message-1"></p>
The code below can be used to change the text content of the paragraph. First up,
document.querySelector is used to target the element. It’s used with #, which
searches for elements by their ID. The text content can be updated by setting a value
on the textContent property.
const messageOne = document.querySelector('#message-1')
messageOne.textContent = 'My new text'
The Heroku CLI gives you commands to deploy and manage your Node.js applications.
Before you can do that, you’ll need to log in to your Heroku account. This makes sure
that the command you run actually changes your Heroku applications.
heroku login
Documentation Links
• GitHub
• Heroku
• Heroku CLI download
Lesson 3: Version Control with Git
In this lesson, you’ll learn about version control. Version control allows you to track
changes to your project code over time. This makes it easy to recover lost code
and restore your project to a previously working version.
Imagine you have an application with 250 paying users. You just finished work on a great
new feature and you deploy it to production so your customers can use it. Hours later, you
discover a bug that’s preventing users from using the application. What do you do next?
Without version control, you’re in trouble. The only version of your app is the one you have
on your machine. The buggy application that’s crashing for your users. You have no way of
getting back to the old version of your app that was working. Users are stuck with a
broken application until you can fix the bug and get a new version of the app deployed.
With version control, you’re in the clear. You can revert back to your application’s
previous working state and deploy that. This means that users can continue to use the
original version while you can take a breath and get back to working on that new feature
until it’s ready.
You can grab the Git installer from git-scm.com. After installing Git, run git --version to
print the version of Git installed.
Documentation Links
• Git
This lesson contains a detailed presentation. Please refer to the video for a recap of how
Git works.
Before going any further, Git needs to be configured to ignore this node_modules folder.
This is a generated directory which doesn’t need to be under version control. You can
always regenerate node_modules by running npm install. Create a .gitignore file
with the following line to ignore the folder.
node_modules/
Committing Changes
Think of a commit as a save point. A commit lets you create a save point that contains
your project files exactly as they were when the commit was created. You’ll create new
commits to track your changes as you continue to build out your application.
Before creating a commit, it’s a good idea to run git status to get a summary of the
changes that are about to be committed. This will show untracked files, unstaged changes,
and staged files.
Using git add <path to file>, you can add files to the staging area. Changes to files in
the staging area will be included in the next commit. The shortcut below adds
all untracked files and unstaged changes to the staging area.
git add .
You can now use the git commit command to create a commit. Each commit requires a
commit message. The command below creates a commit and provides “Initial commit”
as the commit message.
From here, you can continue to add new features to the project and use the git commands
to create new commits.
Lesson 6: Setting up SSH Keys
In this lesson, you’ll be setting up SSH on your machine. SSH is the protocol used
to securely transfer code between your machine and GitHub/Heroku.
SSH uses an SSH key pair to secure the connection between your machine and the
machine you’re communicating with. You can check if you already have an SSH key pair
with the following command. You have a key pair if you see id_rsa and id_rsa.pub in
the output.
ls -a -l ~/.ssh
You can create a new key pair using the following command. Make sure to swap out the
email for your email address.
The SSH key needs to be configured to be used for new SSH connections. First, ensure
that the SSH agent is running. You can do that using the command below.
Next, add the new SSH private key file to the SSH agent. The following command is for
macOS users.
ssh-add -K ~/.ssh/id_rsa
The command below will allow you to dump the contents of the public key file to the
terminal. Copy and paste the contents to the clip board and register the SSH key
with GitHub here.
cat ~/.ssh/id_rsa.pub
Once the repository is created, you’ll need to set up the origin remote. Replace <repo
url> with the repository URL provided by GitHub.
You can now push your latest commits to the remote! After pushing your commits, refresh
the GitHub repository page in your browser to see your project files and folder appear.
The start script in package.json is used to tell Heroku which command to run. Set start
equal to node src/app.js to ensure that Heroku can start your app up correctly.
Heroku uses an environment variable to provide the port value you need to listen on. The
code below accesses the Heroku port value and uses it to start up the server.
app.listen(port, () => {
console.log('Server is up on port ' + port)
})
You can run git pus heroku master to deploy. From there, run heroku open to
open
your application in the browser.
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
The dev script needs nodemon to be installed. The issue is that nodemon isn’t listed as
a dependency in package.json. However, this can be fixed by uninstalling nodemon
globally.
Now, npm install will be able to install all your application dependencies, including
nodemon!
This lesson contains a detailed presentation. Please refer to the video for a recap of
NoSQL and MongoDB.
You can start the server using the following command. Make sure to swap out
“/Users/Andrew/” with the correct path to your users home directory.
/Users/Andrew/mongodb/bin/mongod --dbpath=/Users/Andrew/mongodb-data
Documentation Links
• MongoDB download page
You can download the MongoDB Community Server from the MongoDB download page.
The download is a zip file. Unzip the contents, change the folder name to “mongodb”, and
move it to your users home directory. From there, create a “mongodb-data” directory in
your user directory to store the database data.
You can start the server using the following command. Make sure to swap out
“/Users/Andrew/” with the correct path to your users home directory.
/Users/Andrew/mongodb/bin/mongod --dbpath=/Users/Andrew/mongodb-data
Documentation Links
• MongoDB download page
Robo 3T is a completely free MongoDB admin tool. Grab the installer from here and get
it installed on your machine.
Documentation Links
• Robo 3T download page
Connecting to MongoDB
MongoDB provides a native driver that allows you to connect to your database from
Node. You can grab the driver by installing the mongodb npm module as shown below.
npm i [email protected]
With the driver installed, you can use the following code to connect to the database. You
just need to provide two pieces of information. The first is the connection URL and
the second is the name of the database. You can pick any database name that you
like.
const db = client.db(databaseName)
Inserting a Document
With the connection open, you’re ready to insert documents into the database. Remember
that a database is made up of collections, and collections are used to store documents.
The code below inserts a new document into the “users” collection. db.collection is
used to get a reference to the collection you’re trying to manipulate. insertOne is used
to insert a new document into that collection.
db.collection('users').insertOne({
name: 'Andrew',
age: 27
})
Documentation Links
• npm: mongodb
• MongDB driver documentation
Inserting Documents
You already know that insertOne can be used to insert a single document. You can also
use insertMany to insert multiple documents at once. The example below inserts two
documents into “tasks” collection. insertMany expects an array of objects, an array of
the documents you want to insert.
db.collection('tasks').insertMany([
{
description: 'Clean the house',
completed: true
},{
description: 'Renew inspection',
completed: false
}
], (error, result) => {
if (error) {
return console.log('Unable to insert tasks!')
}
console.log(result.ops)
})
Documentation Links
• insertOne
• insertMany
An ObjectID is a GUID (Globally Unique Identifier). GUIDs are generated randomly via an
algorithm to ensure uniqueness. These IDs can be generated on the server, but as seen
in the snippet above, they can be generated on the client as well. That means a client can
generate the ID for a document it’s about to insert in to the database.
Finding Documents
You can search for documents in a given collection using find or findOne. find can be
used to fetch multiple documents, while findOne can be used to fetch a single
document.
The example below uses find to search for documents in the tasks collection. You can
provide an object as the first argument to find to filter the documents. The example
below sets completed equal to false to fetch only those tasks that haven’t been
completed.
db.collection('tasks').find({ completed: false }).toArray((error, tasks) => {
console.log(tasks)
})
The next example uses findOne to find a single document by its ID. In this case, it’s
necessary to pass the string version of the ID to the ObjectID constructor function
to convert it to an ObjectID.
Documentation Links
• find
• findOne
doWorkPromise.then((result) => {
console.log('Success!', result)
}).catch((error) => {
console.log('Error!', error)
})
The update calls require a second argument as well. This is an object where you
define the updates you want to make. For this, you need to use one of the supported
update operators.
The updateOne call below uses $inc to increment the age field on the targeted document
by 1.
db.collection('users').updateOne({
_id: new ObjectID("5c0fe6634362c1fb75b9d6b5")
}, {
$inc: {
age: 1
}
}).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error)
})
The updateMany call below uses $set to set the completed field to true for all
documents
where the completed field is currently false.
db.collection('tasks').updateMany({
completed: false
}, {
$set: {
completed: true
}
}).then((result) => {
console.log(result.modifiedCount)
}).catch((error) => {
console.log(error)
Documentation Links
• updateOne
• updateMany
• Update operators
Deleting Documents
You can delete documents from MongoDB using deleteOne or deleteMany. Both accept
an object as the first argument. This object is used to filter just the documents you want
to delete.
The example below uses deleteMany to delete all users whose age field is 27.
db.collection('users').deleteMany({
age: 27
}).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error)
})
This next example uses deleteOne to delete a single document, the first one with a
description of "Clean the house".
db.collection('tasks').deleteOne({
description: "Clean the house"
}).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error)
})
Documentation Links
• deleteOne
• deleteMany
Section 11: REST APIs and Mongoose
Setting up Mongoose
First up, install Mongoose.
npm i [email protected]
Like the MongoDB native driver, Mongoose provides a connect function you can use to
connect to your MongoDB database.
mongoose.connect('mongodb://127.0.0.1:27017/task-manager-api', {
useNewUrlParser: true,
useCreateIndex: true
})
With the model defined, it’s time to start creating and saving users. The User variable
above stores the Mongoose model. This is a constructor function that can be used to
create new users. The snippet below creates a new user with the name 'Andrew' and the
age 27. This alone won’t save any data to the database, but it’s a step in the right
direction.
The new model instance can be saved to the database using the save method.
me.save().then(() => {
console.log(me)
}).catch((error) => {
console.log('Error!', error)
})
Documentation Links
• Mongoose
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Lesson 4: Data Validation and Sanitization: Part I
In this lesson, you’ll set up data validation and sanitization for your models. Validation
will allow you to restrict what data can be stored in the database, while sanitization will
allow you to store user data in a uniform and standardized way.
npm i [email protected]
Mongoose comes with support for basic validation and sanitization. The user model below
shows how this can be configured. required is used to validate that a value is provided
for a given field. trim is used to remove extra spaces before or after data. lowercase is
used to convert the data to lowercase before saving it to the database. You can find a
complete list of options in the schema documentation.
You can also define custom validation for your models. This is done using validate as
shown in the example below. The method gets called with the value to validate, and it
should throw an error if the data is invalid. The example below uses the isEmail
method from validator to validate the email address is valid before saving it to the
database.
const mongoose = require('mongoose')
const validator = require('validator')
Documentation Links
• npm: validator
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
This lesson contains a detailed presentation. Please refer to the video for a recap of
how asynchronous programming works.
You can grab Postman here. It’s free and available for all operating systems.
Documentation Links
• Postman
The code below uses app.post to set up a POST request handler for /users. The handler
function creates a new instance of the user model and saves it to the database.
express.json is also setup to parse incoming JSON into a JavaScript object which you
can access on req.body.
app.use(express.json())
user.save().then(() => {
res.send(user)
}).catch((e) => {
res.status(400).send(e)
})
})
There are no notes for this challenge video as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Lesson 10: Resource Reading Endpoints: Part I
In this lesson, you’ll learn how to create REST API endpoints for reading resources. This
will allow users of the API to fetch users and tasks from the database.
The code below uses app.get to set up a GET request handler for /users/:id. :id
serves
as a placeholder for the ID of the user to fetch. If the request is GET /users/321, then
the ID will be 321. This is known as a URL parameter, and you can access the value for
URL parameters on req.params.
User.findById(_id).then((user) => {
if (!user) {
return res.status(404).send()
}
res.send(user)
}).catch((e) => {
res.status(500).send()
})
})
Documentation Links
• Express route parameters
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Lesson 12: Promise Chaining
In this lesson, you’ll explore promise chaining. Promise chaining is a syntax that allows
you to chain together multiple asynchronous tasks in a specific order. This is great for
complex code where one asynchronous task needs to be performed after the completion
of a different asynchronous task.
Promise Chaining
To demonstrate promise chaining, the following function will be used to simulate an
asynchronous task. In reality, it’s just adding up a couple of numbers, waiting two seconds,
and fulfilling the promise with the sum.
resolve(a + b)
}, 2000)
})
}
With the dummy asynchronous function defined, promise chaining can be used to call add
twice. The code below adds up 1 and 2 for a total of 3. It then uses the sum of 3 as
the input for another call to add. The second call to add adds up 3 and 4 for a total
of 7.
Promise chaining occurs when the then callback function returns a promise. This allows
you to chain on another then call which will run when the second promise is fulfilled. catch
can still be called to handle any errors that might occur along the way.
add(1, 2).then((sum) => {
console.log(sum) // Will print 3
return add(sum, 4)
}).then((sum2) => {
console.log(sum2) // Will print 7
}).catch((e) => {
console.log(e)
})
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Exploring Async/Await
The example below uses the add function that was created two lessons ago.
The first step to using async and await is to create an asynchronous function. This is
done using the async keyword before the function definition. This can be seen in the
definition of doWork below. Any function can be defined as an asynchronous function,
not just arrow functions.
With an async function in place, you can now use the await operator. The await operator
can only be used inside of asynchronous functions. This removes the need for
excess callbacks and makes code much easier to read.
The await operator is used with promises in asynchronous functions. You can see this
used three times in doWork. The await operator allows you to work with promises in a
way that looks like synchronous code. If the promise is fulfilled, the fulfilled value can be
accessed as the return value from the function. If the promise is rejected, it would be as
though the function threw an error. await will pause the function execution until the
promise is either fulfilled or rejected.
It’s important to note that async and await are syntax enhancements for working with
promises. Promises are still at the core of asynchronous code that uses async and
await.
doWork().then((result) => {
console.log('result', result)
}).catch((e) => {
console.log('e', e)
})
Documentation Links
• mdn: async function
• mdn: await operator
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Integrating Async/Await
Below is a modified version of GET /users. The handler function was set up as an
asynchronous function which allows you to use await in the function. await is used with
the promise from User.find to get a list of all the users without needing to call then
and catch with handler functions. A try/catch statement is also used to handle any
errors that might occur.
app.get('/users', async (req, res) => {
try {
const users = await User.find({})
res.send(users)
} catch (e) {
res.status(500).send()
}
})
When working with updates, it’s a good idea to alert the user if they’re trying to update
something that they can’t update. The code below checks that the user is only updating
fields that can be updated, otherwise it will send back an error response.
if (!isValidOperation) {
return res.status(400).send({ error: 'Invalid updates!' })
}
If all goes well, the updates will be applied to the user, then a response will be sent back.
If the provided updates are valid, findByIdAndUpdate can be used to update the
document in the database. Try/catch is used here to send back an error if something
goes wrong when updating the user. This would include the new data not passing the
validation defined for the model.
try {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new:
true, runValidators: true })
if (!user) {
return res.status(404).send()
}
res.send(user)
} catch (e) {
res.status(400).send(e)
}
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Deleting Resources
Resource deleting endpoints use the DELETE HTTP method. The URL structure is
/resources/:id for deleting an individual resource by its ID. If you want to delete
an individual task with the ID of 897, it would be DELETE /tasks/897.
try {
const user = await User.findByIdAndDelete(req.params.id)
if (!user) {
return res.status(404).send()
}
res.send(user)
} catch (e) {
res.status(500).send()
}
module.exports = router
The router defined in the file above can be added into the Express application in
index.js. This is done by loading the router in with require and then passing the router
to app.use. You can set up as many routers as you need for your application, though
it’s common to have a router for each distinct resource your REST API has.
Documentation Links
• express.Router
Hashing Passwords
Storing plain text passwords is a bad idea. Most folks reuse password for multiple
accounts online. That means if your database gets compromised, the hacker can reuse
those credentials on other sites such as credit cards or bank accounts. We don’t want
to leave our users open to further attacks.
The solution is to hash passwords using a secure one-way hashing algorithm. Users
passwords will stay hidden and secure, even if the database is compromised.
The hash method can be used to hash the plain text password. The example below
hashes the password “Red12345!”.
The compare method is used to compare a plain text password against a previously
hashed password. This would be useful when logging in. The user logging in provides
the plain text password for their account. The application fetches the hashed password
from the database for that user. compare is then called to confirm it’s a match.
Documentation Links
• npm: bcryptjs
Mongoose Middleware
Middleware allows you to register some code to run before or after a lifecycle event for
your model. As an example, you could use middleware to register some code to run just
after a user is deleted. You could also use middleware to register some code to run just
before the user is saved. This can be used to hash passwords just before saving users
to the database.
The example below calls pre with the 'save' lifecycle event. This registers a function to
run just before users are saved. The function itself checks if the password has been
altered. If the password has been altered, the plain text password is overwritten with
a hashed version.
userSchema.pre('save', async function (next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next()
})
Documentation Links
• Mongoose middleware
Logging in Users
Logging in a user is a two-step process. The user provides their email and password,
and the first thing to do is fetch the user by their email. From there, bcrypt is used to
verify the password provided matches the hashed password stored in the database. If
either step fails, the users won’t be able to log in. If both steps succeed, then you know
the user is who they say they are.
The code below sets up findByCredentials which finds a user by their email and
password.
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email })
if (!user) {
throw new Error('Unable to login')
}
if (!isMatch) {
throw new Error('Unable to login')
}
return user
}
You can then call findByCredentials from you application when users need to login. The
example below shows how this can be done.
npm i [email protected]
The sign method can be used to generate a new token. sign accepts three arguments:
The first is the data to embed in the token: This needs to include a unique identifier for the
user.
The second is a secret phrase: This is used to issue and validate tokens, ensuring that
the token data hasn’t been tampered with.
The third is a set of options: The example below uses expiresIn to create a token
that’s valid for seven days.
Tokens can be issued to users when they sign up or log in to the application. These can
then be stored on the data and used to authenticate the user when they perform other
options.
The server can verify the token using verify. This requires two arguments:
The second is the secret phrase that the token was created with. If valid, the embedded
data will be returned. This would allow the server to figure out which user is performing
the operation.
The snippet below adds a tokens array onto the user model. This will be used to store all
valid authentication tokens for a user.
// Other properties and options omitted for brevity
const userSchema = new mongoose.Schema({
tokens: [{
token: {
type: String,
required: true
}
}]
})
The instance method below is responsible for generating new authentication tokens. The
token is created, stored in the database, and finally returned from the function.
return token
}
Documentation Links
• Express middleware
if (!user) {
throw new Error()
}
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' })
}
}
module.exports = auth
The authentication middleware can be added to individual endpoints to lock them down.
This is shown with GET /users/me below. auth is added as the second argument to
router.get, meaning that it will run before the route handler function runs. This will ensure
the user is authenticated.
There are no notes for this video. Refer to the video to learn how to set up
Postman environments.
userSchema.methods.toJSON = function () {
const user = this
const userObject = user.toObject()
delete userObject.password
delete userObject.tokens
return userObject
}
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Mongoose Relationships
To set up the relationship, both the user and task model will be changed. First up, a
new field needs to be added onto the task. This will store the ID of the user who
created it.
// Other properties and options omitted for brevity
const Task = mongoose.model('Task', {
owner: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
}
})
Next, a virtual property needs to be added onto the user. The code below adds a
tasks
field onto users that can be used to fetch the tasks for a given user. It’s a virtual
property because users in the database won’t have a tasks field. It’s a reference to the
task data stored in the separate collection.
userSchema.virtual('tasks', {
ref: 'Task',
localField: '_id',
foreignField: 'owner'
})
With the relationship configured, tasks can be created with an owner value.
The code below shows how you can fetch the owner of a given task.
The code below shows how you can fetch the tasks for a given user.
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Lesson 15: Cascade Delete Tasks
In this lesson, you’ll learn how to use Mongoose middleware to clean up a user’s tasks
when they close their account. This will make sure that all their data is securely removed
from the database.
Filtering Data
GET /tasks below supports a completed query parameter which can be set to true
or false. This will prevent clients from fetching unnecessary data that they don’t plan
on using.
const match = {}
From there, check if the query parameter was provided. The provided value should be
parsed into a boolean and stored on match.completed.
if (req.query.completed) {
match.completed = req.query.completed === 'true'
}
Last up, match can be added onto populate to fetch just the users that match the search
criteria.
await req.user.populate({
path: 'tasks',
match
}).execPopulate()
Data Pagination
Pagination is configured using limit and skip. These two values give the client
complete control of the data they’re getting back.
If a client wanted the first page of 10 tasks, limit would be set to 10 and skip would be
set to 0. If the client wanted the third page of 10 tasks, limit would be set to 10 and skip
would be set to 20.
Both limit and skip can be added onto the options object passed to populate. The
code below uses parseInt to convert the string query parameters into numbers first.
await req.user.populate({
path: 'tasks',
match,
options: {
limit: parseInt(req.query.limit),
skip: parseInt(req.query.skip)
}
}).execPopulate()
Sorting Data
The options object used for pagination can also be used for sorting. A sort property
should be set, which is an object containing key/value pairs. The key is the field to
sort. The value is 1 for ascending and -1 for descending sorting.
GET /tasks will get support for a sortBy query parameter. The value should include the
field to sort and the order in which to sort. createdAt:asc would sort the tasks in
ascending order with the oldest first. createdAt:asc would sort the tasks in a
descending order with the newest first.
const sort = {}
If the query parameter is provided, it’ll get parsed and sort will be updated.
if (req.query.sortBy) {
const parts = req.query.sortBy.split(':')
sort[parts[0]] = parts[1] === 'desc' ? -1 : 1
}
sort is then added onto options. If sortBy isn’t provided, sort will be an empty object
and no sorting will occur.
await req.user.populate({
path: 'tasks',
match,
options: {
limit: parseInt(req.query.limit),
skip: parseInt(req.query.skip),
sort
}
}).execPopulate()
Configuing Multer
First up, install the library.
npm i [email protected]
Multer can then be configured to fit your specific needs. The example below shows off a
basic configuration where dest is set to avatars. This will store all uploaded files in
a directory called avatars.
Multer is then added as middleware for the specific endpoint that should allow for file
uploads. The route below is expecting a single avatar field on the submitted form.
Documentation Links
• npm: multer
fileFilter is set to validate the file type. The method below will reject all documents that
don’t have either .doc or .docx file extensions. This same technique could be used to
limit uploads to just images, PDFs, or any other file type.
cb(undefined, true)
}
})
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
The function itself sends back a JSON response with the error message from multer.
router.post('/users/me/avatar', upload.single('avatar'), (req, res) => {
res.send()
}, (error, req, res, next) => {
res.status(400).send({ error: error.message })
})
The avatar upload route will be able to change the user profile data, so the route should
be put behind authentication. The handler function grabs the binary data and stores it
on the avatar field. Finally, the changes are saved.
The route below fetches the image data and sets the Content-Type header for the
response. The URL could be visited to view the profile picture.
if (!user || !user.avatar) {
throw new Error()
}
res.set('Content-Type', 'image/jpg')
res.send(user.avatar)
} catch (e) {
res.status(404).send()
}
})
npm i [email protected]
Now, sharp can be used to manipulate uploaded images. Before the image data is
added
onto the user profile, the data should be passed through sharp. The example below
uses resize to resize all uploads to 250 by 250 pixels. The example also uses png to
convert all images to portable network graphics. Lastly, toBuffer is used to retrieve the
modified image data. The modified data is what should be saved in the database.
const sharp = require('sharp')
Documentation Links
• npm: sharp
Exploring SendGrid
First up, install the module.
npm i sendgrid/[email protected]
Next, create a free SendGrid account and get your API key. Check out the lesson video to
learn how to get your API key. The code below shows what’s necessary to get the
SendGrid module configured. All you need to do is call setApiKey to… well… set your
API key.
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey('SG.EPCyKzFZT6yUHXzuxdU4tQ.d60AWJbSwkMAplANUtf1Vx47t9TFLSLMv
QzmN4tYEuM')
send can be called to send an email from your application. The configuration object can
be
used to provide:
sgMail.send({
to: '[email protected]',
from: '[email protected]',
subject: 'This is my first creation!',
text: 'I hope this one actually get to you.'
})
In the long term, you’ll want to purchase a custom domain and register it with SendGrid.
This will increase your sending reliability.
Documentation Links
• SendGrid
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Environment Variables
First up, install the npm module.
npm i [email protected]
Next up, create an environment file dev.env in the config directory. This will store your
environment variables in the following format.
KEY=value
ANOTHER_KEY=some other value
Next, update the dev script to use env-cmd to load in those environment variables when it
starts up. That would be env-cmd ./config/dev.env nodemon src/index.js.
Now, you can remove API keys and database credentials from the application itself. For
example, you can create MONGODB_URL in the development environment file. The
application code shown below can then reference that environment variable to get its
value. This can be done with the SendGrid API key and the JWT secret used to
generate and verify authentication tokens.
mongoose.connect(process.env.MONGODB_URL, {
useNewUrlParser: true,
useCreateIndex: true,
useFindAndModify: false
})
Documentation Links
• npm: env-cmd
Please refer to the video for the detailed steps required to set up the production
database.
A variation of that command can be used to fetch all the environment variables currently
configured.
heroku config
Setting up Jest
First up, install the module.
npm i [email protected]
Next, create a test script in package.json. The script itself is jest. This allows you to use
npm test to run the Jest test suite.
Now, you’ll need to create a test suite. This is a file in your project that ends with .test.js.
The file extension allows Jest to find and run the test suites for your project.
If the test function throws an error, the test cast will fail. If the test function doesn’t throw an
error, the test case will pass.
})
Documentation Links
• Jest
module.exports = {
calculateTip
}
The test suite below has a single test case for the calculateTip function. The test case
itself calculates a 30% tip on a $10 restaurant bill. The assertion checks that the calculated
total equals $13. The assertion is made using toBe to check for equality.
Documentation Links
• expect
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Your test cases can use async/await as well. The test case below is a refactored version
of
the test case above. The test case function is defined with async. await is used in
the function to ensure that Jest waits for those asynchronous tasks to complete. Both
test cases are functionally identical.
With the environment in place, update the test script to load the environment file in. That
would be env-cmd ./config/test.env jest --watch --runInBand.
Configuring Jest
By default, Jest is expecting to run in the browser. You can use Jest with Node, but
you’ll need to configure Jest to enable support. Jest can be configured by adding a
jest
property in package.json. The configuration below sets testEnvironment to node
to ensure that Jest runs correctly in Node.js.
{
"jest": {
"testEnvironment": "node"
}
}
Documentation Links
• Configuring Jest
npm i [email protected]
Now, supertest can be used to test an endpoint. The test case below tests that new users
can sign up for accounts. All the account data provided is valid, so a new account
should be created.
Step one is to pass the express app to request. Next, supertest methods can be chained
together to fit the needs of your tests. post is used to make an HTTP POST request to
/users. send is used to send the correct JSON data to the server. expect is used to
assert that the response status code is correct. In this case, a successful signup should
result in a
201 status code.
const request = require('supertest')
const app = require('../src/app')
Documentation Links
• npm: supertest
Seeding Database
Jest provides lifecycle functions that you can use to configure your test suite. There
are four:
beforeEach works great for adding test data to the database. The beforeEach call below
removes all users and then adds a single test user into the database. By having this run
before each test case, it ensures that the tests run in a consistent environment each
time they execute.
const User = require('../src/models/user')
const userOne = {
name: 'Mike',
email: '[email protected]',
password: '56what!!'
}
beforeEach(async () => {
await User.deleteMany()
await new User(userOne).save()
})
With the test user in place, the test case below is able to test the login operation by
logging in as the test user.
Documentation Links
• Jest setup and teardown
From there, the authentication token can be added as part of the supertest request.
Supertest provides a set method for setting request headers. The test case
below attempts to fetch the user profile for the logged-in user.
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
module.exports = {
setApiKey() {
},
send() {
}
}
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Refer to the video for refactoring instructions. The refactoring requires lots of code to be
shifted between files, which is a little more than could be covered in this guide.
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Lesson 3: WebSockets
In this lesson, you’ll learn about the WebSocket protocol. The WebSocket protocol
supports real-time bi-direction communication, which makes it a great fit for the chat
application.
This lesson contains a detailed presentation. Please refer to the video for a recap of
WebSockets.
Lesson 4: Getting Started with Socket.io
In this lesson, you’ll install and configure Socket.io. Socket.io comes with everything
needed to set up a WebSocket server using Node.
Setting up Socket.io
First up, install the module.
npm i [email protected]
Socket.io can be used on its own or with Express. Since the chat application will be
serving up client-side assets, both Express and Socket.io will get set up. The server file
below shows how to get this done.
const path = require('path')
const http = require('http')
const express = require('express')
const socketio = require('socket.io')
app.use(express.static(publicDirectoryPath))
server.listen(port, () => {
console.log(`Server is up on port ${port}!`)
The server above uses io.on which is provided by Socket.io. on allows the server to listen
for an event and respond to it. In the example above, the server listens for connection
which allows it to run some code when a client connects to the WebSocket server.
<script src="/socket.io/socket.io.js"></script>
<script src="/js/chat.js"></script>
Your client-side JavaScript can then connect to the Socket.io server by calling io. io is
provided by the client-side Socket.io library. Calling this function will set up the connection,
and it’ll cause the server’s connection event handler to run.
Documentation Links
• Socket.io
Events can be sent from the sender using emit. Events can be received by the receiver
using on. The example below shows how this pattern can be used to create a
simple counter application. The following snippet contains the client-side JavaScript
code.
document.querySelector('#increment').addEventListener('click', () => {
// Emit "increment
socket.emit('increment')
})
The client-side code uses on to listen for the countUpdated event. A message will be
logged with the current count when that event is received. The client-side code also uses
emit to send the increment event. This occurs when a button on the screen is
socket.emit('countUpdated', count)
socket.on('increment', () => {
count++
io.emit('countUpdated', count)
})
})
The server above is responsible for emitting countUpdated and listening for increment.
New users get the current count right after they connect to the server. If a client sends
increment to the server, the count is incremented and all connected clients are notified
of the change.
On the client, socket.emit emits an event to the server. On the server, both socket.emit
and io.emit can be used. socket.emit sends an event to that specific client, while
io.emit sends an event to all connected clients.
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
Broadcasting Events
Events can be broadcasted from the server using socket.broadcast.emit. This event will
get sent to all sockets except the one that broadcasted the event. The code below shows
this off. When a new user joins the chat application, socket.broadcast.emit is used to
send a message to all other users that someone new has joined.
io.on('connection', (socket) => {
socket.broadcast.emit('message', 'A new user has joined!')
})
document.querySelector('#send-location').addEventListener('click', () => {
if (!navigator.geolocation) {
return alert('Geolocation is not supported by your browser.')
}
navigator.geolocation.getCurrentPosition((position) => {
socket.emit('sendLocation', {
latitude: position.coords.latitude,
longitude: position.coords.longitude
})
})
})
First up, check if navigator.geolocation exists. This will determine if the browser
supports geolocation. From there, navigator.geolocation.getCurrentPosition can be
called to fetch the user’s location. The provided callback function will get called with the
user’s position, which includes the latitude and longitude.
With the client set up, the server can listen for the sendLocation event. When it’s received,
io.emit is used to share that location with everyone in the chat room
socket.on('sendLocation', (coords) => {
io.emit('message',
`https://google.com/maps?q=${coords.latitude},${coords.longitude}`)
})
Documentation Links
• MDN: Geolocation API
Event Acknowledgements
To explore acknowledgements, let’s set up the server to screen messages for profane
language. The bad-words module will let you check text for profane language.
npm i [email protected]
You already know there are two sides to every event, the sender and the receiver. In the
example below, the client is the one emitting the sendMessage event. The big change is
the addition of the callback function. This function will run if/when the server
acknowledges the event.
console.log('Message delivered!')
})
On the server, the event listener for sendMessage also has a small change. Aside from the
message parameter, it now has access to the callback parameter. This callback
function can be called on the server to trigger the acknowledgement function on the
client.
The callback can be called with or without data. In this example, the callback is called with
an error if profane language was detected. The argument would get passed to the client
where the error could be shown. The callback is called without no arguments if no
profane language was detected. This lets the client know that the message was
successfully processed.
if (filter.isProfane(message)) {
return callback('Profanity is not allowed!')
}
io.emit('message', message)
callback()
})
// Disable button
$messageFormButton.setAttribute('disabled', 'disabled')
// Enable buttons
$messageFormButton.removeAttribute('disabled')
The interaction with the text input can also be improved. The text input should be cleared
and focused on when the form is submitted.
const $messageFormInput = $messageForm.querySelector('input')
Creating a Template
First up, include these in your HTML. Mustache will be used to render the messages.
Moment and Qs will be used a bit later in the section.
<script
src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/3.0.1/mustache.min.js
"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"><
/script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.6.0/qs.min.js"></script>
Rendering messages to the screen will require two changes to the HTML. First up, a place
needs to be created on the page to store the rendered messages.
<div id="messages"></div>
Second, a template needs to be created for the messages. The template below looks like
pretty standard HTML. The only addition is {{message}}. This is the syntax used to inject a
value into the template. In this case, the message text will be shown inside the templates
paragraph.
<script id="message-template" type="text/html">
<div>
<p>{{message}}</p>
</div>
</script>
Rendering a Template
The template can be compiled and rendered using client-side JavaScript. The snippet
below renders a new instance of the message template to the screen whenever it
receives a new message event.
Documentation Links
• Mustache.js
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
A JavaScript timestamp is nothing more than an integer. This integer represents the
number of milliseconds since the Unix Epoch. The Unix Epoch was January 1st, 1970
at midnight, so the timestamp for the current point in time is a pretty big number.
Once the server sends the message and timestamp to the client, the client can format the
timestamp before rendering it. The Moment library provides an easy way to format
timestamps to fit your needs. For the chat app, showing something like “11:48 am”
works well.
moment(message.createdAt).format('h:mm a')
You can find a complete list of the formatting options in the documentation below.
Documentation Links
• Moment
• Moment formatting
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
<!DOCTYPE html>
<html>
<head>
<title>Chat App</title>
<link rel="icon" href="/img/favicon.png">
<link rel="stylesheet" href="/css/styles.min.css">
</head>
<body>
<div class="centered-form">
<div class="centered-form box">
<h1>Join</h1>
<form action="/chat.html">
<label>Display name</label>
<input type="text" name="username" placeholder="Display
name" required>
<label>Room</label>
<input type="text" name="room" placeholder="Room"
required>
<button>Join</button>
</form>
</div>
</div>
</body>
</html>
The to method can also be used with io to send an event to everyone in a room.
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.
callback()
})
When a user disconnects from the application, removeUser is called to remove them from
the list of active users. If a user was removed, a message is sent to everyone in the
chat room letting them know that someone has left.
socket.on('disconnect', () => {
const user = removeUser(socket.id)
if (user) {
io.to(user.room).emit('message', generateMessage(`${user.username}
has left!`))
}
})
The server uses an acknowledgement to send errors back to the client. The client can set
up the callback function and respond to any errors that might occur. If an error does
occur, the snippet below shows the error message and then redirects the user back to the
join page.
getUsersInRoom is used to get a list of all users in a specific room. The server then emits
roomData to all clients in that affected room letting them know to rerender their user list.
On the client-side, the sidebar template will be responsible for rendering the room name
and the list of users. Rendering a list with Moustache requires a new syntax. In the
template below, everything between {{#users}} and {{/users}} will be repeated
for each user. In this case, the username is rendered in a list item that will show up in
the sidebar.
<script id="sidebar-template" type="text/html">
<h2 class="room-title">{{room}}</h2>
<h3 class="list-title">Users</h3>
<ul class="users">
{{#users}}
<li>{{username}}</li>
{{/users}}
</ul>
</script>
The client-side is then able to listen for that event and render the user list to the sidebar.
This lesson contains detailed instructions covering automatic scrolling. The function used
to enable autoscrolling is below, but please refer to the lesson video for a recap of how
it was designed.
const autoscroll = () => {
// New message element
const $newMessage = $messages.lastElementChild
// Visible height
const visibleHeight = $messages.offsetHeight
There are no notes for this challenge video, as no new information is covered. The goal
is to give you experience using what was covered in previous lessons.