Node Workshop PDF
Node Workshop PDF
Node Workshop PDF
1. WiFi! 📶
2. Join the Slack: http://bit.ly/37HQkkK
3. Install Git and Sign up to Github (if you don’t have one yet)
○ You’ll need this to fork the Github Repo
4. Install Chrome/Firefox
5. Install VSCode - why? bash terminals built-in
6. Install node - install latest LTS version
○ Download and install from https://nodejs.org/en/download/
○ To test: https://nodejs.org/en/docs/guides/getting-started-guide/
7. Check out the cheat sheet https://goo.gl/m8i2gc for some quick info
Nice to haves! (optional but recommended)
Please install these before the workshop
- Install mongodb (we will use mLab but local DB is good to have for testing)
- Follow https://docs.mongodb.com/manual/installation/
Git setup also in Slack #resources
1. Log-in to github
2. Go to https://github.com/lenmorld/node_workshop
3. In github, fork branch
○ You’ll be asked to login to your github account
4. On local terminal, clone the repo
$ git clone https://github.com/<username>/node_workshop.git
or use SSH if you’d like
5. Checkout dev branch (which should be an empty slate)
$ cd node_workshop
$ git checkout dev
6. Open code editor with node_workshop as the root directory
7. We’re ready!
Git: exploring workshop code
also in Slack #resources
Comparing branches
$ node server.js
>> Hello World!
node_workshop/
server.js
README.md
...
Review of Client-server architecture
REST allows stateless connection between client and server using HTTP protocols and
methods/verbs
REST
// server.js
Server running in
# CLIENT: on another terminal
localhost:4000
$ curl localhost:4000
>> Hello World
C1.2 - Hello World - JSON
// server.js
// JSON object
const song = {
id: 12345,
favorite: false,
title: "Hello World",
artist: "Node programmer",
# to restart server
album: "Node EP"
}; CRTL+C to stop server
$ node server.js
// send JSON response to client
>> Server listening at 4000
res.end(JSON.stringify(song)); // JSON.stringify({a: 1}) -> '{"a":1}'
});
// server.js
...
// Response header
res.writeHead(200, { "Content-Type": "text/html" });
# to restart server
// send HTML response to client # CRTL+C to stop server
res.end("<h1>Hello World</h1>"); $ node server.js
}); >> Starting server at 4000
...
How about serving HTML files?
c1.4 - Using npm and installing express
Express simplifies web server stuff in Node
But first, to install any package (dependency, library) in Node, we need npm
$ npm init -y
$ npm install express
Examine package.json
- installed dependency: express
- Npm scripts
Sidenote: Npm, package.json,
node_modules
Directory structure:
node_workshop/
.gitignore
node_modules/
server.js *include package-lock.json in
package.json the files you check in to git
package-lock.json This is useful in semvar
Express middlewares
What’s a middleware?
● Middlewares are functions that has access to request (req) and response
(res) objects
● We use different kinds of middleware to handle requests
Directory structure:
c1.5 - Introduction to Routing node_workshop/
using express server.js
index.html
// server.js
...
Network tab
gives details
on request and
response
Templating
Here we will use EJS Template engine, since it’s the closest to HTML
render views/info.ejs,
$ curl https://localhost:4000/info
passing some data
c1.8 express.Router() - Organizing code
// routes/pages.js
...
const express = require('express');
const server = express.Router();
express.Router() middleware allows us to:
<ALL THE PAGE ROUTES>
<a href="/pages/info">Info</a>
<a href="/pages/about">About</a>
express.Router() also allows mounting a
// index.html
route module to a route prefix ...
<a href="/pages/info">Info</a>
<a href="/pages/about">About</a>
The good thing with reusable partials like
_header.ejs is you only change it ones for
those views that uses header.ejs.
express.Router() + server.use()
// server.js
...
server.use("/pages", pages);
BONUS: debugging Node.js in VS Code
Debugging Node.js in VSCode works out of the box
Details: https://code.visualstudio.com/docs/nodejs/nodejs-debugging
CHECKPOINT:
Questions
Recap
What can you do with this knowledge?
- Server-rendered pages in HTML (static data) or EJS (dynamic data)
- Simple routing
- using Router() to organize route handlers
Next:
REST API
2 Building a REST server
aka RESTful (HTTP) API web (server)
*We’re building our own API, not using an external API (more on that later)
HTTP requests cardio 🏃🏃♀
Try out these commands to understand HTTP methods, JSON, URL params,
request body, content-type
Sample commands:
$ curl https://jsonplaceholder.typicode.com/posts/1
$ curl https://jsonplaceholder.typicode.com/posts
$ curl -X POST -H "Content-Type: application/json" --data '{"title": "foo", "body": "bar",
"userId": 1}' https://jsonplaceholder.typicode.com/posts/
$ curl -X PUT -H "Content-Type: application/json" --data '{"title": "foo", "body": "bar",
"userId": 2}' https://jsonplaceholder.typicode.com/posts/1
$ curl -X DELETE https://jsonplaceholder.typicode.com/posts/1
// routes/crud.js
module.exports = server;
// data.js
A REST HTTP web server API provides CRUD functionality to its clients
Create POST
Read GET
Update PUT
Delete DELETE
c2.2 Read all items: GET /items route
// routes/crud.js
...
// CRUD RESTful API routes
server.get("/items", (req, res) => {
res.json(data.list);
});
...
...
ES6 arrow
// get an item identified by id
function and
server.get("/items/:id", (req, res) => {
implicit return
const itemId = req.params.id;
const item = data.list.find( _item => _item.id === itemId );
res.json(item);
});
...
$ curl http://localhost:4000/items/0c4IEciLCDdXEhhKxj4ThA
{"id":"0c4IEciLCDdXEhhKxj4ThA","artist":"Muse","title":"Madness","album":"The 2nd Law"}
Designing routes with HTTP methods
What are the methods and parameters we need for REST API?
// routes/crud.js
...
// create/post new item
1. Install and use body-parser server.post("/items", (req, res) => {
middleware for the entire const item = req.body;
console.log('Adding new item: ', item);
app
// add new item to array
2. Inside POST /items route data.list.push(item)
a. Obtain item from req.body
// return updated list
b. Add item to array
res.json(data.list);
c. Return updated list });
...
*while we can use browser for GET requests,
$ curl -X POST -H "Content-Type: application/json" --data '{"id": "abcd1234", "artist": "Meee", "title":
"sooong", "album": "Hello Word"}' http://localhost:4000/items
[{"id":"0c4IEciLCDdXEhhKxj4ThA","artist":"Muse","title":"Madness","album":"The 2nd
Law"},{"id":"2QAHN4C4M8D8E8eiQvQW6a","artist":"One Republic","title":"I
Lived","album":"Native"},{"id":"abcd1234","artist":"Meee","title":"sooong","album":"Hello Word"}]
// routes/crud.js
...
c2.5 Update item: // update an item
server.put("/items/:id", (req, res) => {
[{"title":"Muse++","artist":"Madness++","album":"The 3rd
Law","id":"0c4IEciLCDdXEhhKxj4ThA"},{"id":"2QAHN4C4M8D8E8eiQvQW6a","artist":"One Republic","title":"I
Lived","album":"Native"}]
...
// delete item from list
server.delete("/items/:id", (req, res) => {
Inside DELETE /items route const { id } = req.params;
const itemId = id; ES6 object destructuring
1. Obtain itemId from
req.params using ES6 object console.log("Delete item with id: ", itemId);
destructuring
// filter list copy, by excluding item to delete
2. Delete item from list** const filtered_list = data.list.filter(item => item.id !== itemId);
3. Return list
// replace old list with new one
** many ways to implement this data.list = filtered_list;
res.json(data.list);
});
Testing
$ curl -X DELETE http://localhost:4000/items/0c4IEciLCDdXEhhKxj4ThA
[{"id":"2QAHN4C4M8D8E8eiQvQW6a","artist":"One Republic","title":"I Lived","album":"Native"}]
https://github.com/<username>/node_workshop/tree/m1
https://github.com/<username>/node_workshop/compare/c2.6...m1
Questions
Recap
What can you do with this knowledge?
- CRUD pages for an information system in memory (no DB)
Next:
Data doesn’t persist yet. When server restart/dies, our changes are lost 😢
Coming up: Persisting data into database
3 Connecting to a database:
MongoDB
// a document record sample in Mongodb mLab
{
"_id": {
"$oid": "5bafebc0c5fcaf59fed82980"
noSQL databases },
"id": "5VnDkUNyX6u5Sk0yZiP8XB",
"title": "Thunder",
"artist": "Imagine Dragons",
"album": "Evolve"
}
database node_workshop_db
collections items: [{}, {}, {}]
documents {id: 1234, title: “my song”, ...}
mLab
mLab is the quickest way to setup a MongoDB instance
Free up to 0.5 GB
c3.1 Setting up mLab and MongoDB
For this workshop:
● We’ll use my mLab db instance, with an initial user and some data
I’ll send the credentials through Slack
● Install mongodb driver to our project
https://docs.mlab.com/
Setup and testing of mLab database
1. Login to mLab
2. Click database: node_workshop_db
3. click collection:
a. get connection_url here
b. get items
4. CRUD documents in the
collection (as shown in the screenshot)
Optional: local MongoDB setup
** This only works if you installed mongodb in your local machine as defined in
https://docs.mongodb.com/manual/administration/install-community/
> db.items.find()
# should see fake song we just added
Optional: Testing local DB connection
** This only works if you installed mongodb in your local machine as defined in
https://docs.mongodb.com/manual/administration/install-community/
# Linux
$ mongo <mongodb_connection_link>
# macOS
$ mongo --host <mongodb_connection_link>
# Windows
$ "C:\Program Files\MongoDB\Server\4.0\bin\mongo.exe"
> db.items.find()
# should see some JSON-like objects here with song details
Async Programming with Promises
We’ve been doing it the whole time, using callbacks required by express methods.
// routes/crud.js // server.js
... ...
server.put("/items/:id", function (req, res) {...}); server.listen(port, () => {...});
Callbacks
Sender sends a
function along with
request, which the
service will “call back”
at a later time to give
what was requested
Callbacks } else {
}
successCallback(data);
//ES5
fetchData( function(result){ // successCallback
doSomething(result);
}, function(error) { // failureCallback
throw error;
});
// ES6
fetchData( result => { // successCallback
doSomething(result);
}, error => { // failureCallback
throw error;
});
c3.2 Mongo connection initDb using Callback *** If using local mongodb,
// mongo_db.js use the localhost version
of db_connection_url
... mLab CREDENTIALS: get from Slack #node_snippets
// CALLBACK VERSION
function initDb(dbCollectionName, successCallback, failureCallback) {
const dbConnectionUrl = `mongodb://${dbUser}:${dbPass}@${dbHost}/${dbName}`;
MongoClient.connect(dbConnectionUrl, function (err, dbInstance) {
if (err) {
console.log(`[MongoDB connection] ERROR: ${err}`);
failureCallback(err); // this should be "caught" by the calling function
} else {
const dbObject = dbInstance.db(dbName);
const dbCollection = dbObject.collection(dbCollectionName);
console.log("[MongoDB connection] SUCCESS");
successCallback(dbCollection);
}
});
}
module.exports = { initDb };
c3.2 Server code for mongodb, using Callback and ES5
// server.js
// routes/db_crud.js ...
const express = require("express"); const db_crud = require('./routes/db_crud');
const server = express.Router(); ...
const mongo_db = require('../mongo_db'); // server.use("/", crud); // COMMENT Regular CRUD FOR NOW
// db crud
// mongoDB connection server.use("/", db_crud);
const collectionName = 'items'; ...
// CALLBACK VERSION
mongo_db.initDb(collectionName, function (dbCollection) { // successCallback
server.get("/items", function (req, res) {
dbCollection.find().toArray(function (err, result) {
if (err) throw err;
res.json(result);
})
});
}, function (err) { // failureCallback
throw (err);
});
module.exports = server;
Promises
Service sends a
Promise that later
resolves to the data
requested
// ES6 syntax
fetchData().then(result => {
doSomething(result);
}).catch(error => {
throw error;
});
c3.3 initDb using Promise and ES6 (we’re using this)
// mongo_db.js
...
// PROMISE VERSION
const initDb2 = (dbCollectionName) => {
const dbConnectionUrl = `mongodb://${dbUser}:${dbPass}@${dbHost}/${dbName}`;
// function returns a promise that resolves to a mongodb instance
return new Promise((resolve, reject) => {
MongoClient.connect(dbConnectionUrl, (err, dbInstance) => {
if (err) {
console.log(`[MongoDB connection] ERROR: ${err}`);
reject(err); // this should be "caught" by the calling function
} else {
const dbObject = dbInstance.db(dbName);
const dbCollection = dbObject.collection(dbCollectionName);
console.log("[MongoDB connection] SUCCESS");
resolve(dbCollection);
}
});
});
}
module.exports = { initDb, initDb2 };
c3.3 Server code for mongodb, using Callback
// routes/db_crud.js
...
// COMMENT OUT CALLBACK VERSION
// PROMISE VERSION
mongo_db.initDb2(collectionName).then(dbCollection => {
// db-based CRUD RESTful API routes
// get all items
server.get("/items", (req, res) => {
dbCollection.find().toArray((err, result) => {
if (err) throw err;
res.json(result);
});
});
}).catch(err => {
throw (err);
});
module.exports = server;
MongoDB operations
Operati Method URL mongoDB method Examples:
on
obj
Create POST /items collection.insertOne(obj, callback) {
id: "blah20",
artist: "Artist",
title: "Title",
Read GET /items/:id collection.findOne( query, callback)
album: "Album"
one }
[{...}, {...}
{"_id":"5c74c2ccdd14b6c6ca47ca2f","id":"abcd123456","artist":"meee and youuu","title":"Soong
v2","album":"Hey World"}]
$ curl http://localhost:4000/items/
TESTING
- App should work exactly as before, but
changes are persisted now in the mLab DB
CHECKPOINT:
Questions
Recap
What can you do with this knowledge?
- CRUD system with a DB
- For reference of all mongoDb methods:
- https://docs.mongodb.com/manual/reference/method/
Next:
REST API
m2 (Milestone 2)
Connecting m1 to mongodb
https://github.com/<username>/node_workshop/tree/m2
https://github.com/<username>/node_workshop/compare/c3.7...m2
We have been doing a RESTful API that a client (browser, cURL) can send
requests to
Now, our server will be the “client” and send requests to an external service
e.g. searching a song using Spotify API, fetching memes from Giphy
How our server communicates with an external API is exactly the same as how
our client communicates with our server, which is through HTTP requests,
usually GET and POST 😊
Client <-> Server
Client <-> Server <-> external API
They have APIs for everything nowadays!
Sample commands:
$ curl https://jsonplaceholder.typicode.com/posts/1
$ curl https://jsonplaceholder.typicode.com/posts
$ curl -X POST -H "Content-Type: application/json" --data '{"title": "foo", "body": "bar",
"userId": 1}' https://jsonplaceholder.typicode.com/posts/
$ curl -X PUT -H "Content-Type: application/json" --data '{"title": "foo", "body": "bar",
"userId": 2}' https://jsonplaceholder.typicode.com/posts/1
$ curl -X DELETE https://jsonplaceholder.typicode.com/posts/1
1. Data manipulation: post-processing, filtering, whatever you want to do with the data before sending it
back to the client (some of these you can still do in frontend)
2. Allows you to add some req.param, req.query options that the orig. API doesn’t provide
a. e.g.?description=javascript&location=montreal&titleContains=web&year=2019
3. CORS (Cross-Origin Resource Sharing)
a. If you’re writing frontend code (JS), a lot of these external APIs won’t allow you to communicate
directly (frontend JS <-> API).
b. So, instead they require you to have a server who would act as a proxy/middleman
(frontend JS <-> server <-> API)
c4.3 Manipulating results based on user’s query
// routes/api1.js
...
//?description=javascript&location=montreal&titleContains=developer&year=2019
api1.get("/jobs2", function (req, res) {
const { description, location, titleContains, year } = req.query;
let requestUrl = "https://jobs.github.com/positions.json";
if (description || location) {
const descriptionParam = description ? `description=${description}&` : '';
const locationParam = location ? `location=${location}&` : '';
requestUrl = `${requestUrl}?${descriptionParam}${locationParam}`;
}
axios(requestUrl).then(result => {
// post-processing of results
const results = result.data;
const matches = results.filter(r => {
const { title, created_at } = r;
const titleParam = titleContains ? titleContains.toLowerCase() : '';
const yearParam = year || '';
return title.toLowerCase().includes(titleParam) && created_at.includes(yearParam);
});
res.json(matches);
})...
Testing: localhost:4000/api1/jobs?location=montreal&description=javascript&year=2019&titleContains=developer
Using APIs that requires an API key
we will use: omdbapi.com, Giphy API
1. Login to https://developers.giphy.com/
2. Create an app
3. Get API key
c4.4 adding new Router() and using API key
// routes/api2.js
...
const OMDB_API_KEY = <GET_YOUR_KEY>;
const GIPHY_API_KEY = <GET_YOUR_KEY>;
// /movies?search=black+panther
api2.get("/movies", (req, res) => {
const search = req.query.search;
axios(`http://www.omdbapi.com/?t=${search}&apikey=${OMDB_API_KEY}`)
.then(result => { res.json(result.data); // server.js
... ...
const api2 = require('./routes/api2');
api2.get("/memes", (req, res) => {
...
const { search, limit } = req.query;
server.use('/api2', api2);
axios(`http://api.giphy.com/v1/gifs/search?q=${search}&api_key=${GI
PHY_API_KEY}&limit=${limit}`)
.then(result => { res.json(result.data);
...
TESTING: localhost:4000/api2/movies?search=black+panther
localhost:4000/api2/memes?search=spongebob&limit=5
Using APIs implementing OAuth
1. For simplicity, we will use the free Spotify developer account I setup before
a. You could also setup your own developer account
https://developer.spotify.com/dashboard
routes/api3.js
Define getAccessToken() which returns a Promise
● Prepare request config containing the base-64 encoded credentials in the header
● Send request and resolve Promise with access_token returned by Spotify
Setup route ‘/songs’
● Define .then(), send access_token for now to test
see code change at: https://github.com/<username>/node_workshop/compare/c4.4...c4.5
c4.6 GET request to search query string
We could now use the access_token to make a request
// crud/api3.js
api3.get("/songs", (req, res) => {
const search = req.query.search;
console.log(`[SPOTIFY] : searching ${search}...`);
getAccessToken().then( access_token => {
const _url =
`https://api.spotify.com/v1/search?query=${search}&type=track`;
axios({
method: 'GET',
url: _url,
headers: {
"Authorization": `Bearer ${access_token}`,
"Accept": "application/json"
}
}).then( result => {
console.log(`search response: ${JSON.stringify(result.data)}`);
res.send(result.data.tracks.items);
TOO MUCH DATA
... THOUGH! 😵😵
c4.7 “massaging” API’s response data using .map()
api3.get("/songs", (req, res) => {
So far, this is the item format we have ...
been using: ).then(function (result) {
// "massage" data so we only get the attributes we need
const search_results = result.data.tracks.items;
The object returned by Spotify is too const squashed_results = search_results.map( track => {
👍👍
Testing:
Test all API endpoints to query jobs, songs, movies, memes
http://localhost:4000/api1/users/1
http://localhost:4000/api1/jobs?location=montreal&description=javascript
http://localhost:4000/api1/jobs?location=montreal&description=javascript&year=2019&t
itleContains=developer
http://localhost:4000/api2/movies?search=black+panther
http://localhost:4000/api2/memes?search=spongebob&limit=5
http://localhost:4000/songs?search=bohemian+rhapsody
https://github.com/<username>/node_workshop/compare/c4.7...m3
- add a Find page that includes a form for searching Spotify tracks
- execute a POST /playlist/search route that uses the req.body
- provide a UI to load Spotify search results, and allow adding to Playlist
- refactor track searching into a Promise, such that both /songs and
/playlist/search routes call the same function to search
BONUS m4: styling your template pages
https://github.com/<username>/node_workshop/compare/m3...m4
Questions
Recap
What can you do with this knowledge?
- CRUD pages, persisted in DB, with API integration
Questions?
Need help?
● Allows for “truly RESTful” or stateless auth, since no need to keep session
data in server
● JWT (JSON Web Token) is a signed piece of data, sent along with every
request
● It is used by the receipient to verify the sender’s authenticity
Code changes:
https://github.com/<username>/node_workshop/compare/a2.2...a2.3
more about authentication...
Some of the more advanced topics include: