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..fe30cc3 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,19 @@ -![CF](https://camo.githubusercontent.com/70edab54bba80edb7493cad3135e9606781cbb6b/687474703a2f2f692e696d6775722e636f6d2f377635415363382e706e67) 13: Single Resource Mongo and Express API -=== +# MongoDB - Lab 13 -## 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 +### Description: -## 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 +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. -## Requirements -#### Configuration -* `package.json` -* `.eslintrc` -* `.gitignore` -* `README.md` - * your `README.md` should include detailed instructions on how to use your API +### How to use: -#### 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 +* 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 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/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..84d5ca3 --- /dev/null +++ b/route/band-route.js @@ -0,0 +1,42 @@ +'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'); + + 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(404, 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..9cfd624 --- /dev/null +++ b/test/band-route-test.js @@ -0,0 +1,146 @@ +'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/${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(); + }); + }); + + it('should return 404 not found', done => { + request.put(`${url}/api/band/12235235`) + .send({ name: 'new name', origin: 'new origin'}) + .end((err, res) => { + expect(res.status).to.equal(404); + expect(res.text).to.equal('NotFoundError'); + done(); + }); + }); + }); +});