diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..881053e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,33 @@ +{ +"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 deleted file mode 100644 index 37f3822..0000000 --- a/README.md +++ /dev/null @@ -1,60 +0,0 @@ -![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 diff --git a/model/house.js b/model/house.js new file mode 100644 index 0000000..f0eddab --- /dev/null +++ b/model/house.js @@ -0,0 +1,14 @@ +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const houseSchema = Schema({ + name: { type: String, required: true }, + seat: { type: String, required: true }, + region: { type: String, required: true }, + words: { type: String, required: true }, + timestamp: { type: Date, required: true } +}); + +module.exports = mongoose.model('house', houseSchema); diff --git a/package.json b/package.json new file mode 100644 index 0000000..d219af3 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "notes-app", + "version": "1.0.0", + "description": "", + "main": "server.js", + "scripts": { + "test": "DEBUG='note*' mocha", + "start": "DEBUG='note*' node server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bluebird": "^3.5.0", + "body-parser": "^1.17.2", + "cors": "^2.8.4", + "debug": "^3.0.0", + "express": "^4.15.4", + "http-errors": "^1.6.2", + "mongoose": "^4.11.6", + "morgan": "^1.8.2" + }, + "devDependencies": { + "chai": "^4.1.1", + "mocha": "^3.5.0", + "superagent": "^3.5.2" + } +} diff --git a/route/house-route.js b/route/house-route.js new file mode 100644 index 0000000..2c37fcf --- /dev/null +++ b/route/house-route.js @@ -0,0 +1,24 @@ +'use strict'; + +const Router = require('express').Router; +const jsonParser = require('body-parser').json(); +const debug = require('debug')('note:house-route'); +const House = require('../model/house.js'); +const houseRouter = module.exports = new Router(); + +houseRouter.post('/api/house', jsonParser, function(request, response, next) { + debug('POST: /api/house/'); + + request.body.timestamp = new Date(); + new House(request.body).save() + .then( house => response.json(house)) + .catch(next); +}); + +houseRouter.get('/api/house/:id', function(request, response, next) { + debug('POST: /api/house/:id'); + + House.findById(request.params.id) + .then( list => response.json(list)) + .catch(next); +}); diff --git a/server.js b/server.js new file mode 100644 index 0000000..e4cc623 --- /dev/null +++ b/server.js @@ -0,0 +1,23 @@ +'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')('note:server'); +const houseRouter = require('./route/house-route.js'); + +const app = express(); +const PORT = process.env.PORT || 3000; +const MONGODB_URI = 'mongodb://localhost/houses-app'; + +mongoose.connect(MONGODB_URI); + +app.use(cors()); +app.use(morgan('dev')); +app.use(houseRouter); + +app.listen(PORT, () => { + debug(`listening on: ${PORT}`); +}); diff --git a/test/house-test.js b/test/house-test.js new file mode 100644 index 0000000..8b67aa6 --- /dev/null +++ b/test/house-test.js @@ -0,0 +1,133 @@ +'use strict'; + +const expect = require('chai').expect; +const request = require('superagent'); +const House = require('../model/house.js'); + +require('../server.js'); +const PORT = process.env.PORT || 3000; + +const mongoose = require('mongoose'); +mongoose.Promise = Promise; + +const url = `http://localhost:${PORT}`; + +const exampleHouse = { + name: 'Atreides', + seat: 'Caladan', + region: 'Delta Pavonis', + words: 'Fear is the Mind-Killer' +}; + +const updateHouse = { + seat: 'Arrakeen', + region: 'Arrakis', +}; + +describe('House Routes', function() { + describe('POST: /api/house', function() { + describe('with a valid req body', function() { + after(done => { + if(this.tempHouse) { + House.remove({}) + .then(() => done()) + .catch(done); + return; + } + done(); + }); + + it('should return a house', done => { + request.post(`${url}/api/house`) + .send(exampleHouse) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal('Atreides'); + expect(res.body.seat).to.equal('Caladan'); + expect(res.body.region).to.equal('Delta Pavonis'); + expect(res.body.words).to.equal('Fear is the Mind-Killer'); + this.tempHouse = res.body; + done(); + }); + }); + }); + }); + + describe('GET: /api/house/:id', function() { + describe('with a valid body', function() { + before(done => { + exampleHouse.timestamp = new Date(); + new House(exampleHouse).save() + .then(house => { + this.tempHouse = house; + done(); + }) + .catch(done); + }); + + after(done => { + delete exampleHouse.timestamp; + if(this.tempHouse) { + House.remove({}) + .then(() => done()) + .catch(done); + return; + } + done(); + }); + + it('should return a house', done => { + request.get(`${url}/api/house/${this.tempHouse._id}`) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal('Atreides'); + expect(res.body.seat).to.equal('Caladan'); + expect(res.body.region).to.equal('Delta Pavonis'); + expect(res.body.words).to.equal('Fear is the Mind-Killer'); + done(); + }); + }); + + it('should return status 404', done => { + request.get(`${url}/api/house/1234`) + .end((err, res) => { + expect(res.status).to.equal(404); + done(); + }); + }); + }); + }); + + describe('PUT: /api/house/:id', function() { + before(done => { + exampleHouse.timestamp = new Date(); + new House(exampleHouse).save() + .then(house => { + this.testHouse = house; + done(); + }) + .catch(done); + }); + + after(done => { + House.remove({}) + .then(() => done()) + .catch(done); + }); + + it('should return updated house', done => { + console.log('testHouse id:', this.testHouse._id); + request.put(`${url}/api/house/${this.testHouse._id}`) + .send(updateHouse) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.seat).to.equal('Arrakeen'); + expect(res.body.region).to.equal('Arrakis'); + done(); + }); + }); + }); +});