From 32f9ead5ebae09d2f8935bc35a42b24b82c0422d Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 15:17:54 -0500 Subject: [PATCH 01/18] cleared week directories --- week1/index.js | 1 - week1/package.json | 20 --- week1/readme.md | 156 ------------------ week1/src/config/index.js | 6 - week1/src/models/index.js | 0 week1/src/routes/index.js | 0 week1/src/server.js | 18 --- week2/index.js | 1 - week2/package.json | 20 --- week2/public/index.html | 51 ------ week2/readme.md | 150 ----------------- week2/src/config/index.js | 6 - week2/src/models/index.js | 0 week2/src/routes/index.js | 0 week2/src/server.js | 21 --- week2/src/tester/index.html | 10 -- week3/index.js | 1 - week3/package.json | 20 --- week3/public/index.html | 51 ------ week3/readme.md | 181 --------------------- week3/src/config/index.js | 6 - week3/src/models/index.js | 0 week3/src/routes/index.js | 45 ------ week3/src/server.js | 16 -- week3/src/tester/index.html | 10 -- week4/index.js | 1 - week4/package.json | 21 --- week4/public/index.html | 39 ----- week4/public/js/app.js | 26 --- week4/readme.md | 155 ------------------ week4/src/config/index.js | 6 - week4/src/models/index.js | 0 week4/src/routes/index.js | 58 ------- week4/src/server.js | 18 --- week4/src/tester/index.html | 10 -- week5/index.js | 1 - week5/mongoose_diag.png | Bin 30225 -> 0 bytes week5/package.json | 22 --- week5/public/index.html | 39 ----- week5/public/js/app.js | 26 --- week5/readme.md | 191 ---------------------- week5/src/config/index.js | 10 -- week5/src/models/file.model.js | 29 ---- week5/src/models/file.seed.json | 5 - week5/src/models/index.js | 0 week5/src/routes/index.js | 66 -------- week5/src/server.js | 27 ---- week5/src/tester/index.html | 10 -- week6/index.js | 1 - week6/mongoose_diag.png | Bin 30225 -> 0 bytes week6/package.json | 22 --- week6/public/index.html | 56 ------- week6/public/js/app.js | 67 -------- week6/readme.md | 207 ------------------------ week6/src/config/index.js | 10 -- week6/src/models/file.model.js | 29 ---- week6/src/models/file.seed.json | 5 - week6/src/models/index.js | 0 week6/src/routes/index.js | 74 --------- week6/src/server.js | 27 ---- week6/src/tester/index.html | 10 -- week7/index.js | 1 - week7/mongoose_diag.png | Bin 30225 -> 0 bytes week7/package.json | 22 --- week7/public/index.html | 61 ------- week7/public/js/app.js | 104 ------------ week7/readme.md | 274 -------------------------------- week7/src/config/index.js | 10 -- week7/src/models/file.model.js | 29 ---- week7/src/models/file.seed.json | 5 - week7/src/models/index.js | 0 week7/src/routes/index.js | 89 ----------- week7/src/server.js | 27 ---- week7/src/tester/index.html | 10 -- week8/index.js | 1 - week8/mongoose_diag.png | Bin 30225 -> 0 bytes week8/package.json | 22 --- week8/public/index.html | 62 -------- week8/public/js/app.js | 122 -------------- week8/readme.md | 147 ----------------- week8/src/config/index.js | 10 -- week8/src/models/file.model.js | 30 ---- week8/src/models/file.seed.json | 5 - week8/src/models/index.js | 0 week8/src/routes/index.js | 107 ------------- week8/src/server.js | 27 ---- week8/src/tester/index.html | 10 -- z-deploy/README.md | 144 ----------------- 88 files changed, 3377 deletions(-) delete mode 100644 week1/index.js delete mode 100644 week1/package.json delete mode 100644 week1/readme.md delete mode 100644 week1/src/config/index.js delete mode 100644 week1/src/models/index.js delete mode 100644 week1/src/routes/index.js delete mode 100644 week1/src/server.js delete mode 100644 week2/index.js delete mode 100644 week2/package.json delete mode 100644 week2/public/index.html delete mode 100644 week2/readme.md delete mode 100644 week2/src/config/index.js delete mode 100644 week2/src/models/index.js delete mode 100644 week2/src/routes/index.js delete mode 100644 week2/src/server.js delete mode 100644 week2/src/tester/index.html delete mode 100644 week3/index.js delete mode 100644 week3/package.json delete mode 100644 week3/public/index.html delete mode 100644 week3/readme.md delete mode 100644 week3/src/config/index.js delete mode 100644 week3/src/models/index.js delete mode 100644 week3/src/routes/index.js delete mode 100644 week3/src/server.js delete mode 100644 week3/src/tester/index.html delete mode 100644 week4/index.js delete mode 100644 week4/package.json delete mode 100644 week4/public/index.html delete mode 100644 week4/public/js/app.js delete mode 100644 week4/readme.md delete mode 100644 week4/src/config/index.js delete mode 100644 week4/src/models/index.js delete mode 100644 week4/src/routes/index.js delete mode 100644 week4/src/server.js delete mode 100644 week4/src/tester/index.html delete mode 100644 week5/index.js delete mode 100644 week5/mongoose_diag.png delete mode 100644 week5/package.json delete mode 100644 week5/public/index.html delete mode 100644 week5/public/js/app.js delete mode 100644 week5/readme.md delete mode 100644 week5/src/config/index.js delete mode 100644 week5/src/models/file.model.js delete mode 100644 week5/src/models/file.seed.json delete mode 100644 week5/src/models/index.js delete mode 100644 week5/src/routes/index.js delete mode 100644 week5/src/server.js delete mode 100644 week5/src/tester/index.html delete mode 100644 week6/index.js delete mode 100644 week6/mongoose_diag.png delete mode 100644 week6/package.json delete mode 100644 week6/public/index.html delete mode 100644 week6/public/js/app.js delete mode 100644 week6/readme.md delete mode 100644 week6/src/config/index.js delete mode 100644 week6/src/models/file.model.js delete mode 100644 week6/src/models/file.seed.json delete mode 100644 week6/src/models/index.js delete mode 100644 week6/src/routes/index.js delete mode 100644 week6/src/server.js delete mode 100644 week6/src/tester/index.html delete mode 100644 week7/index.js delete mode 100644 week7/mongoose_diag.png delete mode 100644 week7/package.json delete mode 100644 week7/public/index.html delete mode 100644 week7/public/js/app.js delete mode 100644 week7/readme.md delete mode 100644 week7/src/config/index.js delete mode 100644 week7/src/models/file.model.js delete mode 100644 week7/src/models/file.seed.json delete mode 100644 week7/src/models/index.js delete mode 100644 week7/src/routes/index.js delete mode 100644 week7/src/server.js delete mode 100644 week7/src/tester/index.html delete mode 100644 week8/index.js delete mode 100644 week8/mongoose_diag.png delete mode 100644 week8/package.json delete mode 100644 week8/public/index.html delete mode 100644 week8/public/js/app.js delete mode 100644 week8/readme.md delete mode 100644 week8/src/config/index.js delete mode 100644 week8/src/models/file.model.js delete mode 100644 week8/src/models/file.seed.json delete mode 100644 week8/src/models/index.js delete mode 100644 week8/src/routes/index.js delete mode 100644 week8/src/server.js delete mode 100644 week8/src/tester/index.html delete mode 100644 z-deploy/README.md diff --git a/week1/index.js b/week1/index.js deleted file mode 100644 index 440ef30..0000000 --- a/week1/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./src/server'); diff --git a/week1/package.json b/week1/package.json deleted file mode 100644 index bac8df4..0000000 --- a/week1/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "cl_project", - "version": "1.0.0", - "description": "This is a sample node project", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [ - "code", - "louisville", - "sample", - "project" - ], - "author": "Aaron W. Johnson", - "license": "ISC", - "dependencies": { - "express": "^4.14.0" - } -} diff --git a/week1/readme.md b/week1/readme.md deleted file mode 100644 index 1f648b3..0000000 --- a/week1/readme.md +++ /dev/null @@ -1,156 +0,0 @@ -# FSJS Week 1 - Our Glorious Node Project - -**Outline** - -* Install NodeJS -* Set up the project -* npm init -* Install core packages -* Organize the project -* "Hello World" web server - -## Install NodeJS - -- [Windows (http://blog.teamtreehouse.com/install-node-js-npm-windows)](http://blog.teamtreehouse.com/install-node-js-npm-windows) -- [Mac (http://blog.teamtreehouse.com/install-node-js-npm-mac)](http://blog.teamtreehouse.com/install-node-js-npm-mac) -- [Linux (http://blog.teamtreehouse.com/install-node-js-npm-linux)](http://blog.teamtreehouse.com/install-node-js-npm-linux) - -## Set up the project -1. Clone the project -``` -git clone https://github.com/CodeLouisville/FSJS-class-project.git -cd FSJS-class-project -``` - -2. Get rid of `week1` (we're going to rebuild it) -``` -rm -rf week1 -``` - -## Start a project with `npm init` - -Starting a project in node is simple: -``` -mkdir week1 -cd week1 -npm init -``` - -`npm init` simply creates a `package.json` file a populates it with the answers to some questions. You can edit it in a text editor. - - -## Install code packages - -First, take a look at `package.json`, then run this command: -``` -npm install express --save -``` - -Now, go back to `package.json` and look at the 'dependencies' section. -Also, a new directory has appeared: `node_modules` - -The above command tells `npm` to download the [express](https://expressjs.com/) package, save it in a newly created `node_modules` directory, and then add a line in `package.json` to make note of the fact that we need 'express' for this project (that's what the `--save` part does). - - -## Organize this thing - -When starting a project, a good practice is to lay out your directory structure and create some empty, basic files: -``` -. -├── package.json -└── src - ├── config // application configuration - │   └── index.js - ├── models // Database models - │   └── index.js - ├── routes // HTTP(S) routing/controllers - │   └── index.js - └── server.js // Set up server and listen on port -``` - -## Hello World - -Open `src/config/index.js` -```javascript -// src/config/index.js - -module.exports = { - appName: 'Our Glorious Node Project', - port: 3030 -} -``` - -Open `src/server.js` -Pull in some needed modules -```javascript -// src/server.js - -const express = require('express'); -const config = require('./config'); -``` - -Create our application object -```javascript -const app = express(); -``` - -Tell it what to do -```javascript -app.use(function(req, res, next) { - res.end("Hello World!"); -}); -``` - -Start the server -```javascript -app.listen(config.port, function() { - console.log(`${config.appName} is listening on port ${config.port}`); -}); -``` - -On the command line: -``` -node src/server.js -``` - - -Test it out by going to `http://localhost:3030` in your browser. - -Now, mix it up a bit. Put this code **BEFORE** the other `app.use` bit. -```javascript -app.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); -``` - -Visit `http://localhost:3030/doc` -Move the `/doc` route below the original `app.use` code and refresh. What happened? - -## Bonus material - -### require() is a big deal -Yes it is. The full documentation for require() (really, for Node modules in general) can be found [here (https://nodejs.org/api/modules.html)](https://nodejs.org/api/modules.html). - -`require()` is what allows you to organize your code in to easy-to-understand (hopefully) directories and files, but join them all together in to a single application. - -For comparison: in a browser environment, if you want to make content from multiple file available to the larger application you can 1) concatenate them all in to one file or 2) load them individually via a ` - - - - - - - diff --git a/week2/readme.md b/week2/readme.md deleted file mode 100644 index facbaf3..0000000 --- a/week2/readme.md +++ /dev/null @@ -1,150 +0,0 @@ -# FSJS Week 2 - Our Superlative Web Page - -**Outline** - -* Set up the project for the front end -* Serve a static page -* Add a template engine - -## Set up the project -_It should still be setup from [week 1](../week1)_ - -1. Clean the project -_If you did something you want to keep, last week, you can make a branch and commit, or copy those files out. To continue here, we are going to reset `week1` back to our code_ -``` -cd FSJS-class-project -git status -git reset --hard HEAD -git pull -``` - -2. Get rid of `week2` _(we're going to rebuild it)_ -``` -rm -rf week2 -``` - -3. Copy `week1` to `week2` _(this is our starting point)_ -``` -cp -R week1 week2 -cd week2 -``` - -4. Install dependencies -``` -npm install -``` - -## Serve a static page -1. Create a "public" directory inside the `src` directory -``` -mkdir public -``` - -2. Set up our express application to serve static files. -Add a reference to Node's `path` module to the top of the page in the `server.js` -```javascript -const path = require('path'); -``` -[[Documentation for path](https://nodejs.org/api/path.html)] - -Then add the following line to `server.js` BEFORE any routes -```javascript -const publicPath = path.resolve(__dirname, './public'); -app.use(express.static(publicPath)); -``` -[[Documentation for Node Modules (dirname)](https://nodejs.org/api/modules.html)] -[[Guide for ExpresJS static](https://expressjs.com/en/starter/static-files.html)] - -`express.static()` will search the `public` directory for a file that matches the requested path. For example: `index.html`, `img/puppy.jpg`, etc. If there is a match, that file is streamed back to the requester, otherwise, express moves on to the next route. - -3. Add an `index.html` to the public folder. -```html - - - - - Our Glorious Node Project - - -

A wild webpage appears...

- - - -``` - -4. Start the server and check that you can access a static `html` page - -Note: We previously had a "Hello World" endpoint that was served when user's requested the path `/`. That path is now unreachable, because all requests for `/` will receive `index.html`. - -`/doc` still works, though. - - -## Add a template engine -1. We'll be using jQuery and Handlebars, so let's add them to our `index.html` at the end of the `` tag -```html - - -``` -[[Documentation for jQuery](https://api.jquery.com/)] -[[Documentation for Handlebars](http://handlebarsjs.com/reference.html)] - -2. Drop a quick template in `index.html` to see how handlebars renders content: -```html - -``` - -3. Render a list of fake data. Start by adding a place in the html to render the list after the `

`: -```html -
-``` - -4. Create some fake data in another script tag -```html - -``` - -5. Create a template for each list item. -```html - -``` - -6. Right below the `list` array, compile the template and render it into the container. -```javascript -const template = $('#list-template').html(); -const compiled = Handlebars.compile(template); -$('#list-container').html(compiled(data)); -``` - -7. Refresh the page - -8. Add some style in the `` -```html - -``` diff --git a/week2/src/config/index.js b/week2/src/config/index.js deleted file mode 100644 index 1df2c5f..0000000 --- a/week2/src/config/index.js +++ /dev/null @@ -1,6 +0,0 @@ -// src/config/index.js - -module.exports = { - appName: 'Our Glorious Node Project', - port: 3030 -} diff --git a/week2/src/models/index.js b/week2/src/models/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/week2/src/routes/index.js b/week2/src/routes/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/week2/src/server.js b/week2/src/server.js deleted file mode 100644 index 0db3b73..0000000 --- a/week2/src/server.js +++ /dev/null @@ -1,21 +0,0 @@ -// src/server.js -const path = require('path'); - -const express = require('express'); -const config = require('./config'); - -const app = express(); -const publicPath = path.resolve(__dirname, '../public'); -app.use(express.static(publicPath)); - -app.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); - -app.use(function(req, res, next) { - res.end("Hello World!"); -}); - -app.listen(config.port, function() { - console.log(`${config.appName} is listening on port ${config.port}`); -}); diff --git a/week2/src/tester/index.html b/week2/src/tester/index.html deleted file mode 100644 index 2b263fa..0000000 --- a/week2/src/tester/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - sdfjhs - - - SKDJHFLKSJDHFLKSDJ - - diff --git a/week3/index.js b/week3/index.js deleted file mode 100644 index 440ef30..0000000 --- a/week3/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./src/server'); diff --git a/week3/package.json b/week3/package.json deleted file mode 100644 index 05b7b92..0000000 --- a/week3/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "cl_project", - "version": "1.0.0", - "description": "This is a sample node project", - "main": "index.js", - "scripts": { - "start": "nodemon index.js" - }, - "keywords": [ - "code", - "louisville", - "sample", - "project" - ], - "author": "Aaron W. Johnson", - "license": "ISC", - "dependencies": { - "express": "^4.14.0" - } -} diff --git a/week3/public/index.html b/week3/public/index.html deleted file mode 100644 index 378cfe6..0000000 --- a/week3/public/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - Our Glorious Node Project - - - - -

A wild webpage appears...

-
- - - - - - - - - diff --git a/week3/readme.md b/week3/readme.md deleted file mode 100644 index bc06695..0000000 --- a/week3/readme.md +++ /dev/null @@ -1,181 +0,0 @@ -# FSJS Week 3 - A Wild Router Appears - -**Outline** - -* Set up the project -* Grab some DevTools -* The 5 operation of CRUD (list, detail, create, update, delete) -* Send mock, static data as response - - -## Set up the project -1. (optional) Clone the project -``` -git clone https://github.com/CodeLouisville/FSJS-class-project.git -cd FSJS-class-project -``` - -2. Get rid of `week3` (we're going to rebuild it) -``` -rm -rf week3 -``` - -3. Copy `week2` to `week3` -``` -cp -R week2 week3 -cd week3 -``` - -4. Install dependencies -``` -npm install -``` -## Hooray! New DevTools! - -### Postman - -Postman allows you to send HTTP requests to your API. You can tailor the url, method, payload, querystring, and headers. This is a very powerful API testing tool which will make our development easier. - -[[Download here](https://www.getpostman.com/docs/postman/launching_postman/installation_and_updates)] - -### Nodemon - -Tired of restarting your Node server every time you change a file? Hand getting a cramp from hitting `ctrl-c` after every typo fix? **You're in luck** - -Enter: Nodemon - Monitor for any changes in your node.js application and automatically restart the server - perfect for development. - -``` -npm install nodemon -g -``` - -Here, we're NOT using the `--save` switch, but we are using the mysterious `-g`. `-g` tells npm to install Nodemon globally (so it will be available for all your projects). Therefore, we don't need to save it to our `package.json` file. - -Next, add a script to `package.json`. Find the `scripts` section and replace it with the following: -```javascript -"scripts": { - "start": "nodemon index.js" -}, -``` -[[Documentation on NPM scripts](https://docs.npmjs.com/misc/scripts)] - -To start our server, type: -``` -npm start -``` - -Now, when we make changes to files, the server restarts automatically. Try it out! - -## The 5 Operations of CRUD - -| Operation | Suggested HTTP | Data | -| --- | --- | --- | -| Create | POST | Create a new element | -| Read | GET | Get a single element | -| Update | PUT | Replace an element with new data | -| Delete | DELETE | Delete a single element | -| List | GET | Get an array of elements | - -1. Do a little clean-up by moving our endpoints to a separate file. Add the following to `routes/index.js` -```javascript -// src/routes/index.js -const router = require('express').Router(); -``` -[[ExpressJS Router documentation](https://expressjs.com/en/4x/api.html#router)] - -A router object allows us to create an isolated bundle of endpoints and middleware. This is not new functionality, just a convenient way to package code in to separate, easy-to-read and easy-to-maintain files. - -Now, move our existing routes over from `server.js`: -```javascript -router.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); - -module.exports = router; -``` -(Note that our "Hello world" route is unnecessary since we are serving a static index.html) - -Now, head back to `server.js` and make sure our app knows how to use the router. Create a variable for our router at the top of the file: -```javascript -const router = require('./routes'); -``` -And then direct our app to use the router AFTER the line where we handle static files: -```javascript -app.use(express.static(publicPath)); -app.use('/api', router); -``` - -What does the `'/api'` part do? [`app.use()` Documentation here](https://expressjs.com/en/4x/api.html#app.use). Basically, this prepends `/api` to all the paths defined in `router` (currently, we only have `/doc`). So, instead of making a GET request to `/doc`, we will now make a request to `/api/doc`. - -**Fire up postman and try it** - -2. Add some basic **List** and **Create** handlers to `routes\index.js`: -```javascript -router.get('/file', function(req, res, next) { - res.end('List all files'); -}); - -router.post('/file', function(req, res, next) { - res.end('Create a new file'); -}); -``` -[[Documentation for router.get() and router.post()](https://expressjs.com/en/4x/api.html#router.METHOD)] - -Head over to postman and test it out. - -3. Add **Update**, **Delete**, and **Read** endpoints - all of which take a route parameter: -```javascript -router.put('/file/:fileId', function(req, res, next) { - res.end(`Updating file '${req.params.fileId}'`); -}); - -router.delete('/file/:fileId', function(req, res, next) { - res.end(`Deleting file '${req.params.fileId}'`); -}); - -router.get('/file/:fileId', function(req, res, next) { - res.end(`Reading file '${req.params.fileId}'`); -}); -``` -[[Documentation for Route Parameters](https://expressjs.com/en/guide/routing.html#route-parameters)] - -Route parameters allow you to pass information to the router via the url itself. When express finds a route parameter (indicated by `:`), it creates a property on `req.params` with the same name. - -## Return some data - -1. Let's add a static array of "file" object for testing purposes. Near the top of the `routes/index.js` file, add the following: -```javascript -const FILES = [ - {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, - {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, - {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, - {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, -]; -``` - -2. Return the entire list as JSON. Replace the handler for `GET /file`, with: -```javascript -router.get('/file', function(req, res, next) { - res.json(FILES); -}); -``` -[[Documentation for res.json()](https://expressjs.com/en/4x/api.html#res.json)] - -`res.json()` accepts any type of data, stringifies with `JSON.stringify()` and sends the response with the header `Content-Type: application/json`. - -3. Return a single element by replacing the handler for `GET /file/:fileId` with: -```javascript -router.get('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - // same as 'const fileId = req.params.fileId' - - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - res.json(file); -}); -``` -[[Documentation for object destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring)] -[[Documentation for Array.prototype.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find?v=example)] -[[Documentation for res.status()](https://expressjs.com/en/4x/api.html#res.status)] diff --git a/week3/src/config/index.js b/week3/src/config/index.js deleted file mode 100644 index 1df2c5f..0000000 --- a/week3/src/config/index.js +++ /dev/null @@ -1,6 +0,0 @@ -// src/config/index.js - -module.exports = { - appName: 'Our Glorious Node Project', - port: 3030 -} diff --git a/week3/src/models/index.js b/week3/src/models/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/week3/src/routes/index.js b/week3/src/routes/index.js deleted file mode 100644 index 84d461c..0000000 --- a/week3/src/routes/index.js +++ /dev/null @@ -1,45 +0,0 @@ -// src/routes/index.js -const router = require('express').Router(); - -const FILES = [ - {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, - {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, - {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, - {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, -]; - - -router.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); - -router.get('/file', function(req, res, next) { - res.json(FILES); -}); - - -router.post('/file', function(req, res, next) { - res.end('Create a new file'); -}); - -router.put('/file/:fileId', function(req, res, next) { - res.end(`Updating file '${req.params.fileId}'`); -}); - -router.delete('/file/:fileId', function(req, res, next) { - res.end(`Deleting file '${req.params.fileId}'`); -}); - -router.get('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - // same as 'const fileId = req.params.fileId' - - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - res.json(file); -}); - -module.exports = router; diff --git a/week3/src/server.js b/week3/src/server.js deleted file mode 100644 index 032d8a4..0000000 --- a/week3/src/server.js +++ /dev/null @@ -1,16 +0,0 @@ -// src/server.js -const path = require('path'); - -const express = require('express'); -const config = require('./config'); -const router = require('./routes'); - -const app = express(); -const publicPath = path.resolve(__dirname, '../public'); -app.use(express.static(publicPath)); -app.use('/api', router); - - -app.listen(config.port, function() { - console.log(`${config.appName} is listening on port ${config.port}`); -}); diff --git a/week3/src/tester/index.html b/week3/src/tester/index.html deleted file mode 100644 index 2b263fa..0000000 --- a/week3/src/tester/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - sdfjhs - - - SKDJHFLKSJDHFLKSDJ - - diff --git a/week4/index.js b/week4/index.js deleted file mode 100644 index 440ef30..0000000 --- a/week4/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./src/server'); diff --git a/week4/package.json b/week4/package.json deleted file mode 100644 index c943a5c..0000000 --- a/week4/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "cl_project", - "version": "1.0.0", - "description": "This is a sample node project", - "main": "index.js", - "scripts": { - "start": "nodemon index.js" - }, - "keywords": [ - "code", - "louisville", - "sample", - "project" - ], - "author": "Aaron W. Johnson", - "license": "ISC", - "dependencies": { - "body-parser": "^1.17.2", - "express": "^4.14.0" - } -} diff --git a/week4/public/index.html b/week4/public/index.html deleted file mode 100644 index c45aa5a..0000000 --- a/week4/public/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - Our Glorious Node Project - - - - -

A wild webpage appears...

-
- - - - - - - - - diff --git a/week4/public/js/app.js b/week4/public/js/app.js deleted file mode 100644 index 6e02ca1..0000000 --- a/week4/public/js/app.js +++ /dev/null @@ -1,26 +0,0 @@ - - -function getFiles() { - return $.ajax('/api/file') - .then(res => { - console.log("Results from getFiles()", res); - return res; - }) - .fail(err => { - console.log("Error in getFiles()", err); - throw err; - }); -} - - -function refreshFileList() { - const template = $('#list-template').html(); - const compiledTemplate = Handlebars.compile(template); - - getFiles() - .then(files => { - const data = {files: files}; - const html = compiledTemplate(data); - $('#list-container').html(html); - }) -} diff --git a/week4/readme.md b/week4/readme.md deleted file mode 100644 index 454ca97..0000000 --- a/week4/readme.md +++ /dev/null @@ -1,155 +0,0 @@ -# FSJS Week 4 - The Full Stackey - -**Outline** - -* Set up the project -* Render data from the server on our page -* Add POST and PUT endpoints - - -## Set up the project -1. (optional) Clone the project -``` -git clone https://github.com/CodeLouisville/FSJS-class-project.git -cd FSJS-class-project -``` - -**OR** if you need to ditch your current changes and pull a fresh copy down, try this: -``` -git stash -git pull -``` - -2. Get rid of `week4` (we're going to rebuild it) -``` -rm -rf week4 -``` - -3. Copy `week3` to `week4` -``` -cp -R week3 week4 -cd week4 -``` - -4. Install dependencies -``` -npm install -``` - -## Render data from the server -We have a nice API endpoint to spit out data (albeit static data from an array). Let's use that in our front-end. - -1. Create a new file in a new directory: `public\js\app.js`. -(We could continue to put all our javascript in `script` tags in `index.html`, but placing this code in a separate file will help keep things neat and organized) - -2. Load this in to our `index.html`. At the bottom of the file, just below the `script` tag that loads the handlebars library, add: -```html - -``` - -3. Update our handlebars template to render a file and not a BTVS character list. Replace the `#list-template` script in `index.html` with the following: -```html - -``` -While we're at it, we can let's delete the code that rendered our previous test data. (If you want to keep it around for reference, just comment it out.) - -4. In `app.js`, create a function to get the file list: -```javascript -function getFiles() { - return $.ajax('/api/file') - .then(res => { - console.log("Results from getFiles()", res); - return res; - }) - .fail(err => { - console.log("Error in getFiles()", err); - throw err; - }); -} -``` - -5. Create a function to refresh the list -```javascript -function refreshFileList() { - const template = $('#list-template').html(); - const compiledTemplate = Handlebars.compile(template); - - getFiles() - .then(files => { - const data = {files: files}; - const html = compiledTemplate(data); - $('#list-container').html(html); - }) -} -``` -Test it out by refreshing the page, opening a debugging console, and typing `refreshFileList()`; - -6. Refresh the list automatically when the page first loads by adding `refreshFileList()` to the remaining `$().ready()` function in `index.html` - -## Finish What we started -1. We are going to be sending data from the client back to the server. To do that, we will convert a plain JS object to a JSON-formatted string (really, jQuery will do that for us). We need to set up our express server to parse that JSON string and turn it back in to an object. - -Fortunately, there a library for that: -``` -npm install body-parser --save -``` -[[Documentation for body-parser](https://github.com/expressjs/body-parser)] - -`body-parser` will look at the body of a request and, if the `Content-Type` is `application/json`, will parse the body using `JSON.parse()`. The results of that (if successful) will be put in `req.body` for use by any middleware. - -2. At the top of `server.js`, require the body-parser module: -```javascript -const bodyParser = require('body-parser'); -``` - -3. Tell our server to use it. In `server.js`, right AFTER we set up static files serving, add the following: -```javascript -app.use(function(req, res, next) { - console.log("req.body BEFORE parsing", req.body); - next(); -}) - -app.use(bodyParser.json()); - -app.use(function(req, res, next) { - console.log("req.body AFTER parsing", req.body); - next(); -}) -``` -Head over to postman. Create a POST request to ANY endpoint. Tell postman that the content is JSON (use the dropdown). Type in any valid JSON-formatted string and hit send. You'll see the contents outputted by the two middleware we added before and after the bodyParser middleware. - -4. Delete the logging middleware - -5. Go back to our routes and add the POST and PUT endpoints. In `routes/index.js`, swap out the `router.put()` and `router.post()` callbacks (which were just placeholders) with the following: -```javascript -router.post('/file', function(req, res, next) { - const newId = '' + FILES.length; - const data = req.body; - data.id = newId; - - FILES.push(data); - res.status(201).json(data); -}); -``` -and -```javascript -router.put('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - file.title = req.body.title; - file.description = req.body.description; - res.json(file); -}); -``` diff --git a/week4/src/config/index.js b/week4/src/config/index.js deleted file mode 100644 index 1df2c5f..0000000 --- a/week4/src/config/index.js +++ /dev/null @@ -1,6 +0,0 @@ -// src/config/index.js - -module.exports = { - appName: 'Our Glorious Node Project', - port: 3030 -} diff --git a/week4/src/models/index.js b/week4/src/models/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/week4/src/routes/index.js b/week4/src/routes/index.js deleted file mode 100644 index fa8503e..0000000 --- a/week4/src/routes/index.js +++ /dev/null @@ -1,58 +0,0 @@ -// src/routes/index.js -const router = require('express').Router(); - -const FILES = [ - {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, - {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, - {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, - {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, -]; - - -router.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); - -router.get('/file', function(req, res, next) { - res.json(FILES); -}); - - -router.post('/file', function(req, res, next) { - const newId = '' + FILES.length; - const data = req.body; - data.id = newId; - - FILES.push(data); - res.status(201).json(data); -}); - -router.put('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - file.title = req.body.title; - file.description = req.body.description; - res.json(file); -}); - -router.delete('/file/:fileId', function(req, res, next) { - res.end(`Deleting file '${req.params.fileId}'`); -}); - -router.get('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - // same as 'const fileId = req.params.fileId' - - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - res.json(file); -}); - -module.exports = router; diff --git a/week4/src/server.js b/week4/src/server.js deleted file mode 100644 index d7430e5..0000000 --- a/week4/src/server.js +++ /dev/null @@ -1,18 +0,0 @@ -// src/server.js -const path = require('path'); -const bodyParser = require('body-parser'); - -const express = require('express'); -const config = require('./config'); -const router = require('./routes'); - -const app = express(); -const publicPath = path.resolve(__dirname, '../public'); -app.use(express.static(publicPath)); -app.use(bodyParser.json()); -app.use('/api', router); - - -app.listen(config.port, function() { - console.log(`${config.appName} is listening on port ${config.port}`); -}); diff --git a/week4/src/tester/index.html b/week4/src/tester/index.html deleted file mode 100644 index 2b263fa..0000000 --- a/week4/src/tester/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - sdfjhs - - - SKDJHFLKSJDHFLKSDJ - - diff --git a/week5/index.js b/week5/index.js deleted file mode 100644 index 440ef30..0000000 --- a/week5/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./src/server'); diff --git a/week5/mongoose_diag.png b/week5/mongoose_diag.png deleted file mode 100644 index c40ad4c90b293a85e5b2526e60791ec0ba2a813f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30225 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 QdL`1=KC4xsaXIAw05~Q<_W%F@ diff --git a/week5/package.json b/week5/package.json deleted file mode 100644 index 3117c53..0000000 --- a/week5/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "cl_project", - "version": "1.0.0", - "description": "This is a sample node project", - "main": "index.js", - "scripts": { - "start": "nodemon index.js" - }, - "keywords": [ - "code", - "louisville", - "sample", - "project" - ], - "author": "Aaron W. Johnson", - "license": "ISC", - "dependencies": { - "body-parser": "^1.17.2", - "express": "^4.14.0", - "mongoose": "^4.10.4" - } -} diff --git a/week5/public/index.html b/week5/public/index.html deleted file mode 100644 index c45aa5a..0000000 --- a/week5/public/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - Our Glorious Node Project - - - - -

A wild webpage appears...

-
- - - - - - - - - diff --git a/week5/public/js/app.js b/week5/public/js/app.js deleted file mode 100644 index 6e02ca1..0000000 --- a/week5/public/js/app.js +++ /dev/null @@ -1,26 +0,0 @@ - - -function getFiles() { - return $.ajax('/api/file') - .then(res => { - console.log("Results from getFiles()", res); - return res; - }) - .fail(err => { - console.log("Error in getFiles()", err); - throw err; - }); -} - - -function refreshFileList() { - const template = $('#list-template').html(); - const compiledTemplate = Handlebars.compile(template); - - getFiles() - .then(files => { - const data = {files: files}; - const html = compiledTemplate(data); - $('#list-container').html(html); - }) -} diff --git a/week5/readme.md b/week5/readme.md deleted file mode 100644 index 79c38ed..0000000 --- a/week5/readme.md +++ /dev/null @@ -1,191 +0,0 @@ -# FSJS Week 5 - Mongo!!!! - -**Outline** - -2. Install Mongo (Go ahead and start on this) -1. Set up for week5 -3. Create a model and seed it with data -4. Connect Mongo to our application - -## 1. Install Mongo -**Windows**: https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/ - -**Linux** - https://docs.mongodb.com/manual/administration/install-on-linux/ - -**OSX** - https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/ - - -## 2. Setup Project -1. (optional) Clone the project -``` -git clone https://github.com/CodeLouisville/FSJS-class-project.git -cd FSJS-class-project -``` - -**OR** if you need to ditch your current changes and pull a fresh copy down, try this: -``` -git stash -git pull -``` - -2. Get rid of `week5` (we're going to rebuild it) -``` -rm -rf week5 -``` - -3. Copy `week4` to `week5` -``` -cp -R week4 week5 -cd week5 -``` - -4. Install dependencies -``` -npm install -``` - -5. Install mongoose -``` -npm install mongoose --save -``` -**Mongoose Documentation:** http://mongoosejs.com/docs/api.html - -## HOLD THE PHONE... -**What is Mongo? Sounds like a cartoon character's name...** - -Mongo is a database. It is a place to store structured data so that your application can quickly and easily find it later. Mongo is known as a no-SQL database. In the case of Mongo, that means that it stores data in units called `documents` - which look just like javascript objects (key-value pairs, nested objects, arrays, etc.). - -## WAIT A MINUTE.... -**What is this `mongoose` of which you speak?** - -Mongoose is an ORM (Object Relational Mapping) tool. It is used in your application to make the process of querying, inserting, updating, and deleting data in a Mongo database. In addition, it turns the plain ol' javascript objects you get back from Mongo in to more feature-rich objects for your application to use. - -![Mongoose Diagram](mongoose_diag.png) - - -## Create a model using mongoose - -**In a nutshell, we will:** -1. Tell mongoose how to talk to the mongo server -2. Make sure mongoose connects to mongo when your application starts. -3. Create a "model" in mongoose. This is where you define what your data looks like. -4. Use Mongo in our route handlers instead of the array we've been using. -5. Add some test data. - - -### Configure our app to work with mongo -1. Edit our config file (at `src/config/index.js`) so that the returned configuration object includes mongo configuration: - ```javascript - module.exports = { - appName: 'Our Glorious Node Project', - port: 3030, - db: { - host: 'localhost', - dbName: 'fsjs', - } - }; - ``` - -2. Connect to mongo through the mongoose library. In `src/server.js`, somewhere near the top of the file, import mongoose with - ```javascript - // Load mongoose package - const mongoose = require('mongoose'); - ``` - Then, somewhere AFTER the line where you load your configuration, connect with the following - ```javascript - // Connect to MongoDB and create/use database as configured - mongoose.connection.openUri(`mongodb://${config.db.host}/${config.db.dbName}`); - ``` - - -### Build the model - -1. In the `src/models` directory, create an empty file called `file.model.js` -2. At the top of that file, pull in mongoose - ```javascript - // Load mongoose package - const mongoose = require('mongoose'); - ``` - -3. Create a schema - ```javascript - const FileSchema = new mongoose.Schema({ - title: String, - description: String, - created_at: { type: Date, default: Date.now }, - }); - ``` - Notice that the `title` and `description` fields are also present in our faked data (`/src/routes/index.js`). We've also added a new field called `created_at`, which will be a Date and will default to the current time. - -4. Turn that schema in to a mongoose model, register it, and export it - ```javascript - const File = mongoose.model('File', FileSchema); - module.exports = File; - ``` - A lot is going on here. We are storing the `File` schema inside the mongoose object (which will make it available anywhere in your application). We're also giving a name ("File") so we can distinguish it from any other model we may want to register. We're also exporting the model from this module. - -5. Make sure that the `file.model.js` script is run by `require`-ing it somewhere...like in `src/server.js`, below the line where we connect mongoose to mongo: - ```javascript - // Import all models - require('./models/file.model.js'); - ``` - -## Connect to our app -1. In `src/routes/index.js`, pull in mongoose at the top of the file. - ```javascript - const mongoose = require('mongoose'); - ``` - -2. Edit the `GET /file` route. Replace our development code with - ```javascript - mongoose.model('File').find({}, function(err, files) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - - res.json(files); - }); - ``` - **Model.find:** http://mongoosejs.com/docs/api.html#model_Model.find - -3. Restart server and test - **Where did our data go?** - -### What about some test data? -Strategy: On startup, check if there are any files in the database, if not, then add files from a seed file. - -1. Create a file in `/src/models` called `file.seed.json` - ```json - [ - {"title":"Satellite of Love Plans.svg", "description": "Includes fix for exhaust port vulnerability" }, - {"title":"Rules of Cribbage.doc", "description": "9th edition" }, - {"title":"avengers_fanfic.txt", "description": "PRIVATE DO NOT READ" } - ] - ``` - -2. In `file.model.js`, after you create and export the model, get the current count of documents in the collection - ```javascript - File.count({}, function(err, count) { - if (err) { - throw err; - } - // ... - }); - ``` - **Model.count:** http://mongoosejs.com/docs/api.html#model_Model.count - -3. Add the seed data - ```javascript - if (count > 0) return ; - - const files = require('./file.seed.json'); - File.create(files, function(err, newFiles) { - if (err) { - throw err; - } - console.log("DB seeded") - }); - ``` - **Model.create:** http://mongoosejs.com/docs/api.html#model_Model.create diff --git a/week5/src/config/index.js b/week5/src/config/index.js deleted file mode 100644 index 8b6e7d5..0000000 --- a/week5/src/config/index.js +++ /dev/null @@ -1,10 +0,0 @@ -// src/config/index.js - -module.exports = { - appName: 'Our Glorious Node Project', - port: 3030, - db: { - host: 'localhost', - dbName: 'fsjs', - } -}; diff --git a/week5/src/models/file.model.js b/week5/src/models/file.model.js deleted file mode 100644 index 5166098..0000000 --- a/week5/src/models/file.model.js +++ /dev/null @@ -1,29 +0,0 @@ -// Load mongoose package -const mongoose = require('mongoose'); - -const FileSchema = new mongoose.Schema({ - title: String, - description: String, - created_at: { type: Date, default: Date.now }, -}); - - -const File = mongoose.model('File', FileSchema); -module.exports = File; - - -File.count({}, function(err, count) { - if (err) { - throw err; - } - - if (count > 0) return ; - - const files = require('./file.seed.json'); - File.create(files, function(err, newFiles) { - if (err) { - throw err; - } - console.log("DB seeded") - }); -}); diff --git a/week5/src/models/file.seed.json b/week5/src/models/file.seed.json deleted file mode 100644 index 6b401c1..0000000 --- a/week5/src/models/file.seed.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - {"title":"Satellite of Love Plans.svg", "description": "Includes fix for exhaust port vulnerability" }, - {"title":"Rules of Cribbage.doc", "description": "9th edition" }, - {"title":"avengers_fanfic.txt", "description": "PRIVATE DO NOT READ" } -] diff --git a/week5/src/models/index.js b/week5/src/models/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/week5/src/routes/index.js b/week5/src/routes/index.js deleted file mode 100644 index 9e44a08..0000000 --- a/week5/src/routes/index.js +++ /dev/null @@ -1,66 +0,0 @@ -// src/routes/index.js -const router = require('express').Router(); -const mongoose = require('mongoose'); - -const FILES = [ - {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, - {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, - {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, - {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, -]; - - -router.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); - -router.get('/file', function(req, res, next) { - mongoose.model('File').find({}, function(err, files) { - if (err) { - console.log(err); - res.status(500).json(err); - } - - res.json(files); - }); -}); - - -router.post('/file', function(req, res, next) { - const newId = '' + FILES.length; - const data = req.body; - data.id = newId; - - FILES.push(data); - res.status(201).json(data); -}); - -router.put('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - file.title = req.body.title; - file.description = req.body.description; - res.json(file); -}); - -router.delete('/file/:fileId', function(req, res, next) { - res.end(`Deleting file '${req.params.fileId}'`); -}); - -router.get('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - // same as 'const fileId = req.params.fileId' - - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - res.json(file); -}); - -module.exports = router; diff --git a/week5/src/server.js b/week5/src/server.js deleted file mode 100644 index fa63e81..0000000 --- a/week5/src/server.js +++ /dev/null @@ -1,27 +0,0 @@ -// src/server.js -const path = require('path'); -const bodyParser = require('body-parser'); - -// Load mongoose package -const mongoose = require('mongoose'); - -const express = require('express'); -const config = require('./config'); -const router = require('./routes'); - -// Connect to MongoDB and create/use database as configured -mongoose.connect(`mongodb://${config.db.host}/${config.db.dbName}`); - -// Import all models -require('./models/file.model.js'); - -const app = express(); -const publicPath = path.resolve(__dirname, '../public'); -app.use(express.static(publicPath)); -app.use(bodyParser.json()); -app.use('/api', router); - - -app.listen(config.port, function() { - console.log(`${config.appName} is listening on port ${config.port}`); -}); diff --git a/week5/src/tester/index.html b/week5/src/tester/index.html deleted file mode 100644 index 2b263fa..0000000 --- a/week5/src/tester/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - sdfjhs - - - SKDJHFLKSJDHFLKSDJ - - diff --git a/week6/index.js b/week6/index.js deleted file mode 100644 index 440ef30..0000000 --- a/week6/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./src/server'); diff --git a/week6/mongoose_diag.png b/week6/mongoose_diag.png deleted file mode 100644 index c40ad4c90b293a85e5b2526e60791ec0ba2a813f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30225 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 QdL`1=KC4xsaXIAw05~Q<_W%F@ diff --git a/week6/package.json b/week6/package.json deleted file mode 100644 index 3117c53..0000000 --- a/week6/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "cl_project", - "version": "1.0.0", - "description": "This is a sample node project", - "main": "index.js", - "scripts": { - "start": "nodemon index.js" - }, - "keywords": [ - "code", - "louisville", - "sample", - "project" - ], - "author": "Aaron W. Johnson", - "license": "ISC", - "dependencies": { - "body-parser": "^1.17.2", - "express": "^4.14.0", - "mongoose": "^4.10.4" - } -} diff --git a/week6/public/index.html b/week6/public/index.html deleted file mode 100644 index 76ff462..0000000 --- a/week6/public/index.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - Our Glorious Node Project - - - - -
-

A wild webpage appears...

-
- - -
- - - - - - - - - diff --git a/week6/public/js/app.js b/week6/public/js/app.js deleted file mode 100644 index 013bd63..0000000 --- a/week6/public/js/app.js +++ /dev/null @@ -1,67 +0,0 @@ - - -function getFiles() { - return $.ajax('/api/file') - .then(res => { - console.log("Results from getFiles()", res); - return res; - }) - .fail(err => { - console.log("Error in getFiles()", err); - throw err; - }); -} - - -function refreshFileList() { - const template = $('#list-template').html(); - const compiledTemplate = Handlebars.compile(template); - - getFiles() - .then(files => { - const data = {files: files}; - const html = compiledTemplate(data); - $('#list-container').html(html); - }) -} - - -function toggleAddFileForm() { - console.log("Baby steps..."); - toggleAddFileFormVisibility(); -} - -function toggleAddFileFormVisibility() { - $('#form-container').toggleClass('hidden'); -} - -function submitFileForm() { - console.log("You clicked 'submit'. Congratulations."); - - const fileData = { - title: $('#file-title').val(), - description: $('#file-description').val(), - }; - - $.ajax({ - type: "POST", - url: '/api/file', - data: JSON.stringify(fileData), - dataType: 'json', - contentType : 'application/json', - }) - .done(function(response) { - console.log("We have posted the data"); - refreshFileList(); - toggleAddFileForm(); - }) - .fail(function(error) { - console.log("Failures at posting, we are", error); - }) - - console.log("Your file data", fileData); -} - -function cancelFileForm() { - toggleAddFileFormVisibility(); -} diff --git a/week6/readme.md b/week6/readme.md deleted file mode 100644 index adc8985..0000000 --- a/week6/readme.md +++ /dev/null @@ -1,207 +0,0 @@ -# FSJS Week 6 - Something from Nothing - -**Outline** - -1. Set up for week6 -2. Create a hidable form to add files -4. Client-side function to POST a new files -5. Server-side handler that creates file - - -## 1. Setup Project -1. (optional) Clone the project -``` -git clone https://github.com/CodeLouisville/FSJS-class-project.git -cd FSJS-class-project -``` - - **OR** if you need to ditch your current changes and pull a fresh copy down, try this: - ``` - git stash - git pull - ``` - -2. Get rid of `week6` (we're going to rebuild it) -``` -rm -rf week6 -``` - -3. Copy `week5` to `week6` -``` -cp -R week5 week6 -cd week6 -``` - -4. Install dependencies -``` -npm install -``` - -**Strategy:** -* A User will visit the site and see a button that reads `Add File`. -* Clicking on this button will cause a blank form (previously not visible) to appear. -* Our user will use that form to add a new File to the database. -* The form has fields for `title` and `description` fields, a `Submit` and a `Cancel` button. -* The `Submit` and `Cancel` buttons do exactly what you think they would do. - * The `Submit` button will trigger a javascript function that grabs the data from the form and POSTs it to an API endpoint (we already have one...remember it?) - * After POSTing the data and receiving a response, the page will refresh the list of Files. - * The `Cancel` button will close the form without POSTing the data -* Clicking the `Add File` button while the form is open has the same effect as clicking `Cancel.` - -## 2. Create a form - - -1. Clean up the look of our webpage by taking advantage of bootstrap's `.container`. Open `public/index.html` and make the first two lines of the `` look like this: - ```html -
-

A wild webpage appears...

-
-
- ``` - -2. Add a button that calls a function when clicked below the file list. - ```html - - ``` - -3. We need that button to do something when we click it. Add an `onclick` handler: - ```html - - ``` - -3. What's that `toggleAddFileForm()` function? We have to create it. Add the following code to the file we created to house our code: `public/js/app.js`. - ```javascript - function toggleAddFileForm() { - console.log("Baby steps..."); - } - ``` - Refresh the page, open a console, and click the button a few times. - -4. Add a section of HTML that will appear and disappear on command. Add this below the `Add File` button. - ```html - - ``` - If you refresh the page, nothing will appear because of bootstrap's `.hidden` class. - -5. Create a javascript function to toggle the visibility of the form container: - ```javascript - function toggleAddFileFormVisibility() { - $('#form-container').toggleClass('hidden'); - } - ``` - - And call that function within `toggleAddFileForm()` - ```javascript - function toggleAddFileForm() { - console.log("Baby steps..."); - toggleAddFileFormVisibility(); - } - ``` - -6. Now insert a form into the `#form-container` - ```html -
-
- - -
-
- - -
- - -
- ``` - Reload the browser and look at our beautiful form. - -7. What about those `onclick` functions? We already know what 'cancel' should do (close the form), but will figure out what `submit` does later. - ```javascript - function submitFileForm() { - console.log("You clicked 'submit'. Congratulations."); - } - - function cancelFileForm() { - toggleAddFileFormVisibility(); - } - ``` - - At this point, we should have 4 more simple functions in `public/js/app.js` that barely do anything. Let's change that by collecting the form data when we click `submit`: - -8. Add the following to our `submitFileForm` function after the `console.log` line: - ```javascript - function submitFileForm() { - console.log("You clicked 'submit'. Congratulations."); - - const title = $('#file-title').val(); - const description = $('#file-description').val(); - const fileData = { - title: title, - description: description, - }; - - console.log("Your file data", fileData); - } - ``` - -## jQuery, our POSTing hero - -We're going to POST json-formatted data to an endpoint on our server which will do all the hard work. We already have a `POST /api/file` route, but currently it appends the file data to a static array (remember? We never changed that). - -First, we'll use jquery to POST the data, then we'll fix our POST route. - -1. Add the following to our `submitFileForm` function AFTER we create the fileData object. - ```javascript - $.ajax({ - type: "POST", - url: '/api/file', - data: JSON.stringify(fileData), - dataType: 'json', - contentType : 'application/json', - }) - .done(function(response) { - console.log("We have posted the data"); - refreshFileList(); - toggleAddFileFormVisibility(); - }) - .fail(function(error) { - console.log("Failures at posting, we are", error); - }); - ``` - [Documentation for jquery AJAX](https://api.jquery.com/jquery.ajax/) - If we refresh the page and test this, it will work, but we won't be updating the displayed list. Remember that we have that static array thing which we've never changed...let's do that now. - - -## Now, fix the POST route handler - -1. Open the file `src/routes/index.js` and delete everything in our `POST /file` handler. It should look like this when we're done: - ```javascript - router.post('/file', function(req, res, next) { - - }); - ``` - -2. Instead of appending to an array, we will use our mongoose model to insert a new "File" in to the database. Change the `POST /file` handler to the following: - ```javascript - router.post('/file', function(req, res, next) { - const File = mongoose.model('File'); - const fileData = { - title: req.body.title, - description: req.body.description, - }; - - File.create(fileData, function(err, newFile) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - - res.json(newFile); - }); - }); - ``` - [Documentation for mongoose Model.create](http://mongoosejs.com/docs/api.html#model_Model.create) - - Restart the server, go back to our website and add a new File. Our list of files should update. We can reload the page and/or restart the server and we will still have our newly added file in the list. diff --git a/week6/src/config/index.js b/week6/src/config/index.js deleted file mode 100644 index 8b6e7d5..0000000 --- a/week6/src/config/index.js +++ /dev/null @@ -1,10 +0,0 @@ -// src/config/index.js - -module.exports = { - appName: 'Our Glorious Node Project', - port: 3030, - db: { - host: 'localhost', - dbName: 'fsjs', - } -}; diff --git a/week6/src/models/file.model.js b/week6/src/models/file.model.js deleted file mode 100644 index 5166098..0000000 --- a/week6/src/models/file.model.js +++ /dev/null @@ -1,29 +0,0 @@ -// Load mongoose package -const mongoose = require('mongoose'); - -const FileSchema = new mongoose.Schema({ - title: String, - description: String, - created_at: { type: Date, default: Date.now }, -}); - - -const File = mongoose.model('File', FileSchema); -module.exports = File; - - -File.count({}, function(err, count) { - if (err) { - throw err; - } - - if (count > 0) return ; - - const files = require('./file.seed.json'); - File.create(files, function(err, newFiles) { - if (err) { - throw err; - } - console.log("DB seeded") - }); -}); diff --git a/week6/src/models/file.seed.json b/week6/src/models/file.seed.json deleted file mode 100644 index 6b401c1..0000000 --- a/week6/src/models/file.seed.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - {"title":"Satellite of Love Plans.svg", "description": "Includes fix for exhaust port vulnerability" }, - {"title":"Rules of Cribbage.doc", "description": "9th edition" }, - {"title":"avengers_fanfic.txt", "description": "PRIVATE DO NOT READ" } -] diff --git a/week6/src/models/index.js b/week6/src/models/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/week6/src/routes/index.js b/week6/src/routes/index.js deleted file mode 100644 index a433df6..0000000 --- a/week6/src/routes/index.js +++ /dev/null @@ -1,74 +0,0 @@ -// src/routes/index.js -const router = require('express').Router(); -const mongoose = require('mongoose'); - -const FILES = [ - {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, - {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, - {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, - {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, -]; - - -router.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); - -router.get('/file', function(req, res, next) { - mongoose.model('File').find({}, function(err, files) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - - res.json(files); - }); -}); - - -router.post('/file', function(req, res, next) { - const File = mongoose.model('File'); - const fileData = { - title: req.body.title, - description: req.body.description, - }; - - File.create(fileData, function(err, newFile) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - - res.json(newFile); - }); -}); - -router.put('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - file.title = req.body.title; - file.description = req.body.description; - res.json(file); -}); - -router.delete('/file/:fileId', function(req, res, next) { - res.end(`Deleting file '${req.params.fileId}'`); -}); - -router.get('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - // same as 'const fileId = req.params.fileId' - - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - res.json(file); -}); - -module.exports = router; diff --git a/week6/src/server.js b/week6/src/server.js deleted file mode 100644 index fa63e81..0000000 --- a/week6/src/server.js +++ /dev/null @@ -1,27 +0,0 @@ -// src/server.js -const path = require('path'); -const bodyParser = require('body-parser'); - -// Load mongoose package -const mongoose = require('mongoose'); - -const express = require('express'); -const config = require('./config'); -const router = require('./routes'); - -// Connect to MongoDB and create/use database as configured -mongoose.connect(`mongodb://${config.db.host}/${config.db.dbName}`); - -// Import all models -require('./models/file.model.js'); - -const app = express(); -const publicPath = path.resolve(__dirname, '../public'); -app.use(express.static(publicPath)); -app.use(bodyParser.json()); -app.use('/api', router); - - -app.listen(config.port, function() { - console.log(`${config.appName} is listening on port ${config.port}`); -}); diff --git a/week6/src/tester/index.html b/week6/src/tester/index.html deleted file mode 100644 index 2b263fa..0000000 --- a/week6/src/tester/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - sdfjhs - - - SKDJHFLKSJDHFLKSDJ - - diff --git a/week7/index.js b/week7/index.js deleted file mode 100644 index 440ef30..0000000 --- a/week7/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./src/server'); diff --git a/week7/mongoose_diag.png b/week7/mongoose_diag.png deleted file mode 100644 index c40ad4c90b293a85e5b2526e60791ec0ba2a813f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30225 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 QdL`1=KC4xsaXIAw05~Q<_W%F@ diff --git a/week7/package.json b/week7/package.json deleted file mode 100644 index 3117c53..0000000 --- a/week7/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "cl_project", - "version": "1.0.0", - "description": "This is a sample node project", - "main": "index.js", - "scripts": { - "start": "nodemon index.js" - }, - "keywords": [ - "code", - "louisville", - "sample", - "project" - ], - "author": "Aaron W. Johnson", - "license": "ISC", - "dependencies": { - "body-parser": "^1.17.2", - "express": "^4.14.0", - "mongoose": "^4.10.4" - } -} diff --git a/week7/public/index.html b/week7/public/index.html deleted file mode 100644 index f7c46f3..0000000 --- a/week7/public/index.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - Our Glorious Node Project - - - - -
-

A wild webpage appears...

-
- - -
- - - - - - - - - diff --git a/week7/public/js/app.js b/week7/public/js/app.js deleted file mode 100644 index 9f449be..0000000 --- a/week7/public/js/app.js +++ /dev/null @@ -1,104 +0,0 @@ - - -function getFiles() { - return $.ajax('/api/file') - .then(res => { - console.log("Results from getFiles()", res); - return res; - }) - .fail(err => { - console.log("Error in getFiles()", err); - throw err; - }); -} - - -function refreshFileList() { - const template = $('#list-template').html(); - const compiledTemplate = Handlebars.compile(template); - - getFiles() - .then(files => { - - window.fileList = files; - - const data = {files: files}; - const html = compiledTemplate(data); - $('#list-container').html(html); - }) -} - - -function toggleAddFileForm() { - console.log("Baby steps..."); - setFormData({}); - toggleAddFileFormVisibility(); -} - -function toggleAddFileFormVisibility() { - $('#form-container').toggleClass('hidden'); -} - -function submitFileForm() { - console.log("You clicked 'submit'. Congratulations."); - - const fileData = { - title: $('#file-title').val(), - description: $('#file-description').val(), - _id: $('#file-id').val(), - }; - - let method, url; - if (fileData._id) { - method = 'PUT'; - url = '/api/file/' + fileData._id; - } else { - method = 'POST'; - url = '/api/file'; - } - - $.ajax({ - type: method, - url: url, - data: JSON.stringify(fileData), - dataType: 'json', - contentType : 'application/json', - }) - .done(function(response) { - console.log("We have posted the data"); - refreshFileList(); - toggleAddFileForm(); - }) - .fail(function(error) { - console.log("Failures at posting, we are", error); - }) - - console.log("Your file data", fileData); -} - -function cancelFileForm() { - toggleAddFileFormVisibility(); -} - - -function editFileClick(id) { - const file = window.fileList.find(file => file._id === id); - if (file) { - setFormData(file); - toggleAddFileFormVisibility(); - } -} - -function setFormData(data) { - data = data || {}; - - const file = { - title: data.title || '', - description: data.description || '', - _id: data._id || '', - }; - - $('#file-title').val(file.title); - $('#file-description').val(file.description); - $('#file-id').val(file._id); -} diff --git a/week7/readme.md b/week7/readme.md deleted file mode 100644 index d4b65e5..0000000 --- a/week7/readme.md +++ /dev/null @@ -1,274 +0,0 @@ -# FSJS Week 7 - Change is the Only Constant - -**Outline** - -1. Set up for week7 -2. Every file item has an edit button -3. Function to push changes to the server -3. PUT endpoint - - -## 1. Setup Project -1. (optional) Clone the project -``` -git clone https://github.com/CodeLouisville/FSJS-class-project.git -cd FSJS-class-project -``` - - **OR** if you need to ditch your current changes and pull a fresh copy down, try this: - ``` - git stash - git pull - ``` - -2. Get rid of `week7` (we're going to rebuild it) -``` -rm -rf week7 -``` - -3. Copy `week6` to `week7` -``` -cp -R week6 week7 -cd week7 -``` - -4. Install dependencies -``` -npm install -``` - -**Strategy:** -* A User will visit the site and see an edit button beside each file. -* Clicking on this button will cause a form (previously not visible) to appear. -* This will be the same form that adds a new File. -* That form will have all the current information for the selected file -* Our user will use that form to edit the existing File. -* The `Submit` button will trigger a javascript function that grabs the data from the form and PUTs it to an API endpoint -* After PUTting the data and receiving a response, the page will refresh the list of Files. - -## 2. Add an edit button to each item. - -1. Open `public/index.html` and find the `#list-template` handlebars template we use to render the list of files. Add a button to each item. -```html -
  • - {{title}} - {{description}} - - - -
  • -``` - -2. Add some functionality to that button. Add an `onclick` event handler and its corresponding function. -```html - -``` -And the function goes in `/public/js/app.js` -```javascript -function editFileClick() { - console.log("I will edit for you!"); -} -``` - -This works, but every 'Edit' does the exact same thing when clicked. We want a click to (eventually) open a form with the data for a specific file. We need somehow get the File data in to our `editFileClick()` function. There are dozens of ways of accomplishing this. Here's a straight-forward method: - -Pass the unique File `_id` field to our function in the `onclick` event handler. Then use that id to find the File in an array. We'll need to make sure we have an array of File objects available. - -3. Pass the `_id` parameter to the funciton - ```html - - ``` - And now `console.log()` the result to show it works - ```javascript - function editFileClick(id) { - console.log("I will edit for you", id); - } - ``` - - Take a look at the edit button element to see what's going on here. - -4. Whenever we refresh the list of Files (remember our AJAX call), save that array to a property on the global `window` object. This is done in `refreshFileList()` -```javascript -function refreshFileList() { - const template = $('#list-template').html(); - const compiledTemplate = Handlebars.compile(template); - - getFiles() - .then(files => { - - window.fileList = files; - - const data = {files: files}; - const html = compiledTemplate(data); - $('#list-container').html(html); - }) -} -``` - -5. In our `onclick` handler, retrieve the file using `Array.find()` -```javascript -function editFileClick(id) { - const file = window.fileList.find(file => file._id === id); - if (file) { - console.log("I will edit you!", file); - } else { - console.log("Aw shucks, I didn't find", id) - } -} -``` -[Documentation for Array.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find?v=control) - - Refresh the page and click on a few `Edit` buttons to see that it works. - - -6. Edit the `editFileClick()` function so that it opens the form we created last week. When clicked, we should also populate the form with the data we wish to edit. - ```javascript - function editFileClick(id) { - const file = window.fileList.find(file => file._id === id); - if (file) { - $('#file-title').val(file.title); - $('#file-description').val(file.description); - toggleAddFileFormVisibility(); - } - } - ``` - -7. Hey, what about the `_id` field? That seems important. Add a hidden input to the form and set that when we click edit. - ```html -

    `: +```html +
    +``` -`require()` is what allows you to organize your code in to easy-to-understand (hopefully) directories and files, but join them all together in to a single application. +4. Create some fake data in another script tag +```html + +``` -For comparison: in a browser environment, if you want to make content from multiple file available to the larger application you can 1) concatenate them all in to one file or 2) load them individually via a ` +``` -`require()` serves that purpose on the server side by reading the contents of the file you specify, executing it, and making whatever you export available. +6. Right below the `list` array, compile the template and render it into the container. ```javascript -// index.js -var myRandomObject = require('./myFile'); - -// myFile.js -module.exports = {some: {random: ['object']}}; +const template = $('#list-template').html(); +const compiled = Handlebars.compile(template); +$('#list-container').html(compiled(data)); ``` -### What's with all these index.js files -You will see (and create) a lot of `index.js` files in your Node lifetime. The reason for this has to do with how `require()` behaves. - -When you pass the name of a directory to `require()`, it will specifically seek out a file in that directory named `index.js` (if it doesn't find one, it looks for index.node, but that's a story for another time) - - -### So this can be confusing. -Your text editor may have half a dozen open tabs - all with the name `index.js`. That's annoying, but the `index.js` naming convention is there for good reason and it is an important aspect of nodejs development. +7. Refresh the page -Remember, you don't HAVE to have an `index.js` file in a directory, but you should know how Node treats that file if you do. \ No newline at end of file +8. Add some style in the `` +```html + +``` \ No newline at end of file From 0d89e125b0a6d658c4717287b33fd5e620434010 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 15:28:57 -0500 Subject: [PATCH 04/18] add code from week1 --- README.md | 26 +--- package-lock.json | 360 ++++++++++++++++++++++++++++++++++++++++++++ src/config/index.js | 6 + src/models/index.js | 0 src/routes/index.js | 0 src/server.js | 18 +++ 6 files changed, 387 insertions(+), 23 deletions(-) create mode 100644 package-lock.json create mode 100644 src/config/index.js create mode 100644 src/models/index.js create mode 100644 src/routes/index.js create mode 100644 src/server.js diff --git a/README.md b/README.md index e079b0e..de936ee 100644 --- a/README.md +++ b/README.md @@ -7,32 +7,12 @@ * Add a template engine ## Set up the project -_It should still be setup from [week 1](../week1)_ - -1. Clean the project -_If you did something you want to keep, last week, you can make a branch and commit, or copy those files out. To continue here, we are going to reset `week1` back to our code_ -``` -cd FSJS-class-project -git status -git reset --hard HEAD -git pull -``` - -2. Get rid of `week2` _(we're going to rebuild it)_ -``` -rm -rf week2 -``` - -3. Copy `week1` to `week2` _(this is our starting point)_ -``` -cp -R week1 week2 -cd week2 -``` - -4. Install dependencies ``` +git checkout week2 npm install ``` +_This should be similar to how we left it from [week 1](/CodeLouisville/FSJS-class-project/tree/week1)_ + ## Serve a static page 1. Create a "public" directory inside the `src` directory diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e2c6139 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,360 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "accepts": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "requires": { + "mime-types": "2.1.17", + "negotiator": "0.6.1" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "1.0.4", + "debug": "2.6.9", + "depd": "1.1.2", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "1.6.15" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "requires": { + "accepts": "1.3.4", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "1.1.2", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "finalhandler": "1.1.0", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "1.1.2", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "2.0.2", + "qs": "6.5.1", + "range-parser": "1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.1", + "serve-static": "1.13.1", + "setprototypeof": "1.1.0", + "statuses": "1.3.1", + "type-is": "1.6.15", + "utils-merge": "1.0.1", + "vary": "1.1.2" + } + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": "1.3.1" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", + "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "requires": { + "forwarded": "0.1.2", + "ipaddr.js": "1.5.2" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "send": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "requires": { + "debug": "2.6.9", + "depd": "1.1.2", + "destroy": "1.0.4", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "etag": "1.8.1", + "fresh": "0.5.2", + "http-errors": "1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "2.3.0", + "range-parser": "1.2.0", + "statuses": "1.3.1" + } + }, + "serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "requires": { + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "parseurl": "1.3.2", + "send": "0.16.1" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + }, + "type-is": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", + "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "requires": { + "media-typer": "0.3.0", + "mime-types": "2.1.17" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/src/config/index.js b/src/config/index.js new file mode 100644 index 0000000..606778f --- /dev/null +++ b/src/config/index.js @@ -0,0 +1,6 @@ +// src/config/index.js + +module.exports = { + appName: 'Our Glorious Node Project', + port: 3030 +} \ No newline at end of file diff --git a/src/models/index.js b/src/models/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..511e488 --- /dev/null +++ b/src/server.js @@ -0,0 +1,18 @@ +// src/server.js + +const express = require('express'); +const config = require('./config'); + +const app = express(); + +app.use('/doc', function(req, res, next) { + res.end(`Documentation http://expressjs.com/`); +}); + +app.use(function(req, res, next) { + res.end("Hello World!"); +}); + +app.listen(config.port, function() { + console.log(`${config.appName} is listening on port ${config.port}`); +}); From e836eee847d92d7b84c7cd1d05cb098f0ac417da Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 15:41:55 -0500 Subject: [PATCH 05/18] updated week2 readme --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index de936ee..36058b5 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ const path = require('path'); Then add the following line to `server.js` BEFORE any routes ```javascript -const publicPath = path.resolve(__dirname, './public'); +const publicPath = path.resolve(__dirname, '../public'); app.use(express.static(publicPath)); ``` [[Documentation for Node Modules (dirname)](https://nodejs.org/api/modules.html)] @@ -46,7 +46,9 @@ app.use(express.static(publicPath)); Our Glorious Node Project -

    A wild webpage appears...

    +
    +

    A wild webpage appears...

    +
    From c8c19abaa1175891a10e0e75ee3bf2a54d7c4f32 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 16:16:50 -0500 Subject: [PATCH 06/18] change instructions on placement of public --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36058b5..14a0fe8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ _This should be similar to how we left it from [week 1](/CodeLouisville/FSJS-cla ## Serve a static page -1. Create a "public" directory inside the `src` directory +1. Create a "public" directory inside the root directory ``` mkdir public ``` From a3828c76d4d1fbc43d3e798d30fb01036624572d Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 15:32:33 -0500 Subject: [PATCH 07/18] added week3 readme --- README.md | 218 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 128 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 14a0fe8..6d189f6 100644 --- a/README.md +++ b/README.md @@ -1,132 +1,170 @@ -# FSJS Week 2 - Our Superlative Web Page +# FSJS Week 3 - A Wild Router Appears **Outline** -* Set up the project for the front end -* Serve a static page -* Add a template engine +* Set up the project +* Grab some DevTools +* The 5 operation of CRUD (list, detail, create, update, delete) +* Send mock, static data as response + ## Set up the project ``` -git checkout week2 +git checkout week3 npm install ``` -_This should be similar to how we left it from [week 1](/CodeLouisville/FSJS-class-project/tree/week1)_ +## Hooray! New DevTools! + +### Postman + +Postman allows you to send HTTP requests to your API. You can tailor the url, method, payload, querystring, and headers. This is a very powerful API testing tool which will make our development easier. + +[[Download here](https://www.getpostman.com/docs/postman/launching_postman/installation_and_updates)] +### Nodemon + +Tired of restarting your Node server every time you change a file? Hand getting a cramp from hitting `ctrl-c` after every typo fix? **You're in luck** + +Enter: Nodemon - Monitor for any changes in your node.js application and automatically restart the server - perfect for development. + +<<<<<<< HEAD ## Serve a static page 1. Create a "public" directory inside the root directory +======= +>>>>>>> added week3 readme ``` -mkdir public +npm install nodemon -g ``` -2. Set up our express application to serve static files. -Add a reference to Node's `path` module to the top of the page in the `server.js` +Here, we're NOT using the `--save` switch, but we are using the mysterious `-g`. `-g` tells npm to install Nodemon globally (so it will be available for all your projects). Therefore, we don't need to save it to our `package.json` file. + +Next, add a script to `package.json`. Find the `scripts` section and replace it with the following: ```javascript -const path = require('path'); +"scripts": { + "start": "nodemon index.js" +}, ``` -[[Documentation for path](https://nodejs.org/api/path.html)] +[[Documentation on NPM scripts](https://docs.npmjs.com/misc/scripts)] -Then add the following line to `server.js` BEFORE any routes +To start our server, type: +``` +npm start +``` + +Now, when we make changes to files, the server restarts automatically. Try it out! + +## The 5 Operations of CRUD + +| Operation | Suggested HTTP | Data | +| --- | --- | --- | +| Create | POST | Create a new element | +| Read | GET | Get a single element | +| Update | PUT | Replace an element with new data | +| Delete | DELETE | Delete a single element | +| List | GET | Get an array of elements | + +1. Do a little clean-up by moving our endpoints to a separate file. Add the following to `routes/index.js` ```javascript -const publicPath = path.resolve(__dirname, '../public'); -app.use(express.static(publicPath)); +// src/routes/index.js +const router = require('express').Router(); ``` -[[Documentation for Node Modules (dirname)](https://nodejs.org/api/modules.html)] -[[Guide for ExpresJS static](https://expressjs.com/en/starter/static-files.html)] +[[ExpressJS Router documentation](https://expressjs.com/en/4x/api.html#router)] -`express.static()` will search the `public` directory for a file that matches the requested path. For example: `index.html`, `img/puppy.jpg`, etc. If there is a match, that file is streamed back to the requester, otherwise, express moves on to the next route. +A router object allows us to create an isolated bundle of endpoints and middleware. This is not new functionality, just a convenient way to package code in to separate, easy-to-read and easy-to-maintain files. -3. Add an `index.html` to the public folder. -```html - - - - - Our Glorious Node Project - - -
    -

    A wild webpage appears...

    -
    +Now, move our existing routes over from `server.js`: +```javascript +router.use('/doc', function(req, res, next) { + res.end(`Documentation http://expressjs.com/`); +}); - - +module.exports = router; ``` +(Note that our "Hello world" route is unnecessary since we are serving a static index.html) -4. Start the server and check that you can access a static `html` page +Now, head back to `server.js` and make sure our app knows how to use the router. Create a variable for our router at the top of the file: +```javascript +const router = require('./routes'); +``` +And then direct our app to use the router AFTER the line where we handle static files: +```javascript +app.use(express.static(publicPath)); +app.use('/api', router); +``` -Note: We previously had a "Hello World" endpoint that was served when user's requested the path `/`. That path is now unreachable, because all requests for `/` will receive `index.html`. +What does the `'/api'` part do? [`app.use()` Documentation here](https://expressjs.com/en/4x/api.html#app.use). Basically, this prepends `/api` to all the paths defined in `router` (currently, we only have `/doc`). So, instead of making a GET request to `/doc`, we will now make a request to `/api/doc`. -`/doc` still works, though. +**Fire up postman and try it** +2. Add some basic **List** and **Create** handlers to `routes\index.js`: +```javascript +router.get('/file', function(req, res, next) { + res.end('List all files'); +}); -## Add a template engine -1. We'll be using jQuery and Handlebars, so let's add them to our `index.html` at the end of the `` tag -```html - - +router.post('/file', function(req, res, next) { + res.end('Create a new file'); +}); ``` -[[Documentation for jQuery](https://api.jquery.com/)] -[[Documentation for Handlebars](http://handlebarsjs.com/reference.html)] +[[Documentation for router.get() and router.post()](https://expressjs.com/en/4x/api.html#router.METHOD)] -2. Drop a quick template in `index.html` to see how handlebars renders content: -```html - -``` +3. Add **Update**, **Delete**, and **Read** endpoints - all of which take a route parameter: +```javascript +router.put('/file/:fileId', function(req, res, next) { + res.end(`Updating file '${req.params.fileId}'`); +}); -3. Render a list of fake data. Start by adding a place in the html to render the list after the `

    `: -```html -
    -``` +router.delete('/file/:fileId', function(req, res, next) { + res.end(`Deleting file '${req.params.fileId}'`); +}); -4. Create some fake data in another script tag -```html - +router.get('/file/:fileId', function(req, res, next) { + res.end(`Reading file '${req.params.fileId}'`); +}); ``` +[[Documentation for Route Parameters](https://expressjs.com/en/guide/routing.html#route-parameters)] + +Route parameters allow you to pass information to the router via the url itself. When express finds a route parameter (indicated by `:`), it creates a property on `req.params` with the same name. -5. Create a template for each list item. -```html - +## Return some data + +1. Let's add a static array of "file" object for testing purposes. Near the top of the `routes/index.js` file, add the following: +```javascript +const FILES = [ + {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, + {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, + {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, + {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, +]; ``` -6. Right below the `list` array, compile the template and render it into the container. +2. Return the entire list as JSON. Replace the handler for `GET /file`, with: ```javascript -const template = $('#list-template').html(); -const compiled = Handlebars.compile(template); -$('#list-container').html(compiled(data)); +router.get('/file', function(req, res, next) { + res.json(FILES); +}); ``` +[[Documentation for res.json()](https://expressjs.com/en/4x/api.html#res.json)] -7. Refresh the page +`res.json()` accepts any type of data, stringifies with `JSON.stringify()` and sends the response with the header `Content-Type: application/json`. -8. Add some style in the `` -```html - -``` \ No newline at end of file +3. Return a single element by replacing the handler for `GET /file/:fileId` with: +```javascript +router.get('/file/:fileId', function(req, res, next) { + const {fileId} = req.params; + // same as 'const fileId = req.params.fileId' + + const file = FILES.find(entry => entry.id === fileId); + if (!file) { + return res.status(404).end(`Could not find file '${fileId}'`); + } + + res.json(file); +}); +``` +[[Documentation for object destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring)] +[[Documentation for Array.prototype.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find?v=example)] +[[Documentation for res.status()](https://expressjs.com/en/4x/api.html#res.status)] \ No newline at end of file From 82c89b77caf21920fcc82b2c48eb65a8caac54fe Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 15:40:44 -0500 Subject: [PATCH 08/18] added code from week2 --- .gitignore | 1 + public/index.html | 57 +++++++++++++++++++++++++++++++++++++++++++++++ src/server.js | 4 ++++ 3 files changed, 62 insertions(+) create mode 100644 public/index.html diff --git a/.gitignore b/.gitignore index fd4f2b0..8d4c2d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules .DS_Store +npm-debug.log \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..5af53ac --- /dev/null +++ b/public/index.html @@ -0,0 +1,57 @@ + + + + + Our Glorious Node Project + + + +
    +

    A wild webpage appears...

    + +
    +
    + + + + + + + + + + + + \ No newline at end of file diff --git a/src/server.js b/src/server.js index 511e488..5265a83 100644 --- a/src/server.js +++ b/src/server.js @@ -1,9 +1,13 @@ // src/server.js +const path = require('path'); const express = require('express'); const config = require('./config'); const app = express(); +const publicPath = path.resolve(__dirname, '../public'); +app.use(express.static(publicPath)); + app.use('/doc', function(req, res, next) { res.end(`Documentation http://expressjs.com/`); From 66ec2f8211c0b70dc76f47b2fa3f71bae4cec244 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 16:11:47 -0500 Subject: [PATCH 09/18] update npm start command and changed order of routes example --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6d189f6..679faa7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Here, we're NOT using the `--save` switch, but we are using the mysterious `-g`. Next, add a script to `package.json`. Find the `scripts` section and replace it with the following: ```javascript "scripts": { - "start": "nodemon index.js" + "start": "nodemon src/server.js" }, ``` [[Documentation on NPM scripts](https://docs.npmjs.com/misc/scripts)] @@ -113,6 +113,10 @@ Head over to postman and test it out. 3. Add **Update**, **Delete**, and **Read** endpoints - all of which take a route parameter: ```javascript +router.get('/file/:fileId', function(req, res, next) { + res.end(`Reading file '${req.params.fileId}'`); +}); + router.put('/file/:fileId', function(req, res, next) { res.end(`Updating file '${req.params.fileId}'`); }); @@ -120,10 +124,6 @@ router.put('/file/:fileId', function(req, res, next) { router.delete('/file/:fileId', function(req, res, next) { res.end(`Deleting file '${req.params.fileId}'`); }); - -router.get('/file/:fileId', function(req, res, next) { - res.end(`Reading file '${req.params.fileId}'`); -}); ``` [[Documentation for Route Parameters](https://expressjs.com/en/guide/routing.html#route-parameters)] From 6cf2729b488bb9ec08095bff1464a0ba7768197b Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 15:32:33 -0500 Subject: [PATCH 10/18] added week3 readme --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 679faa7..decced8 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ npm install Postman allows you to send HTTP requests to your API. You can tailor the url, method, payload, querystring, and headers. This is a very powerful API testing tool which will make our development easier. [[Download here](https://www.getpostman.com/docs/postman/launching_postman/installation_and_updates)] +<<<<<<< HEAD ### Nodemon @@ -32,6 +33,15 @@ Enter: Nodemon - Monitor for any changes in your node.js application and automat ## Serve a static page 1. Create a "public" directory inside the root directory ======= +>>>>>>> added week3 readme +======= + +### Nodemon + +Tired of restarting your Node server every time you change a file? Hand getting a cramp from hitting `ctrl-c` after every typo fix? **You're in luck** + +Enter: Nodemon - Monitor for any changes in your node.js application and automatically restart the server - perfect for development. + >>>>>>> added week3 readme ``` npm install nodemon -g @@ -42,7 +52,11 @@ Here, we're NOT using the `--save` switch, but we are using the mysterious `-g`. Next, add a script to `package.json`. Find the `scripts` section and replace it with the following: ```javascript "scripts": { +<<<<<<< HEAD "start": "nodemon src/server.js" +======= + "start": "nodemon index.js" +>>>>>>> added week3 readme }, ``` [[Documentation on NPM scripts](https://docs.npmjs.com/misc/scripts)] @@ -113,6 +127,7 @@ Head over to postman and test it out. 3. Add **Update**, **Delete**, and **Read** endpoints - all of which take a route parameter: ```javascript +<<<<<<< HEAD router.get('/file/:fileId', function(req, res, next) { res.end(`Reading file '${req.params.fileId}'`); }); @@ -123,6 +138,18 @@ router.put('/file/:fileId', function(req, res, next) { router.delete('/file/:fileId', function(req, res, next) { res.end(`Deleting file '${req.params.fileId}'`); +======= +router.put('/file/:fileId', function(req, res, next) { + res.end(`Updating file '${req.params.fileId}'`); +}); + +router.delete('/file/:fileId', function(req, res, next) { + res.end(`Deleting file '${req.params.fileId}'`); +}); + +router.get('/file/:fileId', function(req, res, next) { + res.end(`Reading file '${req.params.fileId}'`); +>>>>>>> added week3 readme }); ``` [[Documentation for Route Parameters](https://expressjs.com/en/guide/routing.html#route-parameters)] From 0424ce030a70fadfbe8e4cd13cb4586370b4f5e9 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 15:47:00 -0500 Subject: [PATCH 11/18] added and updated week4 readme --- README.md | 233 ++++++++++++++++++++---------------------------------- 1 file changed, 84 insertions(+), 149 deletions(-) diff --git a/README.md b/README.md index decced8..ec12934 100644 --- a/README.md +++ b/README.md @@ -1,197 +1,132 @@ -# FSJS Week 3 - A Wild Router Appears +# FSJS Week 4 - The Full Stackey **Outline** * Set up the project -* Grab some DevTools -* The 5 operation of CRUD (list, detail, create, update, delete) -* Send mock, static data as response +* Render data from the server on our page +* Add POST and PUT endpoints ## Set up the project ``` -git checkout week3 +git clone week4 npm install ``` -## Hooray! New DevTools! +## Render data from the server +We have a nice API endpoint to spit out data (albeit static data from an array). Let's use that in our front-end. -### Postman +1. Create a new file in a new directory: `public\js\app.js`. +(We could continue to put all our javascript in `script` tags in `index.html`, but placing this code in a separate file will help keep things neat and organized) -Postman allows you to send HTTP requests to your API. You can tailor the url, method, payload, querystring, and headers. This is a very powerful API testing tool which will make our development easier. - -[[Download here](https://www.getpostman.com/docs/postman/launching_postman/installation_and_updates)] -<<<<<<< HEAD - -### Nodemon - -Tired of restarting your Node server every time you change a file? Hand getting a cramp from hitting `ctrl-c` after every typo fix? **You're in luck** - -Enter: Nodemon - Monitor for any changes in your node.js application and automatically restart the server - perfect for development. - -<<<<<<< HEAD -## Serve a static page -1. Create a "public" directory inside the root directory -======= ->>>>>>> added week3 readme -======= - -### Nodemon - -Tired of restarting your Node server every time you change a file? Hand getting a cramp from hitting `ctrl-c` after every typo fix? **You're in luck** - -Enter: Nodemon - Monitor for any changes in your node.js application and automatically restart the server - perfect for development. - ->>>>>>> added week3 readme -``` -npm install nodemon -g +2. Load this in to our `index.html`. At the bottom of the file, just below the `script` tag that loads the handlebars library, add: +```html + ``` -Here, we're NOT using the `--save` switch, but we are using the mysterious `-g`. `-g` tells npm to install Nodemon globally (so it will be available for all your projects). Therefore, we don't need to save it to our `package.json` file. - -Next, add a script to `package.json`. Find the `scripts` section and replace it with the following: -```javascript -"scripts": { -<<<<<<< HEAD - "start": "nodemon src/server.js" -======= - "start": "nodemon index.js" ->>>>>>> added week3 readme -}, +3. Update our handlebars template to render a file and not a BTVS character list. Replace the `#list-template` script in `index.html` with the following: +```html + ``` -[[Documentation on NPM scripts](https://docs.npmjs.com/misc/scripts)] +While we're at it, we can let's delete the code that rendered our previous test data. (If you want to keep it around for reference, just comment it out.) -To start our server, type: -``` -npm start -``` - -Now, when we make changes to files, the server restarts automatically. Try it out! - -## The 5 Operations of CRUD - -| Operation | Suggested HTTP | Data | -| --- | --- | --- | -| Create | POST | Create a new element | -| Read | GET | Get a single element | -| Update | PUT | Replace an element with new data | -| Delete | DELETE | Delete a single element | -| List | GET | Get an array of elements | - -1. Do a little clean-up by moving our endpoints to a separate file. Add the following to `routes/index.js` +4. In `app.js`, create a function to get the file list: ```javascript -// src/routes/index.js -const router = require('express').Router(); -``` -[[ExpressJS Router documentation](https://expressjs.com/en/4x/api.html#router)] - -A router object allows us to create an isolated bundle of endpoints and middleware. This is not new functionality, just a convenient way to package code in to separate, easy-to-read and easy-to-maintain files. - -Now, move our existing routes over from `server.js`: +function getFiles() { + return $.ajax('/api/file') + .then(res => { + console.log("Results from getFiles()", res); + return res; + }) + .fail(err => { + console.log("Error in getFiles()", err); + throw err; + }); +} +``` + +5. Create a function to refresh the list ```javascript -router.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); +function refreshFileList() { + const template = $('#list-template').html(); + const compiledTemplate = Handlebars.compile(template); -module.exports = router; + getFiles() + .then(files => { + const data = {files: files}; + const html = compiledTemplate(data); + $('#list-container').html(html); + }) +} ``` -(Note that our "Hello world" route is unnecessary since we are serving a static index.html) +Test it out by refreshing the page, opening a debugging console, and typing `refreshFileList()`; -Now, head back to `server.js` and make sure our app knows how to use the router. Create a variable for our router at the top of the file: -```javascript -const router = require('./routes'); +6. Refresh the list automatically when the page first loads by adding `refreshFileList()` to the remaining `$().ready()` function in `index.html` + +## Finish What we started +1. We are going to be sending data from the client back to the server. To do that, we will convert a plain JS object to a JSON-formatted string (really, jQuery will do that for us). We need to set up our express server to parse that JSON string and turn it back in to an object. + +Fortunately, there a library for that: ``` -And then direct our app to use the router AFTER the line where we handle static files: -```javascript -app.use(express.static(publicPath)); -app.use('/api', router); +npm install body-parser --save ``` +[[Documentation for body-parser](https://github.com/expressjs/body-parser)] -What does the `'/api'` part do? [`app.use()` Documentation here](https://expressjs.com/en/4x/api.html#app.use). Basically, this prepends `/api` to all the paths defined in `router` (currently, we only have `/doc`). So, instead of making a GET request to `/doc`, we will now make a request to `/api/doc`. - -**Fire up postman and try it** +`body-parser` will look at the body of a request and, if the `Content-Type` is `application/json`, will parse the body using `JSON.parse()`. The results of that (if successful) will be put in `req.body` for use by any middleware. -2. Add some basic **List** and **Create** handlers to `routes\index.js`: +2. At the top of `server.js`, require the body-parser module: ```javascript -router.get('/file', function(req, res, next) { - res.end('List all files'); -}); - -router.post('/file', function(req, res, next) { - res.end('Create a new file'); -}); +const bodyParser = require('body-parser'); ``` -[[Documentation for router.get() and router.post()](https://expressjs.com/en/4x/api.html#router.METHOD)] - -Head over to postman and test it out. -3. Add **Update**, **Delete**, and **Read** endpoints - all of which take a route parameter: +3. Tell our server to use it. In `server.js`, right AFTER we set up static files serving, add the following: ```javascript -<<<<<<< HEAD -router.get('/file/:fileId', function(req, res, next) { - res.end(`Reading file '${req.params.fileId}'`); -}); - -router.put('/file/:fileId', function(req, res, next) { - res.end(`Updating file '${req.params.fileId}'`); -}); +app.use(function(req, res, next) { + console.log("req.body BEFORE parsing", req.body); + next(); +}) -router.delete('/file/:fileId', function(req, res, next) { - res.end(`Deleting file '${req.params.fileId}'`); -======= -router.put('/file/:fileId', function(req, res, next) { - res.end(`Updating file '${req.params.fileId}'`); -}); +app.use(bodyParser.json()); -router.delete('/file/:fileId', function(req, res, next) { - res.end(`Deleting file '${req.params.fileId}'`); -}); - -router.get('/file/:fileId', function(req, res, next) { - res.end(`Reading file '${req.params.fileId}'`); ->>>>>>> added week3 readme -}); +app.use(function(req, res, next) { + console.log("req.body AFTER parsing", req.body); + next(); +}) ``` -[[Documentation for Route Parameters](https://expressjs.com/en/guide/routing.html#route-parameters)] +Head over to postman. Create a POST request to ANY endpoint. Tell postman that the content is JSON (use the dropdown). Type in any valid JSON-formatted string and hit send. You'll see the contents outputted by the two middleware we added before and after the bodyParser middleware. -Route parameters allow you to pass information to the router via the url itself. When express finds a route parameter (indicated by `:`), it creates a property on `req.params` with the same name. +4. Delete the logging middleware -## Return some data - -1. Let's add a static array of "file" object for testing purposes. Near the top of the `routes/index.js` file, add the following: +5. Go back to our routes and add the POST and PUT endpoints. In `routes/index.js`, swap out the `router.put()` and `router.post()` callbacks (which were just placeholders) with the following: ```javascript -const FILES = [ - {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, - {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, - {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, - {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, -]; -``` +router.post('/file', function(req, res, next) { + const newId = '' + FILES.length; + const data = req.body; + data.id = newId; -2. Return the entire list as JSON. Replace the handler for `GET /file`, with: -```javascript -router.get('/file', function(req, res, next) { - res.json(FILES); + FILES.push(data); + res.status(201).json(data); }); ``` -[[Documentation for res.json()](https://expressjs.com/en/4x/api.html#res.json)] - -`res.json()` accepts any type of data, stringifies with `JSON.stringify()` and sends the response with the header `Content-Type: application/json`. - -3. Return a single element by replacing the handler for `GET /file/:fileId` with: +and ```javascript -router.get('/file/:fileId', function(req, res, next) { +router.put('/file/:fileId', function(req, res, next) { const {fileId} = req.params; - // same as 'const fileId = req.params.fileId' - const file = FILES.find(entry => entry.id === fileId); if (!file) { return res.status(404).end(`Could not find file '${fileId}'`); } + file.title = req.body.title; + file.description = req.body.description; res.json(file); }); -``` -[[Documentation for object destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring)] -[[Documentation for Array.prototype.find()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find?v=example)] -[[Documentation for res.status()](https://expressjs.com/en/4x/api.html#res.status)] \ No newline at end of file +``` \ No newline at end of file From 9cec72da98d5b9c818b97ed282df70821aadaaf5 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 16:10:32 -0500 Subject: [PATCH 12/18] added code from week3 --- package.json | 3 ++- src/routes/index.js | 47 +++++++++++++++++++++++++++++++++++++++++++++ src/server.js | 10 ++-------- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 2df23f9..a42f85f 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ }, "devDependencies": {}, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "nodemon src/server.js" }, "repository": { "type": "git", diff --git a/src/routes/index.js b/src/routes/index.js index e69de29..51d34c0 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -0,0 +1,47 @@ +// src/routes/index.js +const router = require('express').Router(); + +// Totally fake data +const FILES = [ + {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, + {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, + {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, + {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, +]; + + +router.use('/doc', function(req, res, next) { + res.end(`Documentation http://expressjs.com/`); +}); + +router.get('/file', function(req, res, next) { + res.json(FILES); +}); + +router.get('/file/:fileId', function(req, res, next) { + const {fileId} = req.params; + // same as 'const fileId = req.params.fileId' + + const file = FILES.find(entry => entry.id === fileId); + if (!file) { + return res.status(404).end(`Could not find file '${fileId}'`); + } + + res.json(file); +}); + +router.post('/file', function(req, res, next) { + res.end('Create a new file'); +}); + +router.put('/file/:fileId', function(req, res, next) { + res.end(`Updating file '${req.params.fileId}'`); +}); + +router.delete('/file/:fileId', function(req, res, next) { + res.end(`Deleting file '${req.params.fileId}'`); +}); + + +module.exports = router; + diff --git a/src/server.js b/src/server.js index 5265a83..c958b92 100644 --- a/src/server.js +++ b/src/server.js @@ -3,20 +3,14 @@ const path = require('path'); const express = require('express'); const config = require('./config'); +const router = require('./routes'); const app = express(); const publicPath = path.resolve(__dirname, '../public'); app.use(express.static(publicPath)); +app.use('/api', router); -app.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); - -app.use(function(req, res, next) { - res.end("Hello World!"); -}); - app.listen(config.port, function() { console.log(`${config.appName} is listening on port ${config.port}`); }); From 2d6c7edfdba4b5a6d20c06aef405539423016a0a Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 8 Feb 2018 15:37:19 -0500 Subject: [PATCH 13/18] update git instructions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec12934..532ac4b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ## Set up the project ``` -git clone week4 +git checkout -b week4 origin/week4 npm install ``` @@ -129,4 +129,4 @@ router.put('/file/:fileId', function(req, res, next) { file.description = req.body.description; res.json(file); }); -``` \ No newline at end of file +``` From e8b7628c43a52e8f778755ef31789364e6a40ca1 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 15 Feb 2018 04:46:16 -0500 Subject: [PATCH 14/18] updated week5 readme --- .DS_Store | Bin 6148 -> 6148 bytes README.md | 285 ++++++++++++++++++++++++++++------------------ mongoose_diag.png | Bin 0 -> 30225 bytes public/blarg.html | 0 public/js/app.js | 28 +++++ 5 files changed, 201 insertions(+), 112 deletions(-) create mode 100644 mongoose_diag.png create mode 100644 public/blarg.html create mode 100644 public/js/app.js diff --git a/.DS_Store b/.DS_Store index d52ed8ea76e1de4baf7ad425ad022b88c1c62375..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 100644 GIT binary patch delta 67 zcmZoMXfc=|&Zs)EP;8=}A_oHyFfuR*Y}^>eKJh@*W_At%4o20D8^1G8<`+@q1WGX^ TfYeMj;Zfe4AhLvcVgm~RE=&+7 delta 115 zcmZoMXfc=|&e%3FQEZ}~qA()^0|O8XFfbUE1sCPz -``` +### Option 2 - Install mongo on your machine -3. Update our handlebars template to render a file and not a BTVS character list. Replace the `#list-template` script in `index.html` with the following: -```html - -``` -While we're at it, we can let's delete the code that rendered our previous test data. (If you want to keep it around for reference, just comment it out.) - -4. In `app.js`, create a function to get the file list: -```javascript -function getFiles() { - return $.ajax('/api/file') - .then(res => { - console.log("Results from getFiles()", res); - return res; - }) - .fail(err => { - console.log("Error in getFiles()", err); - throw err; - }); -} -``` +**Windows**: https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/ -5. Create a function to refresh the list -```javascript -function refreshFileList() { - const template = $('#list-template').html(); - const compiledTemplate = Handlebars.compile(template); - - getFiles() - .then(files => { - const data = {files: files}; - const html = compiledTemplate(data); - $('#list-container').html(html); - }) -} -``` -Test it out by refreshing the page, opening a debugging console, and typing `refreshFileList()`; +**Linux** + https://docs.mongodb.com/manual/administration/install-on-linux/ -6. Refresh the list automatically when the page first loads by adding `refreshFileList()` to the remaining `$().ready()` function in `index.html` +**OSX** + https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/ -## Finish What we started -1. We are going to be sending data from the client back to the server. To do that, we will convert a plain JS object to a JSON-formatted string (really, jQuery will do that for us). We need to set up our express server to parse that JSON string and turn it back in to an object. -Fortunately, there a library for that: +## 2. Setup Project +1. Clear changes made last week ``` -npm install body-parser --save +git reset --hard HEAD ``` -[[Documentation for body-parser](https://github.com/expressjs/body-parser)] - -`body-parser` will look at the body of a request and, if the `Content-Type` is `application/json`, will parse the body using `JSON.parse()`. The results of that (if successful) will be put in `req.body` for use by any middleware. -2. At the top of `server.js`, require the body-parser module: -```javascript -const bodyParser = require('body-parser'); +2. Check out a clean week5 ``` - -3. Tell our server to use it. In `server.js`, right AFTER we set up static files serving, add the following: -```javascript -app.use(function(req, res, next) { - console.log("req.body BEFORE parsing", req.body); - next(); -}) - -app.use(bodyParser.json()); - -app.use(function(req, res, next) { - console.log("req.body AFTER parsing", req.body); - next(); -}) +git checkout -b week4 origin/week5 +git pull +npm install ``` -Head over to postman. Create a POST request to ANY endpoint. Tell postman that the content is JSON (use the dropdown). Type in any valid JSON-formatted string and hit send. You'll see the contents outputted by the two middleware we added before and after the bodyParser middleware. - -4. Delete the logging middleware -5. Go back to our routes and add the POST and PUT endpoints. In `routes/index.js`, swap out the `router.put()` and `router.post()` callbacks (which were just placeholders) with the following: -```javascript -router.post('/file', function(req, res, next) { - const newId = '' + FILES.length; - const data = req.body; - data.id = newId; - - FILES.push(data); - res.status(201).json(data); -}); +3. Install mongoose ``` -and -```javascript -router.put('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - file.title = req.body.title; - file.description = req.body.description; - res.json(file); -}); +npm install mongoose --save ``` +**Mongoose Documentation:** http://mongoosejs.com/docs/api.html + +## HOLD THE PHONE... +**What is Mongo? Sounds like a cartoon character's name...** + +Mongo is a database. It is a place to store structured data so that your application can quickly and easily find it later. Mongo is known as a no-SQL database. In the case of Mongo, that means that it stores data in units called `documents` - which look just like javascript objects (key-value pairs, nested objects, arrays, etc.). + +## WAIT A MINUTE.... +**What is this `mongoose` of which you speak?** + +Mongoose is an ORM (Object Relational Mapping) tool. It is used in your application to make the process of querying, inserting, updating, and deleting data in a Mongo database. In addition, it turns the plain ol' javascript objects you get back from Mongo in to more feature-rich objects for your application to use. + +![Mongoose Diagram](mongoose_diag.png) + + +## Create a model using mongoose + +**In a nutshell, we will:** +1. Tell mongoose how to talk to the mongo server +2. Make sure mongoose connects to mongo when your application starts. +3. Create a "model" in mongoose. This is where you define what your data looks like. +4. Use Mongo in our route handlers instead of the array we've been using. +5. Add some test data. + + +### Configure our app to work with mongo +1. Edit our config file (at `src/config/index.js`) so that the returned configuration object includes mongo configuration: + ```javascript + module.exports = { + appName: 'Our Glorious Node Project', + port: 3030, + db: { + username: , + password: , + host: 'ds159507.mlab.com:59507', + dbName: 'fsjs-class-project', + } + }; + ``` + +2. Connect to mongo through the mongoose library. In `src/server.js`, somewhere near the top of the file, import mongoose with the following. Note that when we connect to the mongo server, we are piecing together the connection string handed to us by mLab. + ```javascript + // Load mongoose package + const mongoose = require('mongoose'); + ``` + Then, somewhere AFTER the line where you load your configuration, connect with the following + ```javascript + // Connect to MongoDB and create/use database as configured + mongoose.connection.openUri(`mongodb://${config.db.username}:${config.db.password}@${config.db.host}/${config.db.dbName}`); + ``` + + +### Build the model + +1. In the `src/models` directory, create an empty file called `file.model.js` +2. At the top of that file, pull in mongoose + ```javascript + // Load mongoose package + const mongoose = require('mongoose'); + ``` + +3. Create a schema + ```javascript + const FileSchema = new mongoose.Schema({ + title: String, + description: String, + created_at: { type: Date, default: Date.now }, + }); + ``` + Notice that the `title` and `description` fields are also present in our faked data (`/src/routes/index.js`). We've also added a new field called `created_at`, which will be a Date and will default to the current time. + +4. Turn that schema in to a mongoose model, register it, and export it + ```javascript + const File = mongoose.model('File', FileSchema); + module.exports = File; + ``` + A lot is going on here. We are storing the `File` schema inside the mongoose object (which will make it available anywhere in your application). We're also giving a name ("File") so we can distinguish it from any other model we may want to register. We're also exporting the model from this module. + +5. Make sure that the `file.model.js` script is run by `require`-ing it somewhere...like in `src/server.js`, below the line where we connect mongoose to mongo: + ```javascript + // Import all models + require('./models/file.model.js'); + ``` + +## Connect to our app +1. In `src/routes/index.js`, pull in mongoose at the top of the file. + ```javascript + const mongoose = require('mongoose'); + ``` + +2. Edit the `GET /file` route. Replace our development code with + ```javascript + mongoose.model('File').find({}, function(err, files) { + if (err) { + console.log(err); + return res.status(500).json(err); + } + + res.json(files); + }); + ``` + **Model.find:** http://mongoosejs.com/docs/api.html#model_Model.find + +3. Restart server and test - **Where did our data go?** + +### What about some test data? +Strategy: On startup, check if there are any files in the database, if not, then add files from a seed file. + +1. Create a file in `/src/models` called `file.seed.json` + ```json + [ + {"title":"Satellite of Love Plans.svg", "description": "Includes fix for exhaust port vulnerability" }, + {"title":"Rules of Cribbage.doc", "description": "9th edition" }, + {"title":"avengers_fanfic.txt", "description": "PRIVATE DO NOT READ" } + ] + ``` + +2. In `file.model.js`, after you create and export the model, get the current count of documents in the collection + ```javascript + File.count({}, function(err, count) { + if (err) { + throw err; + } + // ... + }); + ``` + **Model.count:** http://mongoosejs.com/docs/api.html#model_Model.count + +3. Add the seed data + ```javascript + if (count > 0) return ; + + const files = require('./file.seed.json'); + File.create(files, function(err, newFiles) { + if (err) { + throw err; + } + console.log("DB seeded") + }); + ``` + **Model.create:** http://mongoosejs.com/docs/api.html#model_Model.create \ No newline at end of file diff --git a/mongoose_diag.png b/mongoose_diag.png new file mode 100644 index 0000000000000000000000000000000000000000..c40ad4c90b293a85e5b2526e60791ec0ba2a813f GIT binary patch literal 30225 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 QdL`1=KC4xsaXIAw05~Q<_W%F@ literal 0 HcmV?d00001 diff --git a/public/blarg.html b/public/blarg.html new file mode 100644 index 0000000..e69de29 diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..2e2aa62 --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,28 @@ + +function getFiles() { + return $.ajax('/api/file') + .then(res => { + console.log("Results from getFiles()", res); + return res; + }) + .fail(err => { + console.error("Error in getFiles()", err); + throw err; + }); +} + +function refreshFileList() { + const template = $('#list-template').html(); + const compiledTemplate = Handlebars.compile(template); + + getFiles() + .then(bob => { + const data = {files: bob}; + const html = compiledTemplate(data); + console.log('our html', html); + $('#list-container').html(html); + }) +} + + +refreshFileList(); From 903cbbf0c81dbe82ac2c6c1c8866bece3d95278d Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 15 Feb 2018 04:53:46 -0500 Subject: [PATCH 15/18] added work from previous week --- package-lock.json | 4 +++- package.json | 1 + public/blarg.html | 0 public/index.html | 45 +++++++++------------------------------------ public/js/app.js | 4 ++-- src/routes/index.js | 22 +++++++++++++++++++++- src/server.js | 2 ++ 7 files changed, 38 insertions(+), 40 deletions(-) delete mode 100644 public/blarg.html diff --git a/package-lock.json b/package-lock.json index e2c6139..53bf028 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,8 @@ { - "requires": true, + "name": "fsjs-class-project", + "version": "1.0.0", "lockfileVersion": 1, + "requires": true, "dependencies": { "accepts": { "version": "1.3.4", diff --git a/package.json b/package.json index a42f85f..5e01f12 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "## Install NodeJS", "main": "index.js", "dependencies": { + "body-parser": "^1.18.2", "express": "^4.16.2" }, "devDependencies": {}, diff --git a/public/blarg.html b/public/blarg.html deleted file mode 100644 index e69de29..0000000 diff --git a/public/index.html b/public/index.html index 5af53ac..972d9d1 100644 --- a/public/index.html +++ b/public/index.html @@ -13,45 +13,18 @@

    A wild webpage appears...

    - + - - - - + \ No newline at end of file diff --git a/public/js/app.js b/public/js/app.js index 2e2aa62..3c65e09 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -16,8 +16,8 @@ function refreshFileList() { const compiledTemplate = Handlebars.compile(template); getFiles() - .then(bob => { - const data = {files: bob}; + .then(files => { + const data = {files: files}; const html = compiledTemplate(data); console.log('our html', html); $('#list-container').html(html); diff --git a/src/routes/index.js b/src/routes/index.js index 51d34c0..0aac3c6 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -14,10 +14,16 @@ router.use('/doc', function(req, res, next) { res.end(`Documentation http://expressjs.com/`); }); +/** + * Get a list of all files in the DB + */ router.get('/file', function(req, res, next) { res.json(FILES); }); +/** + * Get a single file by passing its id as a URL param + */ router.get('/file/:fileId', function(req, res, next) { const {fileId} = req.params; // same as 'const fileId = req.params.fileId' @@ -30,14 +36,28 @@ router.get('/file/:fileId', function(req, res, next) { res.json(file); }); +/** + * Create a new file + */ router.post('/file', function(req, res, next) { - res.end('Create a new file'); + const newId = '' + FILES.length; + const data = req.body; + data.id = newId; + + FILES.push(data); + res.status(201).json(data); }); +/** + * Update an existing file + */ router.put('/file/:fileId', function(req, res, next) { res.end(`Updating file '${req.params.fileId}'`); }); +/** + * Delete a file + */ router.delete('/file/:fileId', function(req, res, next) { res.end(`Deleting file '${req.params.fileId}'`); }); diff --git a/src/server.js b/src/server.js index c958b92..f376394 100644 --- a/src/server.js +++ b/src/server.js @@ -2,11 +2,13 @@ const path = require('path'); const express = require('express'); +const bodyParser = require('body-parser'); const config = require('./config'); const router = require('./routes'); const app = express(); const publicPath = path.resolve(__dirname, '../public'); +app.use(bodyParser.json()); app.use(express.static(publicPath)); app.use('/api', router); From 92a3be0899956f40decc5ad0dc4f63167e061b68 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 15 Feb 2018 04:59:16 -0500 Subject: [PATCH 16/18] add forced checkout to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f4b189..277e512 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ git reset --hard HEAD 2. Check out a clean week5 ``` -git checkout -b week4 origin/week5 +git checkout -bf week5 origin/week5 git pull npm install ``` From 49754c3550024c228355cb273bbcabdef93fc514 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 15 Feb 2018 18:01:07 -0500 Subject: [PATCH 17/18] Fix the git fetch/checkout issue --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 277e512..fddd612 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ git reset --hard HEAD 2. Check out a clean week5 ``` -git checkout -bf week5 origin/week5 +git fetch +git checkout -fb week5 origin/week5 git pull npm install ``` @@ -190,4 +191,4 @@ Strategy: On startup, check if there are any files in the database, if not, then console.log("DB seeded") }); ``` - **Model.create:** http://mongoosejs.com/docs/api.html#model_Model.create \ No newline at end of file + **Model.create:** http://mongoosejs.com/docs/api.html#model_Model.create From 985ce4b4fe1241773381c3e7e87eb7c038e22b41 Mon Sep 17 00:00:00 2001 From: Serra Doll Date: Thu, 15 Feb 2018 19:26:13 -0500 Subject: [PATCH 18/18] Update REAME to include further mLab instructions --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fddd612..7045d73 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ * Choose a name...could be anything...maybe "fsjs-class-project" * Complete setup 4. Click on your new database in the list and note the connection links provided +5. You will see an alert box saying "A database user is required to connect to this database." - so under the "Users" tab click the "Add database user" (you can use your mLab login information if you wish) **This will be the username and password we use later in the code** ### Option 2 - Install mongo on your machine

    - - ... - ``` - - ```javascript - function editFileClick(id) { - const file = window.fileList.find(file => file._id === id); - if (file) { - $('#file-title').val(file.title); - $('#file-description').val(file.description); - $('#file-id').val(file._id); - toggleAddFileFormVisibility(); - } - } - ``` - -8. Problem, what if we click `Edit`, close the form and then try to add a new File? When we click `Add File` we expect to see a blank form, but instead we see data from the previously edited File. Since we are reusing this form, we need a way to clear the data. Let's pull all the logic for setting a form's data out in to it's own function. - ```javascript - function setFormData(data) { - data = data || {}; - - const file = { - title: data.title || '', - description: data.description || '', - _id: data._id || '', - }; - - $('#file-title').val(file.title); - $('#file-description').val(file.description); - $('#file-id').val(file._id); - } - ``` - Now, we use that function in `editFileClick()` and `toggleAddFileForm()` - - ```javascript - function toggleAddFileForm() { - console.log("Baby steps..."); - setFormData({}); - toggleAddFileFormVisibility(); - } - ``` - ```javascript - function editFileClick(id) { - const file = window.fileList.find(file => file._id === id); - if (file) { - setFormData(file); - toggleAddFileFormVisibility(); - } - } - ``` - -## Push our changes to the server. - - Most of the hard work on the front-end has been done. Really, the only difference between creating a new file and editing an existing one is that when creating, we `POST` to the server and we don't have an `_id` field, while when editing, we `PUT` to the server AND the URL is slightly different (we add the `_id` field to the url). - - We can accomplish this by checking to see if `#file-id` has a value. If it does, we are editing, if it doesn't we are creating. - -1. In `submitFileForm()` get the `#file-id` value and check if we are PUTting or POSTing - ```javascript - function submitFileForm() { - console.log("You clicked 'submit'. Congratulations."); - - const fileData = { - title: $('#file-title').val(), - description: $('#file-description').val(), - _id: $('#file-id').val(), - }; - - let method, url; - if (fileData._id) { - method = 'PUT'; - url = '/api/file/' + fileData._id; - } else { - method = 'POST'; - url = '/api/file'; - } - - $.ajax({ - type: method, - url: url, - data: JSON.stringify(fileData), - dataType: 'json', - contentType : 'application/json', - }) - .done(function(response) { - console.log("We have posted the data"); - refreshFileList(); - toggleAddFileForm(); - }) - .fail(function(error) { - console.log("Failures at posting, we are", error); - }) - - console.log("Your file data", fileData); - } - ``` - That should do it, but when we try to submit an error, we get a 404 response. We have to create the PUT endpoint. - - -## Handle that PUT - -1. In `/src/routes/index.js`, clear out our previous, Array-based `PUT /api/file/:fileId` route: - ```javascript - router.put('/file/:fileId', function(req, res, next) { - - }); - ``` - -2. Our strategy here is to find the file we're trying to edit in the database, then edit it, then save it - ```javascript - router.put('/file/:fileId', function(req, res, next) { - const File = mongoose.model('File'); - const fileId = req.params.fileId; - - File.findById(fileId, function(err, file) { - if (err) { - console.error(err); - return res.status(500).json(err); - } - if (!file) { - return res.status(404).json({message: "File not found"}); - } - - file.title = req.body.title; - file.description = req.body.description; - - file.save(function(err, savedFile) { - if (err) { - console.error(err); - return res.status(500).json(err); - } - res.json(savedFile); - }) - - }) -}); -``` diff --git a/week7/src/config/index.js b/week7/src/config/index.js deleted file mode 100644 index 8b6e7d5..0000000 --- a/week7/src/config/index.js +++ /dev/null @@ -1,10 +0,0 @@ -// src/config/index.js - -module.exports = { - appName: 'Our Glorious Node Project', - port: 3030, - db: { - host: 'localhost', - dbName: 'fsjs', - } -}; diff --git a/week7/src/models/file.model.js b/week7/src/models/file.model.js deleted file mode 100644 index 5166098..0000000 --- a/week7/src/models/file.model.js +++ /dev/null @@ -1,29 +0,0 @@ -// Load mongoose package -const mongoose = require('mongoose'); - -const FileSchema = new mongoose.Schema({ - title: String, - description: String, - created_at: { type: Date, default: Date.now }, -}); - - -const File = mongoose.model('File', FileSchema); -module.exports = File; - - -File.count({}, function(err, count) { - if (err) { - throw err; - } - - if (count > 0) return ; - - const files = require('./file.seed.json'); - File.create(files, function(err, newFiles) { - if (err) { - throw err; - } - console.log("DB seeded") - }); -}); diff --git a/week7/src/models/file.seed.json b/week7/src/models/file.seed.json deleted file mode 100644 index 6b401c1..0000000 --- a/week7/src/models/file.seed.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - {"title":"Satellite of Love Plans.svg", "description": "Includes fix for exhaust port vulnerability" }, - {"title":"Rules of Cribbage.doc", "description": "9th edition" }, - {"title":"avengers_fanfic.txt", "description": "PRIVATE DO NOT READ" } -] diff --git a/week7/src/models/index.js b/week7/src/models/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/week7/src/routes/index.js b/week7/src/routes/index.js deleted file mode 100644 index a99d9c6..0000000 --- a/week7/src/routes/index.js +++ /dev/null @@ -1,89 +0,0 @@ -// src/routes/index.js -const router = require('express').Router(); -const mongoose = require('mongoose'); - -const FILES = [ - {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, - {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, - {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, - {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, -]; - - -router.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); - -router.get('/file', function(req, res, next) { - mongoose.model('File').find({}, function(err, files) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - - res.json(files); - }); -}); - - -router.post('/file', function(req, res, next) { - const File = mongoose.model('File'); - const fileData = { - title: req.body.title, - description: req.body.description, - }; - - File.create(fileData, function(err, newFile) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - - res.json(newFile); - }); -}); - -router.put('/file/:fileId', function(req, res, next) { - const File = mongoose.model('File'); - const fileId = req.params.fileId; - - File.findById(fileId, function(err, file) { - if (err) { - console.error(err); - return res.status(500).json(err); - } - if (!file) { - return res.status(404).json({message: "File not found"}); - } - - file.title = req.body.title; - file.description = req.body.description; - - file.save(function(err, savedFile) { - if (err) { - console.error(err); - return res.status(500).json(err); - } - res.json(savedFile); - }) - - }) -}); - -router.delete('/file/:fileId', function(req, res, next) { - res.end(`Deleting file '${req.params.fileId}'`); -}); - -router.get('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - // same as 'const fileId = req.params.fileId' - - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - res.json(file); -}); - -module.exports = router; diff --git a/week7/src/server.js b/week7/src/server.js deleted file mode 100644 index fa63e81..0000000 --- a/week7/src/server.js +++ /dev/null @@ -1,27 +0,0 @@ -// src/server.js -const path = require('path'); -const bodyParser = require('body-parser'); - -// Load mongoose package -const mongoose = require('mongoose'); - -const express = require('express'); -const config = require('./config'); -const router = require('./routes'); - -// Connect to MongoDB and create/use database as configured -mongoose.connect(`mongodb://${config.db.host}/${config.db.dbName}`); - -// Import all models -require('./models/file.model.js'); - -const app = express(); -const publicPath = path.resolve(__dirname, '../public'); -app.use(express.static(publicPath)); -app.use(bodyParser.json()); -app.use('/api', router); - - -app.listen(config.port, function() { - console.log(`${config.appName} is listening on port ${config.port}`); -}); diff --git a/week7/src/tester/index.html b/week7/src/tester/index.html deleted file mode 100644 index 2b263fa..0000000 --- a/week7/src/tester/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - sdfjhs - - - SKDJHFLKSJDHFLKSDJ - - diff --git a/week8/index.js b/week8/index.js deleted file mode 100644 index 440ef30..0000000 --- a/week8/index.js +++ /dev/null @@ -1 +0,0 @@ -require('./src/server'); diff --git a/week8/mongoose_diag.png b/week8/mongoose_diag.png deleted file mode 100644 index c40ad4c90b293a85e5b2526e60791ec0ba2a813f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30225 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 QdL`1=KC4xsaXIAw05~Q<_W%F@ diff --git a/week8/package.json b/week8/package.json deleted file mode 100644 index 3117c53..0000000 --- a/week8/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "cl_project", - "version": "1.0.0", - "description": "This is a sample node project", - "main": "index.js", - "scripts": { - "start": "nodemon index.js" - }, - "keywords": [ - "code", - "louisville", - "sample", - "project" - ], - "author": "Aaron W. Johnson", - "license": "ISC", - "dependencies": { - "body-parser": "^1.17.2", - "express": "^4.14.0", - "mongoose": "^4.10.4" - } -} diff --git a/week8/public/index.html b/week8/public/index.html deleted file mode 100644 index f2f8964..0000000 --- a/week8/public/index.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Our Glorious Node Project - - - - -
    -

    A wild webpage appears...

    -
    - - -
    - - - - - - - - - diff --git a/week8/public/js/app.js b/week8/public/js/app.js deleted file mode 100644 index 911d7e1..0000000 --- a/week8/public/js/app.js +++ /dev/null @@ -1,122 +0,0 @@ - - -function getFiles() { - return $.ajax('/api/file') - .then(res => { - console.log("Results from getFiles()", res); - return res; - }) - .fail(err => { - console.log("Error in getFiles()", err); - throw err; - }); -} - - -function refreshFileList() { - const template = $('#list-template').html(); - const compiledTemplate = Handlebars.compile(template); - - getFiles() - .then(files => { - - window.fileList = files; - - const data = {files: files}; - const html = compiledTemplate(data); - $('#list-container').html(html); - }) -} - - -function toggleAddFileForm() { - console.log("Baby steps..."); - setFormData({}); - toggleAddFileFormVisibility(); -} - -function toggleAddFileFormVisibility() { - $('#form-container').toggleClass('hidden'); -} - -function submitFileForm() { - console.log("You clicked 'submit'. Congratulations."); - - const fileData = { - title: $('#file-title').val(), - description: $('#file-description').val(), - _id: $('#file-id').val(), - }; - - let method, url; - if (fileData._id) { - method = 'PUT'; - url = '/api/file/' + fileData._id; - } else { - method = 'POST'; - url = '/api/file'; - } - - $.ajax({ - type: method, - url: url, - data: JSON.stringify(fileData), - dataType: 'json', - contentType : 'application/json', - }) - .done(function(response) { - console.log("We have posted the data"); - refreshFileList(); - toggleAddFileForm(); - }) - .fail(function(error) { - console.log("Failures at posting, we are", error); - }) - - console.log("Your file data", fileData); -} - -function cancelFileForm() { - toggleAddFileFormVisibility(); -} - - -function editFileClick(id) { - const file = window.fileList.find(file => file._id === id); - if (file) { - setFormData(file); - toggleAddFileFormVisibility(); - } -} - -function deleteFileClick(id) { - if (confirm("Are you sure?")) { - $.ajax({ - type: 'DELETE', - url: '/api/file/' + id, - dataType: 'json', - contentType : 'application/json', - }) - .done(function(response) { - console.log("File", id, "is DOOMED!!!!!!"); - refreshFileList(); - }) - .fail(function(error) { - console.log("I'm not dead yet!", error); - }) - } -} - -function setFormData(data) { - data = data || {}; - - const file = { - title: data.title || '', - description: data.description || '', - _id: data._id || '', - }; - - $('#file-title').val(file.title); - $('#file-description').val(file.description); - $('#file-id').val(file._id); -} diff --git a/week8/readme.md b/week8/readme.md deleted file mode 100644 index 2b45a69..0000000 --- a/week8/readme.md +++ /dev/null @@ -1,147 +0,0 @@ -# FSJS Week 8 - Delete the negative; Accentuate the positive! - -**Outline** - -1. Set up for week8 -2. What is a soft delete? -3. Update our model and handlers -4. Delete handler -5. Baby-step our way to a delete button - - -## 1. Setup Project -1. (optional) Clone the project -``` -git clone https://github.com/CodeLouisville/FSJS-class-project.git -cd FSJS-class-project -``` - - **OR** if you need to ditch your current changes and pull a fresh copy down, try this: - ``` - git stash - git pull - ``` - -2. Get rid of `week8` (we're going to rebuild it) -``` -rm -rf week8 -``` - -3. Copy `week7` to `week8` -``` -cp -R week7 week8 -cd week8 -``` - -4. Install dependencies -``` -npm install -``` - -## 2. What is a soft delete - -**Deleting Can Be Hazardous to your Health** -It is difficult to make deleting (as in, actually removing something from the database) save. Meaning, if you give users the ability to destroy data, eventually they will do it by accident. Without some means of recovering that data, deleting can make managers and clients sad. - -**So Don't Do It** -Simply mark a database entry as `deleted` instead. That way, you can exclude "deleted" items from your searches AND recover those items (undelete with ease). - -## 3. So let's apply that idea to our model -1. In `/src/models/file.model.js`, update our model so that it has a `deleted` field: -```javascript -const FileSchema = new mongoose.Schema({ - title: String, - description: String, - created_at: { type: Date, default: Date.now }, - deleted: {type: Boolean, default: false} -}); -``` - -2. Now make sure that our route handlers know to exclude "deleted" items. In `src/routes/index.js` update the `GET /file` handler: -```javascript -router.get('/file', function(req, res, next) { - mongoose.model('File').find({deleted: {$ne: true}}, function(err, files) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - - res.json(files); - }); -}); -``` -So, why not just `{deleted: false}`? -[Documentation for Mongo's $ne operator](https://docs.mongodb.com/manual/reference/operator/query/ne/) - -## 4. Let's do some deleting -1. We can now update the `DELETE /file/:fileId` handler in `src/routes/index.js` to actually do something. Since we aren't removing the file, "deleting" will basically be updating the file. In other works `DELETE /file/:fileId` will look really similar to `PUT /file/:fileId`: -```javascript -router.delete('/file/:fileId', function(req, res, next) { - const File = mongoose.model('File'); - const fileId = req.params.fileId; - - File.findById(fileId, function(err, file) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - if (!file) { - return res.status(404).json({message: "File not found"}); - } - - file.deleted = true; - - file.save(function(err, doomedFile) { - res.json(doomedFile); - }) - - }) -}); -``` -How would you implement an `undelete` operation? - -## 5. Baby-step our way through the front-end stuff -1. Make a "Delete" button appear by each file, just next to the "Edit" button. Open `public/index.html` and add this just after the edit button -```html - -``` - -2. Now make it do something by adding an `onclick` handler (we can copy/paste from the "Edit" button and then change it to suit our needs): -```html - -``` - -3. Create the `deleteFileClick()` function in `public/js/app.js`: -```javascript -function deleteFileClick(id) { - console.log("File", id, "is DOOMED!!!!!!"); -} -``` - -4. Er....maybe we should ask for confirmation before doing this: -```javascript -function deleteFileClick(id) { - console.log("File", id, "is DOOMED!!!!!!"); -} -``` - -5. OK, now send the `DELETE` message to `/file/:fileId`. (We can look at `submitFileForm()` to remind ourselves how to do it): -```javascript -function deleteFileClick(id) { - if (confirm("Are you sure?")) { - $.ajax({ - type: 'DELETE', - url: '/api/file/' + id, - dataType: 'json', - contentType : 'application/json', - }) - .done(function(response) { - console.log("File", id, "is DOOMED!!!!!!"); - refreshFileList(); - }) - .fail(function(error) { - console.log("I'm not dead yet!", error); - }) - } -} -``` diff --git a/week8/src/config/index.js b/week8/src/config/index.js deleted file mode 100644 index 8b6e7d5..0000000 --- a/week8/src/config/index.js +++ /dev/null @@ -1,10 +0,0 @@ -// src/config/index.js - -module.exports = { - appName: 'Our Glorious Node Project', - port: 3030, - db: { - host: 'localhost', - dbName: 'fsjs', - } -}; diff --git a/week8/src/models/file.model.js b/week8/src/models/file.model.js deleted file mode 100644 index 6fea2dc..0000000 --- a/week8/src/models/file.model.js +++ /dev/null @@ -1,30 +0,0 @@ -// Load mongoose package -const mongoose = require('mongoose'); - -const FileSchema = new mongoose.Schema({ - title: String, - description: String, - created_at: { type: Date, default: Date.now }, - deleted: {type: Boolean, default: false} -}); - - -const File = mongoose.model('File', FileSchema); -module.exports = File; - - -File.count({}, function(err, count) { - if (err) { - throw err; - } - - if (count > 0) return ; - - const files = require('./file.seed.json'); - File.create(files, function(err, newFiles) { - if (err) { - throw err; - } - console.log("DB seeded") - }); -}); diff --git a/week8/src/models/file.seed.json b/week8/src/models/file.seed.json deleted file mode 100644 index 6b401c1..0000000 --- a/week8/src/models/file.seed.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - {"title":"Satellite of Love Plans.svg", "description": "Includes fix for exhaust port vulnerability" }, - {"title":"Rules of Cribbage.doc", "description": "9th edition" }, - {"title":"avengers_fanfic.txt", "description": "PRIVATE DO NOT READ" } -] diff --git a/week8/src/models/index.js b/week8/src/models/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/week8/src/routes/index.js b/week8/src/routes/index.js deleted file mode 100644 index eb06892..0000000 --- a/week8/src/routes/index.js +++ /dev/null @@ -1,107 +0,0 @@ -// src/routes/index.js -const router = require('express').Router(); -const mongoose = require('mongoose'); - -const FILES = [ - {id: 'a', title: 'cutecat1.jpg', description: 'A cute cat'}, - {id: 'b', title: 'uglycat1.jpg', description: 'Just kidding, all cats are cute'}, - {id: 'c', title: 'total_recall_poster.jpg', description: 'Quaid, start the reactor...'}, - {id: 'd', title: 'louisville_coffee.txt', description: 'Coffee shop ratings'}, -]; - - -router.use('/doc', function(req, res, next) { - res.end(`Documentation http://expressjs.com/`); -}); - -router.get('/file', function(req, res, next) { - mongoose.model('File').find({deleted: {$ne: true}}, function(err, files) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - - res.json(files); - }); -}); - - -router.post('/file', function(req, res, next) { - const File = mongoose.model('File'); - const fileData = { - title: req.body.title, - description: req.body.description, - }; - - File.create(fileData, function(err, newFile) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - - res.json(newFile); - }); -}); - -router.put('/file/:fileId', function(req, res, next) { - const File = mongoose.model('File'); - const fileId = req.params.fileId; - - File.findById(fileId, function(err, file) { - if (err) { - console.error(err); - return res.status(500).json(err); - } - if (!file) { - return res.status(404).json({message: "File not found"}); - } - - file.title = req.body.title; - file.description = req.body.description; - - file.save(function(err, savedFile) { - if (err) { - console.error(err); - return res.status(500).json(err); - } - res.json(savedFile); - }) - - }) -}); - -router.delete('/file/:fileId', function(req, res, next) { - const File = mongoose.model('File'); - const fileId = req.params.fileId; - - File.findById(fileId, function(err, file) { - if (err) { - console.log(err); - return res.status(500).json(err); - } - if (!file) { - return res.status(404).json({message: "File not found"}); - } - - file.deleted = true; - - file.save(function(err, doomedFile) { - res.json(doomedFile); - }) - - }) -}); - -router.get('/file/:fileId', function(req, res, next) { - const {fileId} = req.params; - // same as 'const fileId = req.params.fileId' - - const file = FILES.find(entry => entry.id === fileId); - if (!file) { - return res.status(404).end(`Could not find file '${fileId}'`); - } - - res.json(file); -}); - -module.exports = router; diff --git a/week8/src/server.js b/week8/src/server.js deleted file mode 100644 index fa63e81..0000000 --- a/week8/src/server.js +++ /dev/null @@ -1,27 +0,0 @@ -// src/server.js -const path = require('path'); -const bodyParser = require('body-parser'); - -// Load mongoose package -const mongoose = require('mongoose'); - -const express = require('express'); -const config = require('./config'); -const router = require('./routes'); - -// Connect to MongoDB and create/use database as configured -mongoose.connect(`mongodb://${config.db.host}/${config.db.dbName}`); - -// Import all models -require('./models/file.model.js'); - -const app = express(); -const publicPath = path.resolve(__dirname, '../public'); -app.use(express.static(publicPath)); -app.use(bodyParser.json()); -app.use('/api', router); - - -app.listen(config.port, function() { - console.log(`${config.appName} is listening on port ${config.port}`); -}); diff --git a/week8/src/tester/index.html b/week8/src/tester/index.html deleted file mode 100644 index 2b263fa..0000000 --- a/week8/src/tester/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - sdfjhs - - - SKDJHFLKSJDHFLKSDJ - - diff --git a/z-deploy/README.md b/z-deploy/README.md deleted file mode 100644 index e85c4d6..0000000 --- a/z-deploy/README.md +++ /dev/null @@ -1,144 +0,0 @@ -# How to Deploy your Shiny new Node App - -Got something working? Want to show someone? - -Let's put it out in the world. - -## Deploy Mongo at mLab - -Hosted mongodb (free tier = < .5G) -https://mlab.com - -1. Sign up -2. Verify email (required) -3. Create new mongodb deployment -4. Select "Amazon", "Single-node", "Sandbox = Free" -5. Type in a "Database Name" like `fsjs_demo` or your project's name -6. Create button (submit form) - -![ss](https://i.imgur.com/N4NpbON.png) - -Once created, click on your new db. - -Click on `Users` and `Add database user` - -Give it a strong user/pass. _(pro tip, don't include `@` or `:` or `*`)_ - -Eg: `fsjsdb` / `35cqfvmbxpXMw$5mM$$WsJav3JH&` _(not actual value)_ - -Copy the connection string from the top of this page: - -> To connect using a driver via the standard MongoDB URI (what's this?): -> `mongodb://:@ds139242.mlab.com:39242/fsjs_demo` - -Take a note and assemble the connection string in your notes like this (you will use it later): - -`mongodb://fsjsdb:35cqfvmbxpXMw$5mM$$WsJav3JH&@ds139242.mlab.com:39242/fsjs_demo` - -Here's a breakdown: - -> `mongodb://` the protocol -> `fsjsdb` the username who the app can use to access the database -> `:` splits user and pass -> `35cqfvmbxpXMw$5mM$$WsJav3JH&` the user's password -> `@` splits the auth from domain -> `ds139242.mlab.com` the domain name of the database, from mlab -> `:` splits the domain and port -> `39242` the port of the database from mlab -> `/` splits the host from the "path" or in this case the "dbname" -> `fsjs_demo` the name of our database - -## Deploy Node at now - -Super, Crazy, Wildly simple node deployments -https://zeit.co/now#get-started - -1. [install](https://zeit.co/download) their desktop `now` app (convenient, optional, also get their [cli](https://zeit.co/download#command-line)) -2. open the app, walk through the tutorial, install the command line tool, enter your email -3. verify the email, you should get a "hooray" -4. click the `get started` button to show you the UI in the GUI -5. select deploy, browse to your project folder (the one with the `package.json` file in it) - -![ss](https://i.imgur.com/middH4S.png) -![ss](https://i.imgur.com/k8vWtGn.png) -![ss](https://i.imgur.com/NcgnrME.png) - -Now... it deploys... - -### Did you get a build error? - -I did, because `package.json` has `scripts` which has `"start": "nodemon index.js"` - -`nodemon` is a NPM tool we installed onto our machines to make development easier, "fancy start". - -So I changed the `package.json` scripts section to the following, so start is basic, and dev is fancy. - -``` - "scripts": { - "start": "node index.js", - "dev": "nodemon index.js" - }, -``` - -Now you can use nodemon locally with `npm run dev` - -Re-deploy: - -Now you can delete the deployment in now: - -> Now -> Deployments -> -> delete -> confirm - -![ss](https://i.imgur.com/jHJaARx.png) - -Now deploy again. - -### Did you get a build error? - -I did, because the node app can't connect to the mongo database... - -That makes sense, we never told our app where the new database is... - -Edit `src/config/index.js` - -> replace the host, and name database config with the full connection url - -``` -module.exports = { - appName: 'Our Glorious Node Project', - // TODO make this environment aware and switch automatically - // port: 3030, - // mongo_url: 'mongodb://localhost/fsjs', - port: 80, - mongo_url: 'mongodb://fsjsdb:35cqfvmbxpXMw$5mM$$WsJav3JH&@ds139242.mlab.com:39242/fsjs_demo', -}; -``` - -Edit `src/server.js` - -> replace the connection string line to the complete one from the config - -``` -// Connect to MongoDB and create/use database as configured -mongoose.connect(config.mongo_url); -``` - -Re-deploy: - -Now you can delete the deployment in now: - -> Now -> Deployments -> -> delete -> confirm - -![ss](https://i.imgur.com/jHJaARx.png) - -Now deploy again. - - -### Did you get it to work? - -I did. - -![ss](https://puu.sh/wvOwK/fc1132e182.png) - -Bonus points, add `/_src` to the end of the domain name... and you can see your source code - -![ss](https://i.imgur.com/0NWxAAw.png) From feae23249d4e4356c9afc3900b3b5b0b42d0fd64 Mon Sep 17 00:00:00 2001 From: Aaron Johnson Date: Thu, 25 Jan 2018 15:19:26 -0500 Subject: [PATCH 02/18] add week1 readme --- README.md | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 36f0367..2197f6e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,156 @@ -# FSJS-class-project +# FSJS Week 1 - Our Glorious Node Project + +**Outline** + +* Install NodeJS +* Set up the project +* npm init +* Install core packages +* Organize the project +* "Hello World" web server ## Install NodeJS - [Windows (http://blog.teamtreehouse.com/install-node-js-npm-windows)](http://blog.teamtreehouse.com/install-node-js-npm-windows) - [Mac (http://blog.teamtreehouse.com/install-node-js-npm-mac)](http://blog.teamtreehouse.com/install-node-js-npm-mac) - [Linux (http://blog.teamtreehouse.com/install-node-js-npm-linux)](http://blog.teamtreehouse.com/install-node-js-npm-linux) + +## Set up the project +1. Clone the project +``` +git clone https://github.com/CodeLouisville/FSJS-class-project.git +cd FSJS-class-project +``` + +2. Get rid of `week1` (we're going to rebuild it) +``` +rm -rf week1 +``` + +## Start a project with `npm init` + +Starting a project in node is simple: +``` +mkdir week1 +cd week1 +npm init +``` + +`npm init` simply creates a `package.json` file a populates it with the answers to some questions. You can edit it in a text editor. + + +## Install code packages + +First, take a look at `package.json`, then run this command: +``` +npm install express --save +``` + +Now, go back to `package.json` and look at the 'dependencies' section. +Also, a new directory has appeared: `node_modules` + +The above command tells `npm` to download the [express](https://expressjs.com/) package, save it in a newly created `node_modules` directory, and then add a line in `package.json` to make note of the fact that we need 'express' for this project (that's what the `--save` part does). + + +## Organize this thing + +When starting a project, a good practice is to lay out your directory structure and create some empty, basic files: +``` +. +├── package.json +└── src + ├── config // application configuration + │ └── index.js + ├── models // Database models + │ └── index.js + ├── routes // HTTP(S) routing/controllers + │ └── index.js + └── server.js // Set up server and listen on port +``` + +## Hello World + +Open `src/config/index.js` +```javascript +// src/config/index.js + +module.exports = { + appName: 'Our Glorious Node Project', + port: 3030 +} +``` + +Open `src/server.js` +Pull in some needed modules +```javascript +// src/server.js + +const express = require('express'); +const config = require('./config'); +``` + +Create our application object +```javascript +const app = express(); +``` + +Tell it what to do +```javascript +app.use(function(req, res, next) { + res.end("Hello World!"); +}); +``` + +Start the server +```javascript +app.listen(config.port, function() { + console.log(`${config.appName} is listening on port ${config.port}`); +}); +``` + +On the command line: +``` +node src/server.js +``` + + +Test it out by going to `http://localhost:3030` in your browser. + +Now, mix it up a bit. Put this code **BEFORE** the other `app.use` bit. +```javascript +app.use('/doc', function(req, res, next) { + res.end(`Documentation http://expressjs.com/`); +}); +``` + +Visit `http://localhost:3030/doc` +Move the `/doc` route below the original `app.use` code and refresh. What happened? + +## Bonus material + +### require() is a big deal +Yes it is. The full documentation for require() (really, for Node modules in general) can be found [here (https://nodejs.org/api/modules.html)](https://nodejs.org/api/modules.html). + +`require()` is what allows you to organize your code in to easy-to-understand (hopefully) directories and files, but join them all together in to a single application. + +For comparison: in a browser environment, if you want to make content from multiple file available to the larger application you can 1) concatenate them all in to one file or 2) load them individually via a ` + ``` +[[Documentation for jQuery](https://api.jquery.com/)] +[[Documentation for Handlebars](http://handlebarsjs.com/reference.html)] -Visit `http://localhost:3030/doc` -Move the `/doc` route below the original `app.use` code and refresh. What happened? +2. Drop a quick template in `index.html` to see how handlebars renders content: +```html + +``` -### require() is a big deal -Yes it is. The full documentation for require() (really, for Node modules in general) can be found [here (https://nodejs.org/api/modules.html)](https://nodejs.org/api/modules.html). +3. Render a list of fake data. Start by adding a place in the html to render the list after the `