├── model ├── db.js └── blobs.js ├── views ├── index.jade ├── error.jade ├── layout.jade └── blobs │ ├── show.jade │ ├── new.jade │ ├── edit.jade │ └── index.jade ├── public └── stylesheets │ └── style.css ├── routes ├── index.js ├── users.js └── blobs.js ├── package.json ├── .gitignore ├── LICENSE ├── app.js ├── bin └── www └── README.md /model/db.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | mongoose.connect('mongodb://localhost/enmskeleton'); -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | -------------------------------------------------------------------------------- /views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content -------------------------------------------------------------------------------- /views/blobs/show.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h1. 5 | Infophoto #{blob._id} 6 | p Name: #{blob.name} 7 | p Badge: #{blob.badge} 8 | p DOB: #{blobdob} 9 | p Is Loved: #{blob.isloved} -------------------------------------------------------------------------------- /model/blobs.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var blobSchema = new mongoose.Schema({ 3 | name: String, 4 | badge: Number, 5 | dob: { type: Date, default: Date.now }, 6 | isloved: Boolean 7 | }); 8 | mongoose.model('Blob', blobSchema); -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-node-mongo-skeleton", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "^1.18.3", 10 | "cookie-parser": "~1.3.3", 11 | "debug": "~2.1.1", 12 | "express": "~4.11.1", 13 | "jade": "~1.9.1", 14 | "method-override": "^2.3.10", 15 | "mongoose": "^5.3.15", 16 | "morgan": "~1.5.1", 17 | "serve-favicon": "~2.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /views/blobs/new.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h1. 5 | #{title} 6 | form#formAddBlob(name="addblob",method="post",action="/blobs") 7 | p Name: 8 | input#inputName(type="text", placeholder="ex. John Smith", name="name") 9 | p Badge: 10 | input#inputBadge(type="number", placeholder="ex. 123456", name="badge") 11 | p DOB: 12 | input#inputDob(type="date", name="dob") 13 | p Are You Loved?: 14 | input#inputIsLoved(type="checkbox", name="isloved") 15 | p 16 | button#btnSubmit(type="submit") submit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules -------------------------------------------------------------------------------- /views/blobs/edit.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h1. 5 | Blob ID #{blob._id} 6 | form(action='/blobs/#{blob._id}/edit',method='post',name='updateblob',enctype='application/x-www-form-urlencoded') 7 | p Name: 8 | input#inputName(type='text', value='#{blob.name}', name='name') 9 | p Badge: 10 | input#inputBadge(type='number', value='#{blob.badge}', name='badge') 11 | p DOB: 12 | input#inputDob(type='date', value='#{blobdob}', name='dob') 13 | p Are you Loved?: 14 | input#inputIsLoved(type='checkbox', name='isloved', checked=('#{blob.isloved}'==='true' ? "checked" : undefined)) 15 | p 16 | input(type='hidden',value='PUT',name='_method') 17 | p 18 | button#btnSubmit(type='submit'). 19 | Update -------------------------------------------------------------------------------- /views/blobs/index.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h1. 5 | #{title} 6 | ul 7 | - each blob, i in blobs 8 | li 9 | = blob.name 10 | = blob.badge 11 | = blob.dob 12 | = blob.isloved 13 | = blob._id 14 | form(action='/blobs/#{blob._id}/edit',method='post',enctype='application/x-www-form-urlencoded') 15 | input(type='hidden',value='DELETE',name='_method') 16 | button(type='submit'). 17 | Delete 18 | p 19 | a(href='/blobs/#{blob._id}/edit') Edit 20 | p 21 | a(href='/blobs/#{blob._id}') Show -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 EMC Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | path = require('path'), 3 | favicon = require('serve-favicon'), 4 | logger = require('morgan'), 5 | cookieParser = require('cookie-parser'), 6 | bodyParser = require('body-parser'); 7 | 8 | var db = require('./model/db'), 9 | blob = require('./model/blobs'); 10 | 11 | var routes = require('./routes/index'), 12 | blobs = require('./routes/blobs'); 13 | 14 | //var users = require('./routes/users'); 15 | 16 | var app = express(); 17 | 18 | // view engine setup 19 | app.set('views', path.join(__dirname, 'views')); 20 | app.set('view engine', 'jade'); 21 | 22 | // uncomment after placing your favicon in /public 23 | //app.use(favicon(__dirname + '/public/favicon.ico')); 24 | app.use(logger('dev')); 25 | app.use(bodyParser.json()); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(cookieParser()); 28 | app.use(express.static(path.join(__dirname, 'public'))); 29 | 30 | app.use('/', routes); 31 | app.use('/blobs', blobs); 32 | //app.use('/users', users); 33 | 34 | // catch 404 and forward to error handler 35 | app.use(function(req, res, next) { 36 | var err = new Error('Not Found'); 37 | err.status = 404; 38 | next(err); 39 | }); 40 | 41 | // error handlers 42 | 43 | // development error handler 44 | // will print stacktrace 45 | if (app.get('env') === 'development') { 46 | app.use(function(err, req, res, next) { 47 | res.status(err.status || 500); 48 | res.render('error', { 49 | message: err.message, 50 | error: err 51 | }); 52 | }); 53 | } 54 | 55 | // production error handler 56 | // no stacktraces leaked to user 57 | app.use(function(err, req, res, next) { 58 | res.status(err.status || 500); 59 | res.render('error', { 60 | message: err.message, 61 | error: {} 62 | }); 63 | }); 64 | 65 | 66 | module.exports = app; 67 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('express-node-mongo-skeleton:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | express-node-mongo-skeleton 2 | ====================== 3 | express-node-mongo-skeleton was made to have a simple skeleton for a completed Web App ready to go for demonstration purposes and learning how to build CRUD & REST operations into a Node.js Web App. This is the final example that is demonstrated in [How to Create a Complete Express.js + Node.js + MongoDB CRUD and REST Skeleton](https://www.airpair.com/javascript/complete-expressjs-nodejs-mongodb-crud-skeleton) 4 | 5 | ## Installation 6 | - Perform a clone of this repo. 7 | - Make sure MongoDB is installed (`brew install mongodb`) 8 | - Create a MongoDB database named `enmskeleton` (`use enmskeleton`) 9 | - Install packages and start the express.js web service (`npm install && npm start`) 10 | - Navigate to `http://127.0.0.1:3000` to see the express.js welcome page 11 | 12 | ## Usage Instructions 13 | All of the MVC pieces are built, but are also rudimentary and not flashy. The root of our webapp goes to the express.js landing page, but there is a schema created for a new object called `blobs`. To access `blobs`, follow the route that is already in place by going to `http://127.0.0.1:3000/blobs`. 14 | 15 | Add a new blob by going to `http://127.0.0.1:3000/blobs/new`. 16 |
17 | 18 | After submitting, this will take you back to the index page where you can now `Show` or `Edit` or `Delete` that blob record from the UI 19 |
20 | 21 | All of the REST pieces are baked in as well. You can test them using a multitude of different REST based tools. 22 | 23 | ## Contribution 24 | Create a fork of the project into your own reposity. Make all your necessary changes and create a pull request with a description on what was added or removed and details explaining the changes in lines of code. If approved, project owners will merge it. 25 | 26 | Licensing 27 | --------- 28 | express-node-mongo-skeleton is freely distributed under the MIT License. See LICENSE for details -------------------------------------------------------------------------------- /routes/blobs.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | router = express.Router(), 3 | mongoose = require('mongoose'), //mongo connection 4 | bodyParser = require('body-parser'), //parses information from POST 5 | methodOverride = require('method-override'); //used to manipulate POST 6 | 7 | //Any requests to this controller must pass through this 'use' function 8 | //Copy and pasted from method-override 9 | router.use(bodyParser.urlencoded({ extended: true })) 10 | router.use(methodOverride(function(req, res){ 11 | if (req.body && typeof req.body === 'object' && '_method' in req.body) { 12 | // look in urlencoded POST bodies and delete it 13 | var method = req.body._method 14 | delete req.body._method 15 | return method 16 | } 17 | })) 18 | 19 | //build the REST operations at the base for blobs 20 | //this will be accessible from http://127.0.0.1:3000/blobs if the default route for / is left unchanged 21 | router.route('/') 22 | //GET all blobs 23 | .get(function(req, res, next) { 24 | //retrieve all blobs from Monogo 25 | mongoose.model('Blob').find({}, function (err, blobs) { 26 | if (err) { 27 | return console.error(err); 28 | } else { 29 | //respond to both HTML and JSON. JSON responses require 'Accept: application/json;' in the Request Header 30 | res.format({ 31 | //HTML response will render the index.jade file in the views/blobs folder. We are also setting "blobs" to be an accessible variable in our jade view 32 | html: function(){ 33 | res.render('blobs/index', { 34 | title: 'All my Blobs', 35 | "blobs" : blobs 36 | }); 37 | }, 38 | //JSON response will show all blobs in JSON format 39 | json: function(){ 40 | res.json(blobs); 41 | } 42 | }); 43 | } 44 | }); 45 | }) 46 | //POST a new blob 47 | .post(function(req, res) { 48 | // Get values from POST request. These can be done through forms or REST calls. These rely on the "name" attributes for forms 49 | var name = req.body.name; 50 | var badge = req.body.badge; 51 | var dob = req.body.dob; 52 | var company = req.body.company; 53 | var isloved = req.body.isloved; 54 | //call the create function for our database 55 | mongoose.model('Blob').create({ 56 | name : name, 57 | badge : badge, 58 | dob : dob, 59 | isloved : isloved 60 | }, function (err, blob) { 61 | if (err) { 62 | res.send("There was a problem adding the information to the database."); 63 | } else { 64 | //Blob has been created 65 | console.log('POST creating new blob: ' + blob); 66 | res.format({ 67 | //HTML response will set the location and redirect back to the home page. You could also create a 'success' page if that's your thing 68 | html: function(){ 69 | // If it worked, set the header so the address bar doesn't still say /adduser 70 | res.location("blobs"); 71 | // And forward to success page 72 | res.redirect("/blobs"); 73 | }, 74 | //JSON response will show the newly created blob 75 | json: function(){ 76 | res.json(blob); 77 | } 78 | }); 79 | } 80 | }) 81 | }); 82 | 83 | /* GET New Blob page. */ 84 | router.get('/new', function(req, res) { 85 | res.render('blobs/new', { title: 'Add New Blob' }); 86 | }); 87 | 88 | // route middleware to validate :id 89 | router.param('id', function(req, res, next, id) { 90 | //console.log('validating ' + id + ' exists'); 91 | //find the ID in the Database 92 | mongoose.model('Blob').findById(id, function (err, blob) { 93 | //if it isn't found, we are going to repond with 404 94 | if (err) { 95 | console.log(id + ' was not found'); 96 | res.status(404) 97 | var err = new Error('Not Found'); 98 | err.status = 404; 99 | res.format({ 100 | html: function(){ 101 | next(err); 102 | }, 103 | json: function(){ 104 | res.json({message : err.status + ' ' + err}); 105 | } 106 | }); 107 | //if it is found we continue on 108 | } else { 109 | //uncomment this next line if you want to see every JSON document response for every GET/PUT/DELETE call 110 | //console.log(blob); 111 | // once validation is done save the new item in the req 112 | req.id = id; 113 | // go to the next thing 114 | next(); 115 | } 116 | }); 117 | }); 118 | 119 | router.route('/:id') 120 | .get(function(req, res) { 121 | mongoose.model('Blob').findById(req.id, function (err, blob) { 122 | if (err) { 123 | console.log('GET Error: There was a problem retrieving: ' + err); 124 | } else { 125 | console.log('GET Retrieving ID: ' + blob._id); 126 | var blobdob = blob.dob.toISOString(); 127 | blobdob = blobdob.substring(0, blobdob.indexOf('T')) 128 | res.format({ 129 | html: function(){ 130 | res.render('blobs/show', { 131 | "blobdob" : blobdob, 132 | "blob" : blob 133 | }); 134 | }, 135 | json: function(){ 136 | res.json(blob); 137 | } 138 | }); 139 | } 140 | }); 141 | }); 142 | 143 | router.route('/:id/edit') 144 | //GET the individual blob by Mongo ID 145 | .get(function(req, res) { 146 | //search for the blob within Mongo 147 | mongoose.model('Blob').findById(req.id, function (err, blob) { 148 | if (err) { 149 | console.log('GET Error: There was a problem retrieving: ' + err); 150 | } else { 151 | //Return the blob 152 | console.log('GET Retrieving ID: ' + blob._id); 153 | var blobdob = blob.dob.toISOString(); 154 | blobdob = blobdob.substring(0, blobdob.indexOf('T')) 155 | res.format({ 156 | //HTML response will render the 'edit.jade' template 157 | html: function(){ 158 | res.render('blobs/edit', { 159 | title: 'Blob' + blob._id, 160 | "blobdob" : blobdob, 161 | "blob" : blob 162 | }); 163 | }, 164 | //JSON response will return the JSON output 165 | json: function(){ 166 | res.json(blob); 167 | } 168 | }); 169 | } 170 | }); 171 | }) 172 | //PUT to update a blob by ID 173 | .put(function(req, res) { 174 | // Get our REST or form values. These rely on the "name" attributes 175 | var name = req.body.name; 176 | var badge = req.body.badge; 177 | var dob = req.body.dob; 178 | var company = req.body.company; 179 | var isloved = req.body.isloved; 180 | 181 | //find the document by ID 182 | mongoose.model('Blob').findById(req.id, function (err, blob) { 183 | //update it 184 | blob.update({ 185 | name : name, 186 | badge : badge, 187 | dob : dob, 188 | isloved : isloved 189 | }, function (err, blobID) { 190 | if (err) { 191 | res.send("There was a problem updating the information to the database: " + err); 192 | } 193 | else { 194 | //HTML responds by going back to the page or you can be fancy and create a new view that shows a success page. 195 | res.format({ 196 | html: function(){ 197 | res.redirect("/blobs/" + blob._id); 198 | }, 199 | //JSON responds showing the updated values 200 | json: function(){ 201 | res.json(blob); 202 | } 203 | }); 204 | } 205 | }) 206 | }); 207 | }) 208 | //DELETE a Blob by ID 209 | .delete(function (req, res){ 210 | //find blob by ID 211 | mongoose.model('Blob').findById(req.id, function (err, blob) { 212 | if (err) { 213 | return console.error(err); 214 | } else { 215 | //remove it from Mongo 216 | blob.remove(function (err, blob) { 217 | if (err) { 218 | return console.error(err); 219 | } else { 220 | //Returning success messages saying it was deleted 221 | console.log('DELETE removing ID: ' + blob._id); 222 | res.format({ 223 | //HTML returns us back to the main page, or you can create a success page 224 | html: function(){ 225 | res.redirect("/blobs"); 226 | }, 227 | //JSON returns the item with the message that is has been deleted 228 | json: function(){ 229 | res.json({message : 'deleted', 230 | item : blob 231 | }); 232 | } 233 | }); 234 | } 235 | }); 236 | } 237 | }); 238 | }); 239 | 240 | module.exports = router; --------------------------------------------------------------------------------