From b97336c7d4f835a6c695033bc5b32e185afb9c76 Mon Sep 17 00:00:00 2001 From: Boyang Date: Tue, 12 Feb 2019 16:00:35 +0800 Subject: [PATCH 1/2] Create API Server --- README.md | 20 ++++ app/controllers/player.controller.js | 158 +++++++++++++++++++++++++++ app/models/player.model.js | 21 ++++ app/routes/player.routes.js | 18 +++ config/database.config.js | 3 + package.json | 4 +- server.js | 26 +++++ 7 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 app/controllers/player.controller.js create mode 100644 app/models/player.model.js create mode 100644 app/routes/player.routes.js create mode 100644 config/database.config.js diff --git a/README.md b/README.md index 789d5e0..208ef42 100755 --- a/README.md +++ b/README.md @@ -11,6 +11,26 @@ $ cd node-examination/swagger $ npm start $ open http://localhost:3030 + + +// API server +$ cd node-examination/ +$ node server.js + +$ open http://localhost:3000 + + +// MongoDB url +mongodb://localhost:27017/node-examination +(defined in /config/database.config.js) + + +// API urls +[POST] http://localhost:3000/player +[GET] http://localhost:3000/players +[GET] http://localhost:3000/player/{playerId} +[PUT] http://localhost:3000/player +[DELETE] http://localhost:3000/player/{playerId} ``` ## Tasks diff --git a/app/controllers/player.controller.js b/app/controllers/player.controller.js new file mode 100644 index 0000000..836819a --- /dev/null +++ b/app/controllers/player.controller.js @@ -0,0 +1,158 @@ +const Player = require('../models/player.model.js'); + +// Create a new player +exports.create = (req, res) => { + // Validate request + if(!req.body.id) { + return res.status(405).send({ + message: "Invalid input: Player id can not be empty" + }); + } + if(!req.body.name) { + return res.status(405).send({ + message: "Invalid input: Player name can not be empty" + }); + } + if(!req.body.position) { + return res.status(405).send({ + message: "Invalid input: Player position can not be empty" + }); + } + + // Check player with same id + var query = { id: req.body.id }; + Player.find(query) + .then(players => { + if(players && players.length > 0){ + return res.status(405).send({ + message: "Invalid input: Player with id " + req.body.id + " already exist" + }); + }else{ + // Create a player + const player = new Player({ + id: req.body.id, + name: req.body.name, + position: req.body.position + }); + + // Save player in the database + player.save() + .then(data => { + res.send(data); + }).catch(err => { + res.status(500).send({ + message: err.message || "Some error occurred while creating the player." + }); + }); + } + }).catch(err => { + res.status(500).send({ + message: err.message || "Some error occurred while retrieving players." + }); + }); +}; + +// Get all players from the database. +exports.findAll = (req, res) => { + Player.find() + .then(players => { + res.send(players); + }).catch(err => { + res.status(500).send({ + message: err.message || "Some error occurred while retrieving players." + }); + }); +}; + +// Find player by ID +exports.findOne = (req, res) => { + var query = { id: req.params.playerId }; + + Player.findOne(query) + .then(player => { + if(!player) { + return res.status(404).send({ + message: "Player not found with id " + req.params.playerId + }); + } + res.send(player); + }).catch(err => { + if(err.kind === 'ObjectId') { + return res.status(404).send({ + message: "Player not found with id " + req.params.playerId + }); + } + return res.status(500).send({ + message: err.message || "Error retrieving player with id " + req.params.playerId + }); + }); +}; + +// Update player +exports.update = (req, res) => { + // Validate Request + if(!req.body.id) { + return res.status(405).send({ + message: "Invalid input: Player id can not be empty" + }); + } + if(!req.body.name) { + return res.status(405).send({ + message: "Invalid input: Player name can not be empty" + }); + } + if(!req.body.position) { + return res.status(405).send({ + message: "Invalid input: Player position can not be empty" + }); + } + + // Find player and update it with the request body + var query = { id: req.body.id }; + + Player.findOneAndUpdate(query, { + name: req.body.name, + position: req.body.position + }, {new: true}) + .then(player => { + if(!player) { + return res.status(404).send({ + message: "Player not found with id " + req.body.id + }); + } + res.send(player); + }).catch(err => { + if(err.kind === 'ObjectId') { + return res.status(404).send({ + message: "Player not found with id " + req.body.id + }); + } + return res.status(500).send({ + message: err.message || "Error updating player with id " + req.body.id + }); + }); +}; + +// Delete player by ID +exports.delete = (req, res) => { + var query = { id: req.params.playerId }; + + Player.findOneAndRemove(query) + .then(player => { + if(!player) { + return res.status(404).send({ + message: "Player not found with id " + req.params.playerId + }); + } + res.send({message: "Player deleted successfully!"}); + }).catch(err => { + if(err.kind === 'ObjectId' || err.name === 'NotFound') { + return res.status(404).send({ + message: "Player not found with id " + req.params.playerId + }); + } + return res.status(500).send({ + message: err.message || "Could not delete player with id " + req.params.playerId + }); + }); +}; \ No newline at end of file diff --git a/app/models/player.model.js b/app/models/player.model.js new file mode 100644 index 0000000..98ccc26 --- /dev/null +++ b/app/models/player.model.js @@ -0,0 +1,21 @@ +const mongoose = require('mongoose'); + +require('mongoose-long')(mongoose); + +var Long = mongoose.Schema.Types.Long; + +const PlayerSchema = mongoose.Schema({ + id: { + type: Long, + required: true + }, + name: { + type: String, + required: true + }, + position: String +}, { + timestamps: true +}); + +module.exports = mongoose.model('Player', PlayerSchema); \ No newline at end of file diff --git a/app/routes/player.routes.js b/app/routes/player.routes.js new file mode 100644 index 0000000..5a37bb1 --- /dev/null +++ b/app/routes/player.routes.js @@ -0,0 +1,18 @@ +module.exports = (app) => { + const players = require('../controllers/player.controller.js'); + + // Create a new player + app.post('/player', players.create); + + // Get all players + app.get('/players', players.findAll); + + // Find player by ID + app.get('/player/:playerId', players.findOne); + + // Update player + app.put('/player', players.update); + + // Delete player by ID + app.delete('/player/:playerId', players.delete); +} \ No newline at end of file diff --git a/config/database.config.js b/config/database.config.js new file mode 100644 index 0000000..206331c --- /dev/null +++ b/config/database.config.js @@ -0,0 +1,3 @@ +module.exports = { + url: 'mongodb://localhost:27017/node-examination' +} \ No newline at end of file diff --git a/package.json b/package.json index 7cd51e6..597a877 100755 --- a/package.json +++ b/package.json @@ -10,8 +10,10 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { + "body-parser": "^1.18.3", "express": "^4.16.4", - "mongoose": "^5.4.8" + "mongoose": "^5.4.8", + "mongoose-long": "^0.2.1" }, "devDependencies": { "chai": "^4.2.0" diff --git a/server.js b/server.js index 72e5b39..d0b2099 100755 --- a/server.js +++ b/server.js @@ -1,11 +1,37 @@ const express = require('express'); +const bodyParser = require('body-parser'); const app = express(); +// parse requests of content-type - application/x-www-form-urlencoded +app.use(bodyParser.urlencoded({ extended: true })); + +// parse requests of content-type - application/json +app.use(bodyParser.json()); + +// Configuring the database +const dbConfig = require('./config/database.config.js'); +const mongoose = require('mongoose'); + +mongoose.Promise = global.Promise; + +// Connecting to the database +mongoose.connect(dbConfig.url, { + useNewUrlParser: true +}).then(() => { + console.log("Successfully connected to the database"); +}).catch(err => { + console.log('Could not connect to the database. Exiting now...', err); + process.exit(); +}); + app.get('/', (req, res) => { res.json({"message": "Building a RESTful CRUD API with Node.js, Express/Koa and MongoDB."}); }); +// Require API routes +require('./app/routes/player.routes.js')(app); + app.listen(3000, () => { console.log("Server is listening on port 3000"); }); \ No newline at end of file From e57b4c242b5b3dc79deed5cf495beb84b40bfff4 Mon Sep 17 00:00:00 2001 From: Boyang Date: Tue, 12 Feb 2019 18:17:18 +0800 Subject: [PATCH 2/2] Add tests --- README.md | 10 +++- app/models/player.model.js | 6 +- config/database.config.js | 5 +- package.json | 3 +- server.js | 6 +- test/player.test.js | 111 +++++++++++++++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 test/player.test.js diff --git a/README.md b/README.md index 208ef42..121d22f 100755 --- a/README.md +++ b/README.md @@ -15,13 +15,15 @@ $ open http://localhost:3030 // API server $ cd node-examination/ +$ npm install $ node server.js $ open http://localhost:3000 // MongoDB url -mongodb://localhost:27017/node-examination +development: mongodb://localhost:27017/node-examination +test: mongodb://localhost:27017/node-examination-test (defined in /config/database.config.js) @@ -31,6 +33,12 @@ mongodb://localhost:27017/node-examination [GET] http://localhost:3000/player/{playerId} [PUT] http://localhost:3000/player [DELETE] http://localhost:3000/player/{playerId} + + +// Run tests +$ cd node-examination/ +$ npm install -g mocha +$ mocha ``` ## Tasks diff --git a/app/models/player.model.js b/app/models/player.model.js index 98ccc26..a0a94b5 100644 --- a/app/models/player.model.js +++ b/app/models/player.model.js @@ -1,12 +1,8 @@ const mongoose = require('mongoose'); -require('mongoose-long')(mongoose); - -var Long = mongoose.Schema.Types.Long; - const PlayerSchema = mongoose.Schema({ id: { - type: Long, + type: Number, required: true }, name: { diff --git a/config/database.config.js b/config/database.config.js index 206331c..60cda01 100644 --- a/config/database.config.js +++ b/config/database.config.js @@ -1,3 +1,6 @@ module.exports = { - url: 'mongodb://localhost:27017/node-examination' + url: { + development: 'mongodb://localhost:27017/node-examination', + test: 'mongodb://localhost:27017/node-examination-test' + } } \ No newline at end of file diff --git a/package.json b/package.json index 597a877..6f58b48 100755 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "mongoose-long": "^0.2.1" }, "devDependencies": { - "chai": "^4.2.0" + "chai": "^4.2.0", + "chai-http": "^4.2.1" }, "engines": { "node": ">=10.15.0" diff --git a/server.js b/server.js index d0b2099..d793071 100755 --- a/server.js +++ b/server.js @@ -16,7 +16,7 @@ const mongoose = require('mongoose'); mongoose.Promise = global.Promise; // Connecting to the database -mongoose.connect(dbConfig.url, { +mongoose.connect(dbConfig.url[app.settings.env], { useNewUrlParser: true }).then(() => { console.log("Successfully connected to the database"); @@ -34,4 +34,6 @@ require('./app/routes/player.routes.js')(app); app.listen(3000, () => { console.log("Server is listening on port 3000"); -}); \ No newline at end of file +}); + +module.exports = app; \ No newline at end of file diff --git a/test/player.test.js b/test/player.test.js new file mode 100644 index 0000000..45fcadc --- /dev/null +++ b/test/player.test.js @@ -0,0 +1,111 @@ +process.env.NODE_ENV = 'test'; + +var chai = require('chai'); +var chaiHttp = require('chai-http'); + +var server = require('../server'); +var Player = require("../app/models/player.model.js"); + +var should = chai.should(); +chai.use(chaiHttp); + + +describe('Players', function() { + var newPlayer = new Player({ + id: 123, + name: "Tony Sui", + position: "C" + }); + + + it('should add a SINGLE player on /player POST', function(done) { + chai.request(server) + .post('/player') + .send(newPlayer) + .end(function(err, res){ + res.should.have.status(200); + res.should.be.json; + res.body.should.be.a('object'); + res.body.should.have.property('name'); + res.body.should.have.property('id'); + res.body.should.have.property('position'); + res.body.id.should.equal(123); + res.body.name.should.equal('Tony Sui'); + res.body.position.should.equal('C'); + done(); + }); + }); + + + it('should list a SINGLE player on /player/ GET', function(done) { + chai.request(server) + .get('/player/' + newPlayer.id) + .end(function(err, res){ + res.should.have.status(200); + res.should.be.json; + res.body.should.be.a('object'); + res.body.should.have.property('name'); + res.body.should.have.property('id'); + res.body.should.have.property('position'); + res.body.id.should.equal(123); + res.body.name.should.equal('Tony Sui'); + res.body.position.should.equal('C'); + done(); + }); + }); + + it('should update a SINGLE player on /player PUT', function(done) { + chai.request(server) + .put('/player') + .send({ + "id": 123, + "name": "Mary Hu", + "position": "SG" + }) + .end(function(err, res){ + res.should.have.status(200); + res.should.be.json; + res.body.should.be.a('object'); + res.body.should.have.property('name'); + res.body.should.have.property('id'); + res.body.should.have.property('position'); + res.body.id.should.equal(123); + res.body.name.should.equal('Mary Hu'); + res.body.position.should.equal('SG'); + done(); + }); + }); + + it('should list ALL players on /players GET', function(done) { + chai.request(server) + .get('/players') + .end(function(err, res){ + res.should.have.status(200); + res.should.be.json; + res.body.should.be.a('array'); + res.body[0].id.should.equal(123); + res.body[0].name.should.equal('Mary Hu'); + res.body[0].position.should.equal('SG'); + done(); + }); + }); + + it('should delete a SINGLE player on /player/ DELETE', function(done) { + chai.request(server) + .delete('/player/' + newPlayer.id) + .end(function(err, res){ + res.should.have.status(200); + res.should.be.json; + res.body.should.be.a('object'); + + chai.request(server) + .get('/player/' + newPlayer.id) + .end(function(err, res){ + res.should.have.status(404); + res.should.be.json; + res.body.should.be.a('object'); + done(); + }); + }); + }); +}); \ No newline at end of file