From 49b8c7f82dcbe00a7cb28ae35f5ca6b5d6109cf5 Mon Sep 17 00:00:00 2001 From: Nick Asmussen Date: Wed, 2 Aug 2017 18:26:18 -0700 Subject: [PATCH 1/3] builds out an express api with mongodb --- .eslintrc | 21 ++++++ .gitignore | 1 + README.md | 61 +---------------- lib/error-middleware.js | 21 ++++++ model/band.js | 11 +++ npm-debug.log | 48 +++++++++++++ package.json | 38 +++++++++++ route/band-route.js | 40 +++++++++++ server.js | 27 ++++++++ test/band-route-test.js | 145 ++++++++++++++++++++++++++++++++++++++++ 10 files changed, 353 insertions(+), 60 deletions(-) create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 lib/error-middleware.js create mode 100644 model/band.js create mode 100644 npm-debug.log create mode 100644 package.json create mode 100644 route/band-route.js create mode 100644 server.js create mode 100644 test/band-route-test.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..8dc6807 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,21 @@ +{ + "rules": { + "no-console": "off", + "indent": [ "error", 2 ], + "quotes": [ "error", "single" ], + "semi": ["error", "always"], + "linebreak-style": [ "error", "unix" ] + }, + "env": { + "es6": true, + "node": true, + "mocha": true, + "jasmine": true + }, + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true, + "impliedStrict": true + }, + "extends": "eslint:recommended" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md index 37f3822..ac60011 100644 --- a/README.md +++ b/README.md @@ -1,60 +1 @@ -![CF](https://camo.githubusercontent.com/70edab54bba80edb7493cad3135e9606781cbb6b/687474703a2f2f692e696d6775722e636f6d2f377635415363382e706e67) 13: Single Resource Mongo and Express API -=== - -## Submission Instructions - * fork this repository & create a new branch for your work - * write all of your code in a directory named `lab-` + `` **e.g.** `lab-susan` - * push to your repository - * submit a pull request to this repository - * submit a link to your PR in canvas - * write a question and observation on canvas - -## Learning Objectives -* students will be able to work with the MongoDB database management system -* students will understand the primary concepts of working with a NoSQL database management system -* students will be able to create custom data models *(schemas)* through the use of mongoose.js -* students will be able to use mongoose.js helper methods for interacting with their database persistence layer - -## Requirements -#### Configuration -* `package.json` -* `.eslintrc` -* `.gitignore` -* `README.md` - * your `README.md` should include detailed instructions on how to use your API - -#### Feature Tasks -* create an HTTP Server using `express` -* create a resource **model** of your choice that uses `mongoose.Schema` and `mongoose.model` -* use the `body-parser` express middleware to parse the `req` body on `POST` and `PUT` requests -* use the npm `debug` module to log the functions and methods that are being used in your application -* use the express `Router` to create a route for doing **RESTFUL CRUD** operations against your _model_ - -## Server Endpoints -### `/api/resource-name` -* `POST` request - * should pass data as stringifed JSON in the body of a post request to create a new resource - -### `/api/resource-name/:id` -* `GET` request - * should pass the id of a resource through the url endpoint to get a resource - * **this should use `req.params`, not querystring parameters** -* `PUT` request - * should pass data as stringifed JSON in the body of a put request to update a pre-existing resource -* `DELETE` request - * should pass the id of a resource though the url endpoint to delete a resource - * **this should use `req.params`** - -### Tests -* create a test that will ensure that your API returns a status code of 404 for routes that have not been registered -* create a series of tests to ensure that your `/api/resource-name` endpoint responds as described for each condition below: - * `GET` - test 200, returns a resource with a valid body - * `GET` - test 404, respond with 'not found' for valid requests made with an id that was not found - * `PUT` - test 200, returns a resource with an updated body - * `PUT` - test 400, responds with 'bad request' if no request body was provided - * `PUT` - test 404, responds with 'not found' for valid requests made with an id that was not found - * `POST` - test 400, responds with 'bad request' if no request body was provided - * `POST` - test 200, returns a resource for requests made with a valid body - -### Bonus -* **2pts:** a `GET` request to `/api/resource-name` should return an array of stored resources +# MongoDB - Lab 13 diff --git a/lib/error-middleware.js b/lib/error-middleware.js new file mode 100644 index 0000000..0777e1f --- /dev/null +++ b/lib/error-middleware.js @@ -0,0 +1,21 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('song:error-middleware'); + +module.exports = function(err, req, res, next) { + console.error(err.message); + + if (err.status) { + debug('user error'); + + res.status(err.status).send(err.name); + next(); + return; + } + + debug('server error'); + err = createError(500, err.message); + res.status(err.status).send(err.name); + next(); +}; diff --git a/model/band.js b/model/band.js new file mode 100644 index 0000000..2938fad --- /dev/null +++ b/model/band.js @@ -0,0 +1,11 @@ +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const bandSchema = Schema({ + name: { type: String, required: true }, + origin: { type: String, required: true } +}); + +module.exports = mongoose.model('band', bandSchema); diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..356dcf4 --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,48 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/Users/nickjaz/.nvm/versions/node/v6.11.1/bin/node', +1 verbose cli '/Users/nickjaz/.nvm/versions/node/v6.11.1/bin/npm', +1 verbose cli 'run', +1 verbose cli 'test' ] +2 info using npm@3.10.10 +3 info using node@v6.11.1 +4 verbose run-script [ 'pretest', 'test', 'posttest' ] +5 info lifecycle 13-mongodb@1.0.0~pretest: 13-mongodb@1.0.0 +6 silly lifecycle 13-mongodb@1.0.0~pretest: no script for pretest, continuing +7 info lifecycle 13-mongodb@1.0.0~test: 13-mongodb@1.0.0 +8 verbose lifecycle 13-mongodb@1.0.0~test: unsafe-perm in lifecycle true +9 verbose lifecycle 13-mongodb@1.0.0~test: PATH: /Users/nickjaz/.nvm/versions/node/v6.11.1/lib/node_modules/npm/bin/node-gyp-bin:/Users/nickjaz/codefellows/401/labs/13-mongodb/node_modules/.bin:/Users/nickjaz/.nvm/versions/node/v6.11.1/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +10 verbose lifecycle 13-mongodb@1.0.0~test: CWD: /Users/nickjaz/codefellows/401/labs/13-mongodb +11 silly lifecycle 13-mongodb@1.0.0~test: Args: [ '-c', 'DEBUG=\'song*\' mocha' ] +12 silly lifecycle 13-mongodb@1.0.0~test: Returned: code: 1 signal: null +13 info lifecycle 13-mongodb@1.0.0~test: Failed to exec test script +14 verbose stack Error: 13-mongodb@1.0.0 test: `DEBUG='song*' mocha` +14 verbose stack Exit status 1 +14 verbose stack at EventEmitter. (/Users/nickjaz/.nvm/versions/node/v6.11.1/lib/node_modules/npm/lib/utils/lifecycle.js:255:16) +14 verbose stack at emitTwo (events.js:106:13) +14 verbose stack at EventEmitter.emit (events.js:191:7) +14 verbose stack at ChildProcess. (/Users/nickjaz/.nvm/versions/node/v6.11.1/lib/node_modules/npm/lib/utils/spawn.js:40:14) +14 verbose stack at emitTwo (events.js:106:13) +14 verbose stack at ChildProcess.emit (events.js:191:7) +14 verbose stack at maybeClose (internal/child_process.js:891:16) +14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) +15 verbose pkgid 13-mongodb@1.0.0 +16 verbose cwd /Users/nickjaz/codefellows/401/labs/13-mongodb +17 error Darwin 16.6.0 +18 error argv "/Users/nickjaz/.nvm/versions/node/v6.11.1/bin/node" "/Users/nickjaz/.nvm/versions/node/v6.11.1/bin/npm" "run" "test" +19 error node v6.11.1 +20 error npm v3.10.10 +21 error code ELIFECYCLE +22 error 13-mongodb@1.0.0 test: `DEBUG='song*' mocha` +22 error Exit status 1 +23 error Failed at the 13-mongodb@1.0.0 test script 'DEBUG='song*' mocha'. +23 error Make sure you have the latest version of node.js and npm installed. +23 error If you do, this is most likely a problem with the 13-mongodb package, +23 error not with npm itself. +23 error Tell the author that this fails on your system: +23 error DEBUG='song*' mocha +23 error You can get information on how to open an issue for this project with: +23 error npm bugs 13-mongodb +23 error Or if that isn't available, you can get their info via: +23 error npm owner ls 13-mongodb +23 error There is likely additional logging output above. +24 verbose exit [ 1, true ] diff --git a/package.json b/package.json new file mode 100644 index 0000000..028d230 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "13-mongodb", + "version": "1.0.0", + "description": "![CF](https://camo.githubusercontent.com/70edab54bba80edb7493cad3135e9606781cbb6b/687474703a2f2f692e696d6775722e636f6d2f377635415363382e706e67) 13: Single Resource Mongo and Express API ===", + "main": "server.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "DEBUG='song*' mocha", + "start": "DEBUG='song*' node server.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nickjaz/13-mongodb.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/nickjaz/13-mongodb/issues" + }, + "homepage": "https://github.com/nickjaz/13-mongodb#readme", + "dependencies": { + "bluebird": "^3.5.0", + "body-parser": "^1.17.2", + "cors": "^2.8.4", + "debug": "^2.6.8", + "express": "^4.15.3", + "mongoose": "^4.11.5", + "morgan": "^1.8.2" + }, + "devDependencies": { + "chai": "^4.1.0", + "mocha": "^3.5.0", + "superagent": "^3.5.2" + } +} diff --git a/route/band-route.js b/route/band-route.js new file mode 100644 index 0000000..3e8db21 --- /dev/null +++ b/route/band-route.js @@ -0,0 +1,40 @@ +'use strict'; + +const Router = require('express').Router; +const jsonParser = require('body-parser').json(); +const debug = require('debug')('song:band-route'); +const createError = require('http-errors'); +const Band = require('../model/band.js'); +const bandRouter = module.exports = new Router(); + +bandRouter.post('/api/band', jsonParser, function(req, res, next) { + debug('POST: /api/band'); + + new Band(req.body).save() + .then( band => res.json(band)) + .catch( err => next(createError(400, err.message))); +}); + +bandRouter.get('/api/band/:id', function(req, res, next) { + debug('GET: /api/band/:id'); + + Band.findById(req.params.id) + .then( band => res.json(band)) + .catch( err => next(createError(404, err.message))); +}); + +bandRouter.put('/api/band/:id', jsonParser, function(req, res, next) { + debug('PUT: /api/band/:id'); + + Band.findByIdAndUpdate(req.params.id, req.body, { 'new': true }) + .then( band => res.json(band)) + .catch(err => next(createError(400, err.message))); +}); + +bandRouter.delete('/api/band/:id', function(req, res, next) { + debug('DELETE: /api/band/:id'); + + Band.findByIdAndDelete(req.params.id) + .then( () => res.status(204)) + .catch(err => next(err)); +}); diff --git a/server.js b/server.js new file mode 100644 index 0000000..16127f5 --- /dev/null +++ b/server.js @@ -0,0 +1,27 @@ +'use strict'; + +const express = require('express'); +const morgan = require('morgan'); +const cors = require('cors'); +const Promise = require('bluebird'); +const mongoose = require('mongoose'); +const debug = require('debug')('song:server'); + +const bandRouter = require('./route/band-route'); +const errors = require('./lib/error-middleware.js'); + +const app = express(); +const PORT = process.env.PORT || 3000; +const MONGODB_URI = 'mongodb://localhost/bandsongs'; + +mongoose.Promise = Promise; +mongoose.connect(MONGODB_URI); + +app.use(cors()); +app.use(morgan('dev')); +app.use(bandRouter); +app.use(errors); + +app.listen(PORT, () => { + debug(`listening on ${PORT}`); +}); diff --git a/test/band-route-test.js b/test/band-route-test.js new file mode 100644 index 0000000..b6056ef --- /dev/null +++ b/test/band-route-test.js @@ -0,0 +1,145 @@ +'use strict'; + +const expect = require('chai').expect; +const request = require('superagent'); +const Band = require('../model/band.js'); +const PORT = process.env.PORT || 3000; +const mongoose = require('mongoose'); + +mongoose.Promise = Promise; +require('../server.js'); + +const url = `http://localhost:${PORT}`; +const exampleBand = { + name: 'test band name', + origin: 'test band origin' +}; + +describe('Band Routes', function() { + describe('POST: /api/band', function() { + describe('with a valid req body', function() { + after( done => { + if(this.tempBand) { + Band.remove({}) + .then( () => done()) + .catch(done); + return; + } + done(); + }); + + it('should return a band', done => { + request.post(`${url}/api/band`) + .send(exampleBand) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal('test band name'); + expect(res.body.origin).to.equal('test band origin'); + this.tempBand = res.body; + done(); + }); + }); + + it('should return 400 bad request', done => { + request.post(`${url}/api/band`) + .send({ name: 'fake band', genre: 'indie'}) + .end((err, res) => { + console.log('res text:', res.text); + expect(res.status).to.equal(400); + expect(res.text).to.equal('BadRequestError'); + done(); + }); + }); + }); + }); + + describe('GET: /api/band/:id', function() { + describe('with a valid body', function() { + before( done => { + new Band(exampleBand).save() + .then( band => { + this.tempBand = band; + done(); + }) + .catch(done); + }); + + after( done => { + if(this.tempBand) { + Band.remove({}) + .then( () => done()) + .catch(done); + } + }); + + it('should return a band', done => { + request.get(`${url}/api/band/${this.tempBand._id}`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal('test band name'); + expect(res.body.origin).to.equal('test band origin'); + done(); + }); + }); + + it('should return 404 not found', done => { + request.get(`${url}/api/band/12345`) + .end((err, res) => { + expect(res.status).to.equal(404); + expect(res.text).to.equal('NotFoundError'); + done(); + }); + }); + }); + }); + + describe('PUT: /api/band', function() { + before(done => { + Band.create(exampleBand) + .then(band => { + this.testBand = band; + done(); + }) + .catch(err => done(err)); + }); + + after(done => { + Band.remove({}) + .then( () => done()) + .catch(err => done(err)); + }); + + it('should return a band', done => { + request.put(`${url}/api/band/${this.testBand._id}`) + .send({ name: 'new name', origin: 'new origin'}) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal('new name'); + expect(res.body.origin).to.equal('new origin'); + done(); + }); + }); + + it('should return 400 bad request', done => { + request.put(`${url}/api/band/fake`) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(res.text).to.equal('BadRequestError'); + done(); + }); + }); + + it('should return 404 not found', done => { + request.put(`${url}/api/band/12231789`) + .send({ name: 'new name', origin: 'new origin'}) + .end((err, res) => { + expect(res.status).to.equal(404); + expect(res.text).to.equal('NotFoundError'); + done(); + }); + }); + }); +}); From 5f2106f62bab7921b94db86b50abc91f0e9b8670 Mon Sep 17 00:00:00 2001 From: Nick Asmussen Date: Wed, 2 Aug 2017 18:55:01 -0700 Subject: [PATCH 2/3] corrects the error handling for put request --- npm-debug.log | 48 ----------------------------------------- route/band-route.js | 4 +++- test/band-route-test.js | 5 +++-- 3 files changed, 6 insertions(+), 51 deletions(-) delete mode 100644 npm-debug.log diff --git a/npm-debug.log b/npm-debug.log deleted file mode 100644 index 356dcf4..0000000 --- a/npm-debug.log +++ /dev/null @@ -1,48 +0,0 @@ -0 info it worked if it ends with ok -1 verbose cli [ '/Users/nickjaz/.nvm/versions/node/v6.11.1/bin/node', -1 verbose cli '/Users/nickjaz/.nvm/versions/node/v6.11.1/bin/npm', -1 verbose cli 'run', -1 verbose cli 'test' ] -2 info using npm@3.10.10 -3 info using node@v6.11.1 -4 verbose run-script [ 'pretest', 'test', 'posttest' ] -5 info lifecycle 13-mongodb@1.0.0~pretest: 13-mongodb@1.0.0 -6 silly lifecycle 13-mongodb@1.0.0~pretest: no script for pretest, continuing -7 info lifecycle 13-mongodb@1.0.0~test: 13-mongodb@1.0.0 -8 verbose lifecycle 13-mongodb@1.0.0~test: unsafe-perm in lifecycle true -9 verbose lifecycle 13-mongodb@1.0.0~test: PATH: /Users/nickjaz/.nvm/versions/node/v6.11.1/lib/node_modules/npm/bin/node-gyp-bin:/Users/nickjaz/codefellows/401/labs/13-mongodb/node_modules/.bin:/Users/nickjaz/.nvm/versions/node/v6.11.1/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -10 verbose lifecycle 13-mongodb@1.0.0~test: CWD: /Users/nickjaz/codefellows/401/labs/13-mongodb -11 silly lifecycle 13-mongodb@1.0.0~test: Args: [ '-c', 'DEBUG=\'song*\' mocha' ] -12 silly lifecycle 13-mongodb@1.0.0~test: Returned: code: 1 signal: null -13 info lifecycle 13-mongodb@1.0.0~test: Failed to exec test script -14 verbose stack Error: 13-mongodb@1.0.0 test: `DEBUG='song*' mocha` -14 verbose stack Exit status 1 -14 verbose stack at EventEmitter. (/Users/nickjaz/.nvm/versions/node/v6.11.1/lib/node_modules/npm/lib/utils/lifecycle.js:255:16) -14 verbose stack at emitTwo (events.js:106:13) -14 verbose stack at EventEmitter.emit (events.js:191:7) -14 verbose stack at ChildProcess. (/Users/nickjaz/.nvm/versions/node/v6.11.1/lib/node_modules/npm/lib/utils/spawn.js:40:14) -14 verbose stack at emitTwo (events.js:106:13) -14 verbose stack at ChildProcess.emit (events.js:191:7) -14 verbose stack at maybeClose (internal/child_process.js:891:16) -14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) -15 verbose pkgid 13-mongodb@1.0.0 -16 verbose cwd /Users/nickjaz/codefellows/401/labs/13-mongodb -17 error Darwin 16.6.0 -18 error argv "/Users/nickjaz/.nvm/versions/node/v6.11.1/bin/node" "/Users/nickjaz/.nvm/versions/node/v6.11.1/bin/npm" "run" "test" -19 error node v6.11.1 -20 error npm v3.10.10 -21 error code ELIFECYCLE -22 error 13-mongodb@1.0.0 test: `DEBUG='song*' mocha` -22 error Exit status 1 -23 error Failed at the 13-mongodb@1.0.0 test script 'DEBUG='song*' mocha'. -23 error Make sure you have the latest version of node.js and npm installed. -23 error If you do, this is most likely a problem with the 13-mongodb package, -23 error not with npm itself. -23 error Tell the author that this fails on your system: -23 error DEBUG='song*' mocha -23 error You can get information on how to open an issue for this project with: -23 error npm bugs 13-mongodb -23 error Or if that isn't available, you can get their info via: -23 error npm owner ls 13-mongodb -23 error There is likely additional logging output above. -24 verbose exit [ 1, true ] diff --git a/route/band-route.js b/route/band-route.js index 3e8db21..84d5ca3 100644 --- a/route/band-route.js +++ b/route/band-route.js @@ -26,9 +26,11 @@ bandRouter.get('/api/band/:id', function(req, res, next) { bandRouter.put('/api/band/:id', jsonParser, function(req, res, next) { debug('PUT: /api/band/:id'); + if(!req.body.name) return next(createError(400, 'bad request')); + Band.findByIdAndUpdate(req.params.id, req.body, { 'new': true }) .then( band => res.json(band)) - .catch(err => next(createError(400, err.message))); + .catch(err => next(createError(404, err.message))); }); bandRouter.delete('/api/band/:id', function(req, res, next) { diff --git a/test/band-route-test.js b/test/band-route-test.js index b6056ef..9cfd624 100644 --- a/test/band-route-test.js +++ b/test/band-route-test.js @@ -124,8 +124,9 @@ describe('Band Routes', function() { }); it('should return 400 bad request', done => { - request.put(`${url}/api/band/fake`) + request.put(`${url}/api/band/${this.testBand._id}`) .end((err, res) => { + console.log('res text:', res.text); expect(res.status).to.equal(400); expect(res.text).to.equal('BadRequestError'); done(); @@ -133,7 +134,7 @@ describe('Band Routes', function() { }); it('should return 404 not found', done => { - request.put(`${url}/api/band/12231789`) + request.put(`${url}/api/band/12235235`) .send({ name: 'new name', origin: 'new origin'}) .end((err, res) => { expect(res.status).to.equal(404); From 72ae5fae89a0f28f3eebce73977acbe95dee4fa7 Mon Sep 17 00:00:00 2001 From: Nick Asmussen Date: Wed, 2 Aug 2017 18:56:36 -0700 Subject: [PATCH 3/3] adds readme --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index ac60011..fe30cc3 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ # MongoDB - Lab 13 + +### Description: + +This app is an API that utilizes the express framework. Following REST architecture the this app allows a resource to be READ, CREATED, UPDATED and DELETED. It runs the appropriate tests for getting the correct data and error messages. + +### How to use: + +* Run the server from the command line using `npm run start` +* Open a separate tab in the terminal +* From the second tab start by entering `http [optional request method] :8000/api/song/` +to get all the ids of the songs in local storage +* `GET` or READ requests do not require a request method in the command line + * followed by the songs items id like `/[unique-song-id]'` +* `POST` or CREATE requests are made with `POST` as the request method + * followed by a space then key-value pairs like `name=[item-name]` (no space after parenthesis) + * all key-value pairs must have a space between them +* `DELETE` requests are made like `GET` only with the `DELETE` as the request method +* `PUT` or UPDATE requests are made by entering the `/[id]` followed by a space key value pairs like the `POST` request