├── .gitignore ├── chapter01 └── FileManager │ ├── app.js │ ├── config.json │ ├── files │ └── .gitkeep │ ├── helpers │ ├── files.js │ └── index.js │ ├── lib │ └── db.js │ ├── models │ ├── file.js │ └── user.js │ ├── package.json │ ├── public │ ├── javascripts │ │ └── file-upload.js │ └── stylesheets │ │ ├── normalize.css │ │ └── style.css │ ├── routes │ ├── files.js │ ├── index.js │ ├── main.js │ ├── sessions.js │ └── users.js │ └── views │ ├── files │ └── index.jade │ ├── layout.jade │ ├── sessions │ └── new.jade │ └── users │ └── new.jade ├── chapter02 ├── custom-middleware-system │ ├── app │ │ ├── index.js │ │ ├── requestHandler.js │ │ └── router.js │ ├── package.json │ ├── sample-router.js │ └── sample.js └── middleware-examples │ ├── 404.html │ ├── 500.html │ ├── app-routes.js │ ├── cache-render-configured.js │ ├── cache-render.js │ ├── env-middleware.js │ ├── handling-errors-enhanced.js │ ├── handling-errors.js │ ├── ip-whitelist.js │ ├── mounting │ ├── admin.js │ ├── blog.js │ └── index.js │ ├── package.json │ ├── public │ └── core.js │ ├── reusable-handlers.js │ ├── using-middleware.js │ └── views │ └── hello.ejs ├── chapter03 ├── SmartNotes │ ├── config.json │ ├── lib │ │ ├── db.js │ │ └── validator.js │ ├── models │ │ ├── note.js │ │ └── user.js │ ├── package.json │ ├── routes │ │ ├── index.js │ │ ├── notes.js │ │ └── users.js │ ├── server.js │ └── test │ │ ├── fixtures │ │ ├── notes.json │ │ └── users.json │ │ ├── functional │ │ ├── notes.js │ │ └── users.js │ │ ├── unit │ │ ├── note.js │ │ ├── user.js │ │ └── validator.js │ │ └── utils │ │ ├── db.js │ │ └── helpers.js └── etags │ ├── etags.js │ └── package.json ├── chapter04 ├── different-engines │ ├── ejs-server.js │ ├── jade-server.js │ ├── json2html-server.js │ ├── mustache-server.js │ ├── package.json │ └── views │ │ ├── index-jade.jade │ │ ├── index.ejs │ │ └── index.html ├── integrating-template-engine │ ├── engine.js │ ├── package.json │ ├── server.js │ ├── simple.js │ └── views │ │ ├── footer.html │ │ ├── header.html │ │ ├── home.html │ │ ├── layout.html │ │ └── now.html ├── layouts-with-jade │ ├── package.json │ ├── server.js │ └── views │ │ ├── index.jade │ │ └── layout.jade ├── updating-templates-production │ ├── package.json │ ├── server.js │ └── views │ │ ├── example.ejs │ │ ├── example.hbs │ │ ├── example.jade │ │ └── example.swig ├── view-caching │ ├── package.json │ ├── server.js │ ├── users.json │ └── views │ │ └── users.jade └── view-helpers-examples │ ├── package.json │ ├── server.js │ └── views │ ├── footer.html │ ├── header.html │ ├── index.html │ └── search.html ├── chapter05 ├── app │ ├── models │ │ └── movie.js │ ├── package.json │ ├── public │ │ └── style.css │ ├── routes │ │ ├── errors.js │ │ ├── index.js │ │ └── movies.js │ ├── server.js │ └── views │ │ ├── layout │ │ ├── footer.html │ │ └── header.html │ │ ├── movie.html │ │ ├── movies.html │ │ └── search.html └── dry_app │ ├── models │ └── movie.js │ ├── package.json │ ├── public │ └── style.css │ ├── routes │ ├── errors.js │ ├── index.js │ └── movies.js │ ├── server.js │ └── views │ ├── layout │ ├── footer.html │ └── header.html │ ├── movie.html │ ├── movies.html │ └── search.html ├── chapter06 ├── WallApp │ ├── lib │ │ ├── die.js │ │ ├── errorHandler.js │ │ ├── primus.js │ │ └── validator.js │ ├── models │ │ ├── post.js │ │ └── user.js │ ├── package.json │ ├── public │ │ ├── css │ │ │ ├── core.css │ │ │ └── lib │ │ │ │ └── bootstrap.css │ │ ├── img │ │ │ └── ajax-loader.gif │ │ └── js │ │ │ ├── core.js │ │ │ └── lib │ │ │ ├── jquery.js │ │ │ └── primus.js │ ├── routes │ │ ├── index.js │ │ ├── posts.js │ │ ├── session.js │ │ └── users.js │ ├── server.js │ └── views │ │ ├── _posts.html │ │ ├── index.html │ │ ├── layout │ │ ├── footer.html │ │ └── header.html │ │ ├── login.html │ │ └── register.html └── custom-error-handler │ ├── package.json │ └── server.js ├── chapter07 ├── cache-system │ ├── cache.js │ └── package.json ├── etag-cached-data │ └── server.js ├── static-resources │ ├── assets │ │ └── v0.1 │ │ │ ├── sample.css │ │ │ └── sample.js │ ├── certs │ │ ├── nodeapp.crt │ │ ├── nodeapp.csr │ │ ├── nodeapp.key │ │ └── nodeapp.pem │ ├── package.json │ ├── public │ │ ├── sample.css │ │ └── sample.js │ ├── server-cluster.js │ ├── server.js │ ├── stud.conf │ └── views │ │ └── home.html ├── streaming-templates │ ├── package.json │ ├── server.js │ └── template.html └── using-streams │ ├── big-file.txt │ └── server.js ├── chapter08 ├── collecting-metrics │ ├── package.json │ ├── public │ │ ├── dashboard.html │ │ └── js │ │ │ ├── lib │ │ │ ├── excanvas.js │ │ │ ├── excanvas.min.js │ │ │ ├── jquery.colorhelpers.js │ │ │ ├── jquery.flot.canvas.js │ │ │ ├── jquery.flot.categories.js │ │ │ ├── jquery.flot.crosshair.js │ │ │ ├── jquery.flot.errorbars.js │ │ │ ├── jquery.flot.fillbetween.js │ │ │ ├── jquery.flot.image.js │ │ │ ├── jquery.flot.js │ │ │ ├── jquery.flot.navigate.js │ │ │ ├── jquery.flot.pie.js │ │ │ ├── jquery.flot.resize.js │ │ │ ├── jquery.flot.selection.js │ │ │ ├── jquery.flot.stack.js │ │ │ ├── jquery.flot.symbol.js │ │ │ ├── jquery.flot.threshold.js │ │ │ ├── jquery.flot.time.js │ │ │ └── jquery.js │ │ │ └── main.js │ └── server.js ├── health-endpoint │ ├── package.json │ └── server.js ├── logging │ ├── lib │ │ ├── customStream.js │ │ ├── getHrTime.js │ │ └── logger.js │ ├── package.json │ └── server.js ├── network-traffic │ └── server.js ├── slowest-endpoints │ ├── package.json │ └── server.js └── time-spent-in-function │ └── index.js ├── chapter09 ├── debugger │ ├── package.json │ └── server.js ├── debugging-routes │ ├── package.json │ └── server.js ├── error-handler │ ├── README.md │ ├── all_cities.json │ ├── get-data │ ├── lib │ │ ├── error-handler │ │ │ ├── index.js │ │ │ └── public │ │ │ │ ├── main.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ └── tz.js │ ├── package.json │ ├── public │ │ ├── css │ │ │ └── style.css │ │ └── js │ │ │ ├── jquery-1.11.1.min.js │ │ │ └── jsclock-0.8.min.js │ ├── routes │ │ ├── index.js │ │ └── timezones.js │ ├── server.js │ └── views │ │ ├── home.html │ │ └── time.html ├── heapdump-app │ ├── package.json │ └── server.js ├── removing-leftovers │ ├── clean-server.js │ ├── package.json │ └── server.js └── using-replify │ ├── package.json │ └── server.js ├── chapter10 ├── csrf-app │ ├── external-page.js │ ├── package.json │ ├── server.js │ └── views │ │ ├── index.html │ │ └── orders.html ├── reauthenticating-user │ ├── package.json │ ├── server.js │ └── views │ │ ├── home.html │ │ └── login.html ├── root-downgrade │ └── server.js ├── uploading-files │ ├── package.json │ ├── server.js │ └── views │ │ └── home.html └── xss │ ├── package.json │ ├── server.js │ └── views │ ├── user-secure.html │ └── user.html └── chapter11 ├── app.js ├── bin └── www ├── config.json ├── coverage ├── coverage.json ├── lcov-report │ ├── file-share-app │ │ ├── app.js.html │ │ ├── index.html │ │ ├── lib │ │ │ ├── hash.js.html │ │ │ ├── index.html │ │ │ └── middleware │ │ │ │ ├── csrf-helper.js.html │ │ │ │ └── index.html │ │ ├── models │ │ │ ├── file.js.html │ │ │ └── index.html │ │ └── routes │ │ │ ├── files.js.html │ │ │ ├── index.html │ │ │ └── index.js.html │ ├── index.html │ ├── lib │ │ ├── hash.js.html │ │ └── index.html │ ├── models │ │ ├── file.js.html │ │ └── index.html │ ├── prettify.css │ └── prettify.js └── lcov.info ├── eslint.json ├── files └── .gitkeep ├── lib ├── hash.js └── middleware │ └── csrf-helper.js ├── models └── file.js ├── package.json ├── public └── stylesheets │ └── style.css ├── routes ├── files.js └── index.js ├── test ├── functional │ └── files-routes.js └── unit │ └── file-model.js └── views ├── error.html ├── files ├── new.html └── show.html ├── index.html └── layout ├── footer.html └── header.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_STORE 4 | *.swo 5 | -------------------------------------------------------------------------------- /chapter01/FileManager/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Module dependencies 4 | var express = require('express'); 5 | var app = express(); 6 | var morgan = require('morgan'); 7 | var flash = require('connect-flash'); 8 | var multiparty = require('connect-multiparty'); 9 | var cookieParser = require('cookie-parser'); 10 | var cookieSession = require('cookie-session'); 11 | var bodyParser = require('body-parser'); 12 | var methodOverride = require('method-override'); 13 | var errorHandler = require('errorhandler'); 14 | var config = require('./config.json'); 15 | var routes = require('./routes'); 16 | var db = require('./lib/db'); 17 | 18 | // View setup 19 | app.set('view engine', 'jade'); 20 | app.set('views', __dirname + '/views'); 21 | app.locals = require('./helpers/index'); 22 | 23 | // Loading middleware 24 | app.use(morgan('dev')); 25 | app.use(bodyParser.json()); 26 | app.use(bodyParser.urlencoded({ extended: true })); 27 | app.use(methodOverride(function(req, res){ 28 | if (req.body && typeof req.body === 'object' && '_method' in req.body) { 29 | // look in urlencoded POST bodies and delete it 30 | var method = req.body._method; 31 | delete req.body._method; 32 | return method; 33 | } 34 | })); 35 | app.use(cookieParser()); 36 | app.use(cookieSession({ 37 | secret: config.sessionSecret, 38 | cookie: { 39 | maxAge: config.sessionMaxAge 40 | } 41 | })); 42 | app.use(flash()); 43 | 44 | // Declaring application routes 45 | app.get('/', routes.main.requireUserAuth, routes.files.index); 46 | app.get('/files/:file', routes.main.requireUserAuth, routes.files.show); 47 | app.delete('/files/:file', routes.main.requireUserAuth, routes.files.destroy); 48 | app.post('/files', multiparty(), routes.main.requireUserAuth, routes.files.create); 49 | app.get('/users/new', routes.users.new); 50 | app.post('/users', routes.users.create); 51 | app.get('/sessions/new', routes.sessions.new); 52 | app.post('/sessions', routes.sessions.create); 53 | app.delete('/sessions', routes.sessions.destroy); 54 | 55 | if (app.get('env') === 'development') { 56 | app.use(errorHandler()); 57 | } 58 | 59 | // static middleware after the routes 60 | app.use(express.static(__dirname + '/public')); 61 | 62 | // Establishing database connection and binding application to specified port 63 | db.connect(); 64 | app.listen(config.port); 65 | console.log('listening on port %s', config.port); 66 | -------------------------------------------------------------------------------- /chapter01/FileManager/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongoUrl": "mongodb://localhost/filestore", 3 | "port": 3000, 4 | "sessionSecret": "random chars here", 5 | "sessionMaxAge": 3600000 6 | } 7 | -------------------------------------------------------------------------------- /chapter01/FileManager/files/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessioalex/mastering_express_code/a8202b6dcffd33e6716722f83d63bacbbc638454/chapter01/FileManager/files/.gitkeep -------------------------------------------------------------------------------- /chapter01/FileManager/helpers/files.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.formatSize = function(sizeInBytes) { 4 | return (sizeInBytes / 1024).toFixed(2) + ' kb'; 5 | }; 6 | -------------------------------------------------------------------------------- /chapter01/FileManager/helpers/index.js: -------------------------------------------------------------------------------- 1 | exports.helpers = { 2 | files: require('./files') 3 | }; 4 | -------------------------------------------------------------------------------- /chapter01/FileManager/lib/db.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var mongoose = require('mongoose'); 4 | var config = require('../config.json'); 5 | 6 | exports.isValidationError = function(err) { 7 | return ((err.name === 'ValidationError') 8 | || (err.message.indexOf('ValidationError') !== -1)); 9 | }; 10 | 11 | exports.isDuplicateKeyError = function(err) { 12 | return (err.message.indexOf('duplicate key') !== -1); 13 | }; 14 | 15 | exports.connect = function() { 16 | mongoose.connect(config.mongoUrl, { safe: true }, function(err) { 17 | if (err) { 18 | console.error('database connection failure: \n' + err.stack); 19 | process.exit(1); 20 | } 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /chapter01/FileManager/models/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | var pass = require('pwd'); 6 | 7 | var validateUser = function(username) { 8 | return !!(username && /^[a-z][a-z0-9_-]{3,15}$/i.test(username)); 9 | }; 10 | var validatePassword = function(pass) { 11 | return !!(pass && pass.length > 5); 12 | }; 13 | 14 | var User = new Schema({ 15 | username: { 16 | type: String, 17 | validate: validateUser, 18 | unique: true 19 | }, 20 | salt: String, 21 | hash: String 22 | }, { 23 | safe: true 24 | }); 25 | 26 | // we cannot use a virtual password setter since this function is async 27 | User.methods.setPassword = function(password, callback) { 28 | pass.hash(password, (function(err, salt, hash) { 29 | if (err) { return callback(err); } 30 | 31 | this.hash = hash; 32 | this.salt = salt; 33 | 34 | callback(); 35 | }).bind(this)); 36 | }; 37 | 38 | // validate the schema properties && other non-schema properties (password) 39 | User.methods.validateAll = function(props, callback) { 40 | this.validate((function(err) { 41 | if (err) { return callback(err); } 42 | 43 | if (!validatePassword(props.password)) { 44 | return callback(new Error('ValidationError: invalid password')); 45 | } 46 | 47 | return callback(); 48 | }).bind(this)); 49 | }; 50 | 51 | User.methods.saveWithPassword = function(password, callback) { 52 | this.validateAll({ password: password }, (function(err) { 53 | if (err) { return callback(err); } 54 | 55 | this.setPassword(password, (function(err) { 56 | if (err) { return callback(err); } 57 | 58 | this.save(callback); 59 | }).bind(this)); 60 | }).bind(this)); 61 | }; 62 | 63 | User.statics.authenticate = function(username, password, callback) { 64 | // avoid making a call to the database for an invalid username/password 65 | if (!validateUser(username) || !validatePassword(password)) { 66 | // keep this function async in all situations 67 | return process.nextTick(function() { callback(null, false) }); 68 | } 69 | 70 | this.findOne({ username: username }, function(err, user) { 71 | if (err) { return callback(err); } 72 | // no such user in the database 73 | if (!user) { return callback(null, false); } 74 | 75 | pass.hash(password, user.salt, function(err, hash) { 76 | if (err) { return callback(err); } 77 | 78 | // if the auth was successful return the user details 79 | return (user.hash === hash) ? callback(null, user) : callback(null, false); 80 | }); 81 | }); 82 | }; 83 | 84 | module.exports = mongoose.model('User', User); 85 | -------------------------------------------------------------------------------- /chapter01/FileManager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mvc-app", 3 | "version": "0.0.1", 4 | "description": "File sharing app", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Alexandru Vladutu ", 11 | "license": "BSD-2-Clause", 12 | "dependencies": { 13 | "jade": "~0.35.0", 14 | "express": "~4.4.5", 15 | "mongoose": "~3.8.1", 16 | "pwd": "0.0.3", 17 | "connect-flash": "~0.1.1", 18 | "async": "~0.2.9", 19 | "connect-multiparty": "~1.0.1", 20 | "morgan": "~1.1.1", 21 | "cookie-parser": "~1.3.2", 22 | "cookie-session": "~1.0.2", 23 | "body-parser": "~1.4.3", 24 | "method-override": "~2.0.2", 25 | "errorhandler": "~1.1.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter01/FileManager/public/javascripts/file-upload.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var file = { 3 | name: '', 4 | size: 0 5 | }; 6 | var uploadForm = $('#upload-form'); 7 | var fakeUploadBox = $('#fake-upload-box'); 8 | var fileInput = uploadForm.find('input[type=file]'); 9 | 10 | function isValidFileName(name) { 11 | return /[a-z0-9_]/i.test(name); 12 | } 13 | 14 | fileInput.on('change', function(evt) { 15 | var el = $(this); 16 | // get the filename, work for unix && windows 17 | file.name = el.val().split(/(\\|\/)/g).pop(); 18 | 19 | if (evt.target.files) { 20 | // convert to kb 21 | file.size = (evt.target.files[0].size / 1024).toFixed(2); 22 | } 23 | 24 | if (!isValidFileName(file.name)) { 25 | alert('Bad file name! a-z, 0-9 && underscore allowed only!'); 26 | el.val(''); 27 | } else { 28 | fakeUploadBox.val(file.name); 29 | } 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /chapter01/FileManager/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Comic Sans Ms, Times New Roman; 3 | background: #F1F1F1; 4 | } 5 | a { 6 | color: #0084B2; 7 | text-decoration: none; 8 | } 9 | header h1, .container h2 { 10 | padding: 0; 11 | margin: 0; 12 | } 13 | header h1 { 14 | font-family: 'IM Fell Great Primer', sans-serif; 15 | background: #0084B2; 16 | color: #FFF; 17 | } 18 | header h1, .container { 19 | padding: 15px 30px; 20 | } 21 | h3, table { 22 | font-family: Times New Roman; 23 | } 24 | header { 25 | box-shadow: 1px 1px 2px #999; 26 | } 27 | .user-form { 28 | width: 333px; 29 | margin: 0 auto; 30 | border: 1px solid #929292; 31 | padding: 20px; 32 | border-radius: 3px; 33 | background: #FFF; 34 | box-shadow: 1px 1px 10px #CCC; 35 | } 36 | table { 37 | background-color: #FFF; 38 | box-shadow: 1px 1px 5px lightgray; 39 | text-align: left; 40 | width: 350px; 41 | } 42 | thead { 43 | background: #F9F9F9; 44 | } 45 | tr { 46 | border: 1px dotted #DEDEDE; 47 | } 48 | tbody tr:nth-child(odd) { 49 | background: #FFFFF9; 50 | } 51 | th, td { 52 | padding: 4px; 53 | } 54 | input[type=text], input[type=password] { 55 | width: 250px; 56 | text-indent: 5px; 57 | } 58 | input[type=submit] { 59 | color: #fff; 60 | background: #428bca; 61 | border-color: #357ebd; 62 | opacity: 0.9; 63 | } 64 | input[type=submit]:hover { 65 | opacity: 1; 66 | } 67 | input.sign-out { 68 | color: #262626; 69 | background: #FFF; 70 | position: absolute; 71 | top: 10px; 72 | right: 10px; 73 | } 74 | table input[type=submit] { 75 | border: none; 76 | background: transparent; 77 | color: #0084B2; 78 | padding: 0; 79 | } 80 | .notification { 81 | border-left-style: solid; 82 | border-left-width: 5px; 83 | display: inline-block; 84 | text-indent: 5px; 85 | padding: 5px 10px 5px 2px; 86 | } 87 | .notification.error { 88 | border-left-color: #D59394; 89 | background: #F8EEEE; 90 | } 91 | .notification.info { 92 | border-left-color: #97B7CE; 93 | background: #EFF4F8; 94 | } 95 | #upload-form { 96 | margin-bottom: 25px; 97 | } 98 | #upload-form button { 99 | padding: 0 5px; 100 | border: 1px inset #000; 101 | margin-left: 5px; 102 | background: #FFF; 103 | } 104 | #upload-form .browse-file { 105 | display: inline-block; 106 | position: relative; 107 | } 108 | .browse-file input { 109 | width: 250px; 110 | border: 1px inset #CCC; 111 | padding: 0; 112 | } 113 | .browse-file input[type=file] { 114 | opacity: 0; 115 | position: absolute; 116 | left: 0; 117 | cursor: pointer; 118 | } 119 | -------------------------------------------------------------------------------- /chapter01/FileManager/routes/files.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var File = require('../models/file'); 4 | 5 | exports.index = function(req, res, next) { 6 | File.getByUserId(req.session.userId, function(err, files) { 7 | if (err) { return next(err); } 8 | 9 | res.render('files/index', { 10 | username: req.session.username, 11 | files: files, 12 | info: req.flash('info')[0], 13 | error: req.flash('error')[0] 14 | }); 15 | }); 16 | }; 17 | 18 | exports.show = function(req, res, next) { 19 | var file = new File(req.session.userId, req.params.file); 20 | 21 | file.exists(function(exists) { 22 | if (!exists) { return res.send(404, 'Page Not Found'); } 23 | 24 | res.sendfile(file.path); 25 | }); 26 | }; 27 | 28 | exports.destroy = function(req, res, next) { 29 | var file = new File(req.session.userId, req.params.file); 30 | 31 | file.delete(function(err) { 32 | if (err) { return next(err); } 33 | 34 | req.flash('info', 'File successfully deleted!'); 35 | res.redirect('/'); 36 | }); 37 | }; 38 | 39 | exports.create = function(req, res, next) { 40 | if (!req.files.file || (req.files.file.size === 0)) { 41 | req.flash('error', 'No file selected!'); 42 | return res.redirect('/'); 43 | } 44 | 45 | var file = new File(req.session.userId, req.files.file.originalFilename); 46 | 47 | file.save(req.files.file.path, function(err) { 48 | if (err) { return next(err); } 49 | 50 | req.flash('info', 'File successfully uploaded!'); 51 | res.redirect('/'); 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /chapter01/FileManager/routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.main = require('./main'); 4 | exports.users = require('./users'); 5 | exports.sessions = require('./sessions'); 6 | exports.files = require('./files'); 7 | -------------------------------------------------------------------------------- /chapter01/FileManager/routes/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.requireUserAuth = function(req, res, next) { 4 | // redirect user to login page if he's not logged in 5 | if (!req.session.username) { 6 | return res.redirect('/sessions/new'); 7 | } 8 | // needed in the layout for displaying the logout button 9 | res.locals.isLoggedIn = true; 10 | 11 | next(); 12 | }; 13 | -------------------------------------------------------------------------------- /chapter01/FileManager/routes/sessions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var User = require('../models/user'); 4 | 5 | exports.new = function(req, res, next) { 6 | res.render('sessions/new', { 7 | info: req.flash('info')[0], 8 | error: req.flash('error')[0] 9 | }); 10 | }; 11 | 12 | exports.create = function(req, res, next) { 13 | User.authenticate(req.body.username, req.body.password, function(err, userData) { 14 | if (err) { return next(err); } 15 | 16 | if (userData !== false) { 17 | req.session.username = userData.username; 18 | req.session.userId = userData._id; 19 | res.redirect('/'); 20 | } else { 21 | req.flash('error', 'Bad username/password'); 22 | res.redirect('/sessions/new'); 23 | } 24 | }); 25 | }; 26 | 27 | exports.destroy = function(req, res, next) { 28 | delete req.session.username; 29 | delete req.session.userId; 30 | req.flash('info', 'You have successfully logged out'); 31 | res.redirect('/sessions/new'); 32 | }; 33 | -------------------------------------------------------------------------------- /chapter01/FileManager/routes/users.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var User = require('../models/user'); 4 | var File = require('../models/file'); 5 | var db = require('../lib/db'); 6 | 7 | exports.new = function(req, res, next) { 8 | res.render('users/new', { 9 | error: req.flash('error')[0] 10 | }); 11 | }; 12 | 13 | exports.create = function(req, res, next) { 14 | var user = new User({ username: req.body.username }); 15 | 16 | user.saveWithPassword(req.body.password, function(err) { 17 | if (err) { 18 | if (db.isValidationError(err)) { 19 | req.flash('error', 'Invalid username/password'); 20 | return res.redirect('/users/new'); 21 | } else if (db.isDuplicateKeyError(err)) { 22 | req.flash('error', 'Username already exists'); 23 | return res.redirect('/users/new'); 24 | } else { 25 | return next(err); 26 | } 27 | } 28 | 29 | File.createFolder(user._id, function(err) { 30 | if (err) { return next(err); } 31 | 32 | req.flash('info', 'Username created, you can now log in!'); 33 | res.redirect('/sessions/new'); 34 | }); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /chapter01/FileManager/views/files/index.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h2 Hello #{username} 5 | 6 | if !files.length 7 | h3 You don't have any files stored! 8 | else 9 | h3 You have #{files.length} files stored! 10 | 11 | if info 12 | p.notification.info= info 13 | 14 | if error 15 | p.notification.error= error 16 | 17 | 18 | div#upload-form 19 | form(action='/files', method='POST', enctype="multipart/form-data") 20 | div.browse-file 21 | input(type='text', id='fake-upload-box', placeholder='Upload new file!') 22 | input(type='file', name='file') 23 | button(type='submit') Go! 24 | 25 | if files.length 26 | table.file-list 27 | thead 28 | tr 29 | th Name 30 | th Size 31 | th Delete 32 | tbody 33 | each file in files 34 | tr 35 | td 36 | a(href="/files/#{encodeURIComponent(file.name)}") #{file.name} 37 | td #{helpers.files.formatSize(file.stats.size)} 38 | td 39 | form(action="/files/#{encodeURIComponent(file.name)}", method='POST') 40 | input(type='hidden', name="_method", value='DELETE') 41 | input(type='submit', value='delete') 42 | -------------------------------------------------------------------------------- /chapter01/FileManager/views/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | html 3 | head 4 | title File Store 5 | link(rel='stylesheet', href='http://fonts.googleapis.com/css?family=IM+Fell+Great+Primer') 6 | link(rel='stylesheet', href='/stylesheets/normalize.css', type='text/css') 7 | link(rel='stylesheet', href='/stylesheets/style.css', type='text/css') 8 | body 9 | header 10 | h1 File Store 11 | 12 | if isLoggedIn 13 | div 14 | form(action='/sessions', method='POST') 15 | input(type='hidden', name='_method', value='DELETE') 16 | input(type='submit', class='sign-out', value='Sign out') 17 | 18 | div.container 19 | block content 20 | 21 | script(src='http://code.jquery.com/jquery-1.10.1.min.js') 22 | script(src='/javascripts/file-upload.js') 23 | -------------------------------------------------------------------------------- /chapter01/FileManager/views/sessions/new.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | div.user-form 5 | h2 Login 6 | 7 | if error 8 | p.notification.error= error 9 | 10 | if info 11 | p.notification.info= info 12 | 13 | form(action='/sessions', method='post') 14 | p: input(type='text', name='username', placeholder='Username') 15 | p: input(type='password', name='password', placeholder='Password') 16 | p: input(type='submit', value='Login') 17 | 18 | p: a(href='/users/new') New user? Signup! 19 | -------------------------------------------------------------------------------- /chapter01/FileManager/views/users/new.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | div.user-form 5 | h2 Signup 6 | 7 | if error 8 | p.notification.error= error 9 | 10 | form(action='/users', method='post') 11 | p: input(type='text', name='username', placeholder='Username') 12 | p: input(type='password', name='password', placeholder='Password') 13 | p: input(type='submit', value='Signup') 14 | 15 | p: a(href='/sessions/new') Back to login 16 | -------------------------------------------------------------------------------- /chapter02/custom-middleware-system/app/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var RequestHandler = require('./requestHandler'); 4 | var util = require('util'); 5 | 6 | function App() { 7 | // allows us to call App() without using the `new` keyword 8 | if (!(this instanceof App)) { 9 | return new App(); 10 | } 11 | 12 | this.stack = []; 13 | this.handleRequest = this.handleRequest.bind(this); 14 | } 15 | 16 | App.prototype.use = function(route, fn) { 17 | if (typeof route !== 'string') { 18 | fn = route; 19 | route = '/'; 20 | } 21 | 22 | // strip trailing slash 23 | if (route !== '/' && route[route.length - 1] === '/' ) { 24 | route = route.slice(0, -1); 25 | } 26 | 27 | if (fn.length !== 4) { 28 | this.stack.push({ 29 | handle: fn, 30 | route: route 31 | }); 32 | } else { 33 | this.customErrorHandler = fn; 34 | } 35 | }; 36 | 37 | App.prototype.handleRequest = function(req, res, _next) { 38 | var next; 39 | 40 | if (_next) { 41 | next = function(err) { 42 | if (util.isError(err)) { 43 | _next(err); 44 | } else { 45 | _next(); 46 | } 47 | }; 48 | } 49 | 50 | new RequestHandler(this.stack, this.customErrorHandler, next).next(req, res); 51 | }; 52 | 53 | module.exports = App; 54 | -------------------------------------------------------------------------------- /chapter02/custom-middleware-system/app/requestHandler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var url = require('url'); 4 | 5 | function getPathname(str) { 6 | return url.parse(str).pathname; 7 | } 8 | 9 | function RequestHandler(stack, errorHandler, notFoundHandler) { 10 | this.index = 0; 11 | this.stack = stack; 12 | this.errorHandler = errorHandler || function(err, req, res) { 13 | res.writeHead(500, { 'Content-Type': 'text/html' }); 14 | res.end('

Internal Server Error

' + err.stack + '
'); 15 | }; 16 | this.notFoundHandler = notFoundHandler || function(req, res) { 17 | res.writeHead(404, { 'Content-Type': 'text/html' }); 18 | res.end("Cannot " + req.method.toUpperCase() + " " + req.url); 19 | }; 20 | } 21 | 22 | RequestHandler.prototype.next = function(req, res) { 23 | if (this.index < this.stack.length) { 24 | var _next = (function(err) { 25 | if (!err) { 26 | this.next(req, res); 27 | } else { 28 | this.errorHandler(err, req, res); 29 | } 30 | }).bind(this); 31 | 32 | try { 33 | var middleware = this.stack[this.index++]; 34 | 35 | if (middleware.route !== '/') { 36 | var regexp = new RegExp('^\/' + middleware.route.substring(1) + '\/?$'); 37 | // custom route, so we need to alter the `req.url` and remove the 'root' 38 | if (regexp.test(getPathname(req.url))) { 39 | req.originalUrl = req.url; 40 | req.url = req.url.replace(middleware.route, '/'); 41 | } else { 42 | // the route isn't matched, so we call `_next()` 43 | return _next(); 44 | } 45 | } 46 | 47 | middleware.handle(req, res, _next); 48 | } 49 | catch(err) { 50 | this.errorHandler(err, req, res); 51 | } 52 | } else { 53 | this.notFoundHandler(req, res); 54 | } 55 | } 56 | 57 | module.exports = RequestHandler; 58 | -------------------------------------------------------------------------------- /chapter02/custom-middleware-system/app/router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var util = require('util'); 4 | var pathToRegexp = require('path-to-regexp'); 5 | var App = require('./index'); 6 | var url = require('url'); 7 | 8 | function getPathname(str) { 9 | return url.parse(str).pathname; 10 | } 11 | 12 | function Router() { 13 | if (!(this instanceof Router)) { 14 | return new Router(); 15 | } 16 | 17 | // call the 'super' constructor 18 | App.call(this); 19 | } 20 | 21 | util.inherits(Router, App); 22 | 23 | var VERBS = ['all', 'get', 'post', 'put', 'delete', 'head', 'options']; 24 | VERBS.map(function(verb) { 25 | Router.prototype[verb] = function(path, fn) { 26 | var _this = this; 27 | var keys = []; 28 | // in case path is a string, transform it into an Express-type RegExp 29 | var regex = (path.constructor.name === 'RegExp') ? path : pathToRegexp(path, keys); 30 | 31 | var args = Array.prototype.slice.call(arguments); 32 | // multiple handlers (functions) can be specified for a given path 33 | var handlers = args.slice(1); 34 | 35 | handlers.forEach(function(fn) { 36 | _this.use(function(req, res, next) { 37 | // app.all behaves like a regular verb (for example: app.get), 38 | // but it matches all HTTP verbs 39 | if ((verb !== 'all') && (req.method !== verb.toUpperCase())) { return next(); } 40 | // '*' matches all routes 41 | if ((path !== '*') && !regex.test(getPathname(req.url))) { return next(); } 42 | 43 | var params = regex.exec(getPathname(req.url)).slice(1); 44 | 45 | if (keys.length) { 46 | req.params = {}; 47 | keys.forEach(function(key, i) { 48 | if (params[i]) { 49 | req.params[key.name] = params[i]; 50 | } 51 | }); 52 | } else { 53 | req.params = {}; 54 | } 55 | 56 | fn(req, res, next); 57 | }); 58 | }); 59 | } 60 | }); 61 | 62 | module.exports = Router; 63 | -------------------------------------------------------------------------------- /chapter02/custom-middleware-system/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-middleware-system", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "sample-router.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 10 | "license": "MIT", 11 | "dependencies": { 12 | "path-to-regexp": "~0.2.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter02/custom-middleware-system/sample-router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var http = require('http'); 4 | var express = require('./app/router'); 5 | var app = express(); 6 | 7 | app.get('/', function(req, res, next) { 8 | req.message = 'Hello'; 9 | next(); 10 | }, function(req, res, next) { 11 | req.message += ' World!'; 12 | next(); 13 | }, function(req, res, next) { 14 | res.end(req.message); 15 | }); 16 | 17 | app.get('/app2', function(req, res, next) { 18 | res.writeHead(200, { 'Content-Type': 'text/html' }); 19 | res.end('app2'); 20 | }); 21 | 22 | app.get('/users/:user/:name?', function(req, res, next) { 23 | res.writeHead(200, { 'Content-Type': 'text/html' }); 24 | res.end(JSON.stringify(req.params)); 25 | }); 26 | 27 | app.all('*', function(req, res, next) { 28 | res.writeHead(404, { 'Content-Type': 'text/html' }); 29 | res.end('404 - Page Not Found'); 30 | }); 31 | 32 | http.createServer(app.handleRequest).listen(7777); 33 | console.log('server listening on port 7777'); 34 | -------------------------------------------------------------------------------- /chapter02/custom-middleware-system/sample.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var http = require('http'); 4 | var express = require('./app'); 5 | var app = express(); 6 | 7 | app.use(function(req, res, next) { 8 | if (req.url === '/') { 9 | res.writeHead(200, { 'Content-Type': 'text/html' }); 10 | return res.end('Hello world!'); 11 | } 12 | next(); 13 | }); 14 | 15 | app.use('/404', function(req, res, next) { 16 | res.writeHead(404, { 'Content-Type': 'text/html' }); 17 | return res.end('No such page.'); 18 | }); 19 | 20 | app.use('/500', function(req, res, next) { 21 | return next(new Error('something bad happened')); 22 | }); 23 | 24 | var app2 = express(); 25 | app2.use('/app2', function(req, res, next) { 26 | res.writeHead(200, { 'Content-Type': 'text/html' }); 27 | return res.end('Response from the second app.'); 28 | }); 29 | 30 | app.use(app2.handleRequest); 31 | 32 | app.use(function(err, req, res, next) { 33 | res.writeHead(500, { 'Content-Type': 'text/html' }); 34 | res.end('

500 Internal Server Error

'); 35 | }); 36 | 37 | http.createServer(app.handleRequest).listen(7777); 38 | console.log('server listening on port 7777'); 39 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 - Page Not Found 6 | 7 | 8 | 404 - Page Not Found 9 | 10 | 11 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 500 - Internal Server Error 6 | 7 | 8 | 500 - Internal Server Error 9 | 10 | 11 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/app-routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var util = require('util'); 5 | var app = express(); 6 | 7 | app.get('/blog/:slug.:format', function(req, res, next) { 8 | res.send('Hello world'); 9 | }); 10 | 11 | console.log(util.inspect(app._router.stack, null, 4)); 12 | 13 | app.listen(7777); 14 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/cache-render-configured.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | app.set('view engine', 'ejs'); 7 | 8 | var cacheRender = function(store, urlsToCache) { 9 | var store = store || {}; 10 | // if urlsToCache is not provided that means that all urls will be cached 11 | var urlsToCache = urlsToCache || []; 12 | 13 | return function(req, res, next) { 14 | var render = res.render; 15 | var getCacheFunction = function(res, id) { 16 | return function(err, content) { 17 | if (err) { throw err; } 18 | 19 | store[id] = content; 20 | res.send(content); 21 | }; 22 | }; 23 | var pathname = req.path; 24 | 25 | // if there are specific urls to be cached, but this one isn't in the list 26 | // then skip and go to the next middleware in the stack 27 | if ((urlsToCache.length !== 0) && (urlsToCache.indexOf(pathname) === -1)) { 28 | return next(); 29 | } 30 | 31 | if (!store[pathname]) { 32 | res.render = function() { 33 | var args = Array.prototype.slice.call(arguments); 34 | var cacheIt = getCacheFunction(res, pathname); 35 | 36 | if (args.length < 2) { 37 | args[1] = cacheIt; 38 | } else { 39 | if (typeof args[1] === 'function') { 40 | args[1] = cacheIt; 41 | } else { 42 | args[2] = cacheIt; 43 | } 44 | } 45 | render.apply(res, args); 46 | }; 47 | 48 | next(); 49 | } else { 50 | res.send(store[pathname]); 51 | } 52 | }; 53 | }; 54 | 55 | var store = { 56 | '/index' : 'Hello from the index page\n' 57 | }; 58 | var urlsToCache = ['/index', '/test']; 59 | app.use(cacheRender(store, urlsToCache)); 60 | 61 | app.use(function(req, res, next) { 62 | res.render('hello', { 63 | visited: new Date(), 64 | url: req.url 65 | }); 66 | }); 67 | 68 | app.listen(7777); 69 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/cache-render.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | app.set('view engine', 'ejs'); 7 | 8 | var store = {}; 9 | 10 | // /* 11 | app.use(function cacheRender(req, res, next) { 12 | var getCacheFunction = function(res, key) { 13 | // callback expected by res.render() 14 | return function(err, content) { 15 | if (err) { throw err; } 16 | 17 | // store the content in cache and serve it to the client 18 | store[key] = content; 19 | res.send(content); 20 | }; 21 | }; 22 | // the URL pathname and the content represent the key - value pair in the cache store 23 | var pathname = req.path; 24 | 25 | var render = res.render; 26 | // if the compiled template isn't in the cache load it 27 | if (!store[pathname]) { 28 | res.render = function() { 29 | var args = Array.prototype.slice.call(arguments); 30 | var cacheIt = getCacheFunction(res, pathname); 31 | 32 | // add the cache function to the arguments array 33 | if (args.length < 2) { 34 | args[1] = cacheIt; 35 | } else { 36 | if (typeof args[1] === 'function') { 37 | args[1] = cacheIt; 38 | } else { 39 | args[2] = cacheIt; 40 | } 41 | } 42 | render.apply(res, args); 43 | }; 44 | 45 | next(); 46 | } else { 47 | // serve the content directly from the cache 48 | res.send(store[pathname]); 49 | } 50 | }); 51 | // */ 52 | 53 | app.use(function respond(req, res, next) { 54 | res.render('hello', { 55 | visited: new Date(), 56 | url: req.url 57 | }); 58 | }); 59 | 60 | app.listen(7777); 61 | console.log('Server started: http://localhost:7777/'); 62 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/env-middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var logger = require('morgan'); 5 | var compress = require('compression'); 6 | var responseTime = require('response-time'); 7 | var errorHandler = require('errorhandler'); 8 | var app = express(); 9 | 10 | var configureByEnvironment = function(env) { 11 | if (!env) { env = process.env.NODE_ENV; } 12 | 13 | // default to development 14 | env = env || 'development'; 15 | 16 | return function(env2, callback) { 17 | if (env === env2) { callback(); } 18 | }; 19 | }; 20 | 21 | var configure = configureByEnvironment(); 22 | 23 | configure('development', function() { 24 | app.use(logger('dev')); 25 | app.use(responseTime()); 26 | app.use(express.static(__dirname + '/public')); 27 | }); 28 | configure('production', function() { 29 | app.use(logger()); 30 | // enable gzip compression for static resources in production 31 | app.use(compress()); 32 | app.use(express.static(__dirname + '/public')); 33 | }); 34 | 35 | app.get('/', function(req, res, next) { 36 | res.send('Hello world'); 37 | }); 38 | app.get('/error', function(req, res, next) { 39 | next(new Error('manually triggered error')); 40 | }); 41 | 42 | configure('development', function() { 43 | app.use(errorHandler()); 44 | }); 45 | configure('production', function() { 46 | app.use(function customErrorHandler(err, req, res, next) { 47 | res.status(500).send('500 - Internal Server Error'); 48 | console.error(err.stack); 49 | }); 50 | }); 51 | 52 | app.listen(7777); 53 | console.log('server listening on port 7777'); 54 | console.log('application environment: %s', process.env.NODE_ENV || 'development'); 55 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/handling-errors-enhanced.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var logger = require('morgan'); 6 | var fs = require('fs'); 7 | 8 | app.use(logger('dev')); 9 | 10 | app.get('/', function(req, res, next) { 11 | res.send('Hello world'); 12 | }); 13 | app.get('/error', function(req, res, next) { 14 | next(new Error('manually triggered error')); 15 | }); 16 | 17 | // if the request reaches this middleware it means 18 | // it didn't match any route, so it's a '404 - Page Not Found' error 19 | app.use(function(req, res, next) { 20 | var err = new Error('Page Not Found'); 21 | err.statusCode = 404; 22 | 23 | // pass the error to the custom error handler below 24 | next(err); 25 | }); 26 | 27 | var notFoundPage = fs.readFileSync(__dirname + '/404.html').toString('utf8'); 28 | var internalErrorPage = fs.readFileSync(__dirname + '/500.html').toString('utf8'); 29 | 30 | // custom error handler where we handle errors passed by other middleware 31 | app.use(function(err, req, res, next) { 32 | // if not specified, the statusCode defaults to 500 33 | // meaning it’s an internal error 34 | err.statusCode = err.statusCode || 500; 35 | 36 | switch(err.statusCode) { 37 | case 500: 38 | // when using Ajax we're usually expecting JSON, so in those situations 39 | // it's good to be consistent and respond with JSON when errors occur: 40 | if (req.xhr) { 41 | return res.status(err.statusCode).send({ error: '500 - Internal Server Error' }); 42 | } 43 | 44 | res.format({ 45 | text: function() { 46 | res.status(500).send('500 - Internal Server Error'); 47 | }, 48 | html: function() { 49 | res.status(err.statusCode).send(internalErrorPage); 50 | }, 51 | json: function() { 52 | // $ curl -H "Accept: */json" http://localhost:7777/error 53 | // { 54 | // "error": "500 - Internal Server Error" 55 | // } 56 | res.status(err.statusCode).send({ error: '500 - Internal Server Error' }); 57 | } 58 | }); 59 | // log the error to stderr 60 | console.error(err.stack); 61 | break; 62 | case 404: 63 | res.status(err.statusCode).send(notFoundPage); 64 | break; 65 | default: 66 | console.error('Unhandled code', err.statusCode, err.stack); 67 | res.status(err.statusCode).send('An error happened'); 68 | } 69 | }); 70 | 71 | app.listen(7777); 72 | console.log('environment: %s', process.env.NODE_ENV || 'development'); 73 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/handling-errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var logger = require('morgan'); 6 | var fs = require('fs'); 7 | 8 | app.use(logger('dev')); 9 | 10 | app.get('/', function(req, res, next) { 11 | res.send('Hello world'); 12 | }); 13 | app.get('/error', function(req, res, next) { 14 | next(new Error('manually triggered error')); 15 | }); 16 | 17 | // if the request reaches this middleware it means 18 | // it didn't match any route, so it's a '404 - Page Not Found' error 19 | app.use(function(req, res, next) { 20 | var err = new Error('Page Not Found'); 21 | err.statusCode = 404; 22 | 23 | // pass the error to the custom error handler below 24 | next(err); 25 | }); 26 | 27 | var notFoundPage = fs.readFileSync(__dirname + '/404.html').toString('utf8'); 28 | var internalErrorPage = fs.readFileSync(__dirname + '/500.html').toString('utf8'); 29 | 30 | // custom error handler where we handle errors passed by other middleware 31 | app.use(function(err, req, res, next) { 32 | // if not specified, the statusCode defaults to 500 33 | // meaning it’s an internal error 34 | err.statusCode = err.statusCode || 500; 35 | 36 | switch(err.statusCode) { 37 | case 500: 38 | res.status(err.statusCode).send(internalErrorPage); 39 | // log the error to stderr 40 | console.error(err.stack); 41 | break; 42 | case 404: 43 | res.status(err.statusCode).send(notFoundPage); 44 | break; 45 | default: 46 | console.error('Unhandled code', err.statusCode, err.stack); 47 | res.status(err.statusCode).send('An error happened'); 48 | } 49 | }); 50 | 51 | app.listen(7777); 52 | console.log('environment: %s', process.env.NODE_ENV || 'development'); 53 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/ip-whitelist.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | app.enable('trust proxy'); 7 | 8 | app.use(function(req, res, next) { 9 | var ip = req.ip; 10 | 11 | if (ip === '127.0.0.1' || /^192\.168\./.test(ip)) { 12 | next(); 13 | } else { 14 | res.status(403).send('Forbidden!\n'); 15 | } 16 | }); 17 | 18 | app.get('*', function(req, res, next) { 19 | res.send('Hello world'); 20 | }); 21 | 22 | app.listen(process.env.PORT || 7777); 23 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/mounting/admin.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express.Router(); 5 | 6 | app.get('/', function(req, res, next) { 7 | res.send('Admin app says hello'); 8 | }); 9 | 10 | app.get('/error', function(req, res, next) { 11 | return next(new Error('err')); 12 | }); 13 | 14 | app.use(function(err, req, res, next) { 15 | console.error(err.stack); 16 | res.send('ADMIN: an error occured'); 17 | }); 18 | 19 | module.exports = app; 20 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/mounting/blog.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express.Router(); 5 | 6 | app.get('/', function(req, res, next) { 7 | res.send('Blog app says hello'); 8 | }); 9 | 10 | app.get('/error', function(req, res, next) { 11 | return next(new Error('err')); 12 | }); 13 | 14 | app.use(function(err, req, res, next) { 15 | console.error(err.stack); 16 | res.send('BLOG: an error occured'); 17 | }); 18 | 19 | module.exports = app; 20 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/mounting/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | var blog = require('./blog'); 7 | var admin = require('./admin'); 8 | 9 | app.use('/blog', blog); 10 | app.use('/admin', admin); 11 | 12 | app.listen(7777); 13 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "middleware-examples", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "app-routes.js", 6 | "dependencies": { 7 | "express": "~4.4.5", 8 | "morgan": "~1.1.1", 9 | "response-time": "~2.0.0", 10 | "compression": "~1.0.8", 11 | "errorhandler": "~1.1.1" 12 | }, 13 | "devDependencies": {}, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1" 16 | }, 17 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/public/core.js: -------------------------------------------------------------------------------- 1 | alert('hello world'); 2 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/reusable-handlers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | var articles = { 7 | 'express-tutorial' : { 8 | title: 'Practical Web Apps with Express', 9 | content: 'Lean how to create web apps with Express' 10 | }, 11 | 'node-videos': { 12 | title: 'Node.js video tutorials', 13 | content: 'Practical Node tips!' 14 | } 15 | }; 16 | 17 | var loadArticle = function(req, res, next) { 18 | if (!articles[req.params.article]) { 19 | return res.status(404).send('No such article!'); 20 | } 21 | 22 | req.article = articles[req.params.article]; 23 | 24 | next(); 25 | }; 26 | 27 | var requireAdmin = function(req, res, next) { 28 | if (req.ip !== '127.0.0.1') { 29 | return res.status(403).send('Forbidden'); 30 | } 31 | 32 | next(); 33 | }; 34 | 35 | app.param('article', loadArticle); 36 | app.get('/articles/:article/:action', requireAdmin); 37 | 38 | app.get('/articles/:article', function(req, res, next) { 39 | res.send(req.article.content); 40 | }); 41 | 42 | app.get('/articles/:article/edit', requireAdmin, function(req, res, next) { 43 | res.send('Editing article ' + req.article.title); 44 | }); 45 | 46 | app.listen(7777); 47 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/using-middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | console.log('App process id (pid): %s', process.pid); 4 | 5 | var express = require('express'); 6 | var morgan = require('morgan'); 7 | var app = express(); 8 | 9 | app.use('/public', express.static(__dirname + '/public')); 10 | // each time somebody visits the admin URL 11 | // log the ip address as well as other details 12 | app.use('/admin', morgan({ immediate: true })); 13 | app.use('/admin', function auth(req, res, next) { 14 | // normally you should authenticate the user somehow 15 | // but for the sake of the demo we just set the `isAdmin` flag directly 16 | req.isAdmin = true; 17 | next(); 18 | }); 19 | app.use(function respond(req, res) { 20 | if (req.isAdmin) { 21 | res.send('Hello admin!\n'); 22 | } else { 23 | res.send('Hello user!\n'); 24 | } 25 | }); 26 | 27 | app.listen(7777); 28 | console.log('server listening on port 7777'); 29 | 30 | // $ DEBUG=express:router node using-middleware.js 31 | -------------------------------------------------------------------------------- /chapter02/middleware-examples/views/hello.ejs: -------------------------------------------------------------------------------- 1 |

Time: <%- visited %>

2 |

URL: <%- url %>

3 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "mongoUrl": "mongodb://localhost/smartnotes-dev", 4 | "port": 3000 5 | }, 6 | "test": { 7 | "mongoUrl": "mongodb://localhost/smartnotes-test", 8 | "port": 3000 9 | }, 10 | "production": { 11 | "mongoUrl": "mongodb://localhost/smartnotes", 12 | "port": 3000 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/lib/db.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var mongoose = require('mongoose'); 4 | 5 | exports.isValidationError = function(err) { 6 | return (err && err.message && /ValidationError/.test(err.message)); 7 | }; 8 | 9 | exports.isDuplicateKeyError = function(err) { 10 | return (err && err.message && /duplicate key/.test(err.message)); 11 | }; 12 | 13 | exports.connect = function(url, cb) { 14 | cb = cb || function(err) { 15 | if (err) { 16 | console.error('database connection failure: \n' + err.stack); 17 | process.exit(1); 18 | } 19 | }; 20 | 21 | mongoose.connect(url, { safe: true }, cb); 22 | }; 23 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/lib/validator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var validator = require('validator'); 4 | var extend = require('lodash').extend; 5 | var memoize = require('memoizejs'); 6 | 7 | var customValidator = extend({}, validator); 8 | 9 | customValidator.validate = function(method) { 10 | if (!customValidator[method]) { 11 | throw new Error('validator method does not exist'); 12 | } 13 | 14 | // get an array of the arguments except the first one (the method name) 15 | var args = Array.prototype.slice.call(arguments, 1); 16 | 17 | return function(value) { 18 | return customValidator[method].apply(customValidator, Array.prototype.concat(value, args)); 19 | }; 20 | }; 21 | 22 | customValidator.validate = memoize(customValidator.validate); 23 | 24 | module.exports = customValidator; 25 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/models/note.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var validator = require('../lib/validator'); 4 | var timestamps = require('mongoose-timestamp'); 5 | var mongoose = require('mongoose'); 6 | 7 | var Schema = mongoose.Schema; 8 | var ObjectId = Schema.ObjectId; 9 | 10 | var Note = new Schema({ 11 | title: { 12 | type: String, 13 | required: true, 14 | validate: validator.validate('isLength', 3, 255) 15 | }, 16 | description: { 17 | type: String, 18 | required: true, 19 | validate: validator.validate('isLength', 10, 255) 20 | }, 21 | userId: { 22 | type: ObjectId, 23 | required: true, 24 | ref: 'User' 25 | }, 26 | rating: { 27 | type: Number, 28 | default: 0, 29 | min: 0, 30 | max: 10 31 | }, 32 | category: { 33 | type: String, 34 | default: 'general' 35 | }, 36 | public: { 37 | type: Boolean, 38 | default: false 39 | } 40 | }); 41 | 42 | Note.plugin(timestamps); 43 | 44 | module.exports = mongoose.model('Note', Note); 45 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/models/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var validator = require('../lib/validator'); 4 | var passportLocalMongoose = require('passport-local-mongoose'); 5 | 6 | var mongoose = require('mongoose'); 7 | var Schema = mongoose.Schema; 8 | var ObjectId = Schema.ObjectId; 9 | 10 | var User = new Schema({ 11 | username: { 12 | type: String, 13 | required: true, 14 | unique: true, 15 | validate: [{ 16 | validator: validator.validate('isAlphanumeric'), 17 | msg: 'username must be alphanumeric' 18 | }, { 19 | validator: validator.validate('isLength', 4, 255), 20 | msg: 'username must have 4-255 chars' 21 | }] 22 | }, 23 | email: { 24 | type: String, 25 | required: true, 26 | unique: true, 27 | validate: validator.validate('isEmail') 28 | }, 29 | name: { 30 | type: String 31 | } 32 | }); 33 | 34 | User.plugin(passportLocalMongoose); 35 | 36 | module.exports = mongoose.model('User', User); 37 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SmartNotes", 3 | "version": "0.0.0", 4 | "description": "Creating RESTful APIs with Express", 5 | "main": "server.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "express": "~4.5.0", 11 | "mongoose": "~3.8.6", 12 | "validator": "~3.2.1", 13 | "mongoose-timestamp": "~0.2.0", 14 | "memoizejs": "~0.1.0", 15 | "passport-local-mongoose": "~0.2.5", 16 | "async": "~0.2.10", 17 | "lodash": "~2.4.1", 18 | "method-override": "~2.0.2", 19 | "body-parser": "~1.4.3", 20 | "basic-auth-connect": "~1.0.0" 21 | }, 22 | "devDependencies": { 23 | "should": "~3.1.2", 24 | "mocha": "~1.17.1", 25 | "sinon": "~1.8.1", 26 | "proxyquire": "~0.5.2", 27 | "supertest": "~0.9.0" 28 | }, 29 | "scripts": { 30 | "test": "NODE_ENV=test node node_modules/.bin/mocha --reporter spec test/unit/ test/functional/ --timeout 10000 --slow 2000", 31 | "start": "node server.js" 32 | }, 33 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 34 | "license": "MIT" 35 | } 36 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | users: require('./users'), 5 | notes: require('./notes') 6 | }; 7 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/routes/notes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _ = require('lodash'); 4 | var db = require('../lib/db'); 5 | 6 | exports.index = function(req, res, next) { 7 | req.Note.find({ userId: req.user._id }, function(err, notes) { 8 | if (err) { return next(err); } 9 | 10 | res.send(notes); 11 | }); 12 | }; 13 | 14 | exports.show = function(req, res, next) { 15 | req.Note.findOne({ _id: req.params.id, userId: req.user._id }, function(err, note) { 16 | if (err) { return next(err); } 17 | 18 | res.send(note); 19 | }); 20 | }; 21 | 22 | exports.create = function(req, res, next) { 23 | var note = new req.Note(_.pick(req.body, ['title', 'description', 'rating', 'category', 'public'])); 24 | note.userId = req.user._id; 25 | 26 | note.save(function(err, noteData) { 27 | if (err) { 28 | if (db.isValidationError(err)) { 29 | res.status(422).send({ errors: ['invalid data'] }); 30 | } else { 31 | next(err); 32 | } 33 | } else { 34 | res 35 | .status(201) 36 | .set('Location', '/notes/' + noteData._id) 37 | .send(noteData); 38 | } 39 | }); 40 | }; 41 | 42 | exports.showPublic = function(req, res, next) { 43 | req.User.findOne({ username: req.params.username }, function(err, user) { 44 | if (err) { return next(err); } 45 | 46 | if (!user) { return res.status(404).send({ errors: ['no such user'] })}; 47 | 48 | req.Note.find({ userId: user._id, public: true }, function(err, notes) { 49 | if (err) { return next(err); } 50 | 51 | res.send(notes); 52 | }); 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var db = require('./lib/db'); 6 | var config = require('./config.json')[app.get('env')]; 7 | 8 | var methodOverride = require('method-override'); 9 | var bodyParser = require('body-parser'); 10 | 11 | var User = require('./models/user'); 12 | var Note = require('./models/note'); 13 | var routes = require('./routes'); 14 | 15 | db.connect(config.mongoUrl); 16 | 17 | app.use(bodyParser.urlencoded({ extended: true })); 18 | app.use(bodyParser.json()); 19 | app.use(methodOverride(function(req, res) { 20 | if (req.body && typeof req.body === 'object' && '_method' in req.body) { 21 | // look in urlencoded POST bodies and delete it 22 | var method = req.body._method; 23 | delete req.body._method; 24 | return method; 25 | } 26 | })); 27 | 28 | app.use(function(req, res, next) { 29 | req.User = User; 30 | req.Note = Note; 31 | next(); 32 | }); 33 | 34 | app.get('/users/:username', routes.users.show); 35 | app.post('/users', routes.users.create); 36 | app.get('/users/:username/notes', routes.notes.showPublic); 37 | app.patch('/users/:username', routes.users.authenticate, routes.users.update); 38 | app.get('/notes', routes.users.authenticate, routes.notes.index); 39 | app.post('/notes', routes.users.authenticate, routes.notes.create); 40 | app.get('/notes/:id', routes.users.authenticate, routes.notes.show); 41 | 42 | module.exports = app; 43 | 44 | if (!module.parent) { 45 | app.listen(config.port); 46 | console.log('(%s) app listening on port %s', app.get('env'), config.port); 47 | } 48 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/test/fixtures/notes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Express tutorials", 4 | "description": "There are a lot of online Express tutorials", 5 | "category": "tutorials", 6 | "_id": "53013524c6a9cb5e45868c01" 7 | }, 8 | { 9 | "title": "Latest Node.js tips and tricks available", 10 | "description": "Checkout the official website for more details", 11 | "category": "tutorials", 12 | "_id": "53013524c6a9cb5e45868c02", 13 | "public": true 14 | }, 15 | { 16 | "title": "Node webinar held Wednesday", 17 | "description": "Location: San Francisco; Time: 8 PM", 18 | "_id": "53013524c6a9cb5e45868c03", 19 | "public": true 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/test/fixtures/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "username": "johndoe", 4 | "password": "johns_password", 5 | "email": "johndoe@example.com", 6 | "name": "John Doe" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/test/functional/notes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var request = require('supertest'); 4 | var should = require('should'); 5 | var app = require('../../server'); 6 | var db = require('../utils/db'); 7 | 8 | var user = require('../fixtures/users.json')[0]; 9 | var note = require('../fixtures/notes.json')[0]; 10 | 11 | describe('Notes-Routes', function(done) { 12 | before(function(done) { 13 | db.setupDatabase(done); 14 | }); 15 | 16 | after(function(done) { 17 | db.reset(done); 18 | }); 19 | 20 | it("should return the user's notes", function(done) { 21 | request(app) 22 | .get('/notes') 23 | .set('Authorization', 'Basic ' + new Buffer(user.username + ':' + user.password).toString('base64')) 24 | .expect(200) 25 | .expect('Content-Type', /json/) 26 | .end(function(err, res) { 27 | if (err) { throw err; } 28 | 29 | res.body.should.be.an.Array; 30 | 31 | done(); 32 | }); 33 | }); 34 | 35 | it("should retrieve a particular note", function(done) { 36 | request(app) 37 | .get('/notes/' + note._id) 38 | .set('Authorization', 'Basic ' + new Buffer(user.username + ':' + user.password).toString('base64')) 39 | .expect(200) 40 | .expect('Content-Type', /json/) 41 | .end(function(err, res) { 42 | if (err) { throw err; } 43 | 44 | res.body.should.have.properties('createdAt', 'updatedAt', '_id', 'userId', 'title', 'description'); 45 | 46 | done(); 47 | }); 48 | }); 49 | 50 | it("should create a note", function(done) { 51 | request(app) 52 | .post('/notes') 53 | .set('Authorization', 'Basic ' + new Buffer(user.username + ':' + user.password).toString('base64')) 54 | .send({ 55 | title: 'my random note', 56 | description: 'random description here' 57 | }) 58 | .expect(201) 59 | .expect('Location', /\/notes\/[0-9a-f]{24}/) 60 | .expect('Content-Type', /json/) 61 | .end(function(err, res) { 62 | if (err) { throw err; } 63 | 64 | res.body.should.have.properties('createdAt', 'updatedAt', '_id', 'userId', 'title', 'description'); 65 | 66 | done(); 67 | }); 68 | }); 69 | 70 | it("should return the user's public notes", function(done) { 71 | request(app) 72 | .get('/users/' + user.username + '/notes') 73 | .expect(200) 74 | .expect('Content-Type', /json/) 75 | .end(function(err, res) { 76 | if (err) { throw err; } 77 | 78 | res.body.should.be.an.Array; 79 | res.body.forEach(function(note) { 80 | note.public.should.be.true; 81 | }); 82 | 83 | done(); 84 | }); 85 | }); 86 | 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/test/functional/users.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var request = require('supertest'); 4 | var should = require('should'); 5 | var app = require('../../server'); 6 | var db = require('../utils/db'); 7 | 8 | var user = require('../fixtures/users.json')[0]; 9 | 10 | describe('User-Routes', function(done) { 11 | before(function(done) { 12 | db.setupDatabase(done); 13 | }); 14 | 15 | after(function(done) { 16 | db.reset(done); 17 | }); 18 | 19 | it('should return the user details', function(done) { 20 | request(app) 21 | .get('/users/' + user.username) 22 | .expect(200) 23 | .expect('Content-Type', /json/) 24 | .end(function(err, res) { 25 | if (err) { throw err; } 26 | 27 | res.body.should.have.properties('username', 'email', 'name'); 28 | 29 | done(); 30 | }); 31 | }); 32 | 33 | it('should create a new user', function(done) { 34 | request(app) 35 | .post('/users') 36 | .send({ 37 | username: 'newuser', 38 | password: 'newuser_password', 39 | email: 'newuser@example.com', 40 | name: 'doe' 41 | }) 42 | .expect(201) 43 | .expect('Content-Type', /json/) 44 | .expect('Location', '/users/newuser') 45 | .expect({ 46 | username: 'newuser', 47 | email: 'newuser@example.com', 48 | name: 'doe' 49 | }, done); 50 | }); 51 | 52 | it('should update the current user', function(done) { 53 | request(app) 54 | .patch('/users/' + user.username) 55 | .set('Authorization', 'Basic ' + new Buffer(user.username + ':' + user.password).toString('base64')) 56 | .send([{ 57 | op: 'replace', 58 | path: '/email', 59 | value: 'johndoe_the_third@example.com' 60 | }, { 61 | op: 'replace', 62 | path: '/name', 63 | value: 'John Doe The Third' 64 | }]) 65 | .expect(204, done); 66 | }); 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/test/unit/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var should = require('should'); 4 | var sinon = require('sinon'); 5 | var proxyquire = require('proxyquire'); 6 | var helpers = require('../utils/helpers'); 7 | var mongoose = helpers.getMongooseStub(); 8 | 9 | var shouldDefineSchemaProperty = helpers.shouldDefineSchemaProperty.bind(null, mongoose.Schema); 10 | var shouldRegisterSchema = helpers.shouldRegisterSchema.bind(null, mongoose.model, mongoose.Schema); 11 | var shouldBeRequired = helpers.shouldBeRequired.bind(null, mongoose.Schema); 12 | var shouldBeUnique = helpers.shouldBeUnique.bind(null, mongoose.Schema); 13 | var shouldBeA = helpers.shouldBeA.bind(null, mongoose.Schema); 14 | var shouldDefaultTo = helpers.shouldDefaultTo.bind(null, mongoose.Schema); 15 | var shouldBeBetween = helpers.shouldBeBetween.bind(null, mongoose.Schema); 16 | var shouldValidateThat = helpers.shouldValidateThat.bind(null, mongoose.Schema); 17 | var shouldValidateMany = helpers.shouldValidateMany.bind(null, mongoose.Schema); 18 | var shouldLoadPlugin = helpers.shouldLoadPlugin.bind(null, mongoose.Schema); 19 | 20 | describe('User', function() { 21 | var User, mongoosePassport; 22 | 23 | before(function() { 24 | mongoosePassport = sinon.stub(); 25 | User = proxyquire('../../models/user', { 26 | 'passport-local-mongoose': mongoosePassport, 27 | 'mongoose': mongoose 28 | }); 29 | }); 30 | 31 | it('should register the Mongoose model', function() { 32 | shouldRegisterSchema('User'); 33 | }); 34 | 35 | it('should load the passport plugin', function() { 36 | shouldLoadPlugin(mongoosePassport); 37 | }); 38 | 39 | describe('username', function() { 40 | it('should be required', function() { 41 | shouldBeRequired('username'); 42 | }); 43 | 44 | it('should be a string', function() { 45 | shouldBeA('username', String); 46 | }); 47 | 48 | it('should be unique', function() { 49 | shouldBeUnique('username'); 50 | }); 51 | 52 | it('should be alphanumeric and have 4-255 chars', function() { 53 | shouldValidateMany('username', { 54 | args: ['isAlphanumeric'], 55 | msg: 'username must be alphanumeric' 56 | }, { 57 | args: ['isLength', 4, 255], 58 | msg: 'username must have 4-255 chars' 59 | }); 60 | }); 61 | }); 62 | 63 | describe('email', function() { 64 | it('should be required', function() { 65 | shouldBeRequired('email', String); 66 | }); 67 | 68 | it('should be a string', function() { 69 | shouldBeA('email', String); 70 | }); 71 | 72 | it('should be unique', function() { 73 | shouldBeUnique('email'); 74 | }); 75 | 76 | it('should be a valid email', function() { 77 | shouldValidateThat('email', 'isEmail'); 78 | }); 79 | }); 80 | 81 | describe('name', function() { 82 | it('should be a string', function() { 83 | shouldBeA('name', String); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/test/unit/validator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var sinon = require('sinon'); 4 | var should = require('should'); 5 | var validator = require('../../lib/validator'); 6 | var proxyquire = require('proxyquire'); 7 | 8 | describe('validator', function() { 9 | describe('validate', function() { 10 | it("should throw an error if the delegated method doesn't exist", function() { 11 | delete validator.unknownMethod; 12 | (function() { 13 | validator.validate('unknownMethod'); 14 | }).should.throw(/validator method does not exist/i); 15 | }); 16 | 17 | it("should return a function", function() { 18 | validator.noop = function(){}; 19 | validator.validate('noop').should.be.a.Function; 20 | }); 21 | 22 | it("should be memoized", function() { 23 | var noop = sinon.stub(); 24 | var memoize = sinon.spy(function(fn) { return noop; }); 25 | var validator = proxyquire('../../lib/validator', { 26 | 'memoizejs': memoize 27 | }); 28 | 29 | memoize.calledOnce.should.be.true; 30 | validator.validate.should.eql(noop); 31 | }); 32 | 33 | describe("inner function", function() { 34 | it("should call the delegated method with the arguments in order", function() { 35 | var method = sinon.spy(); 36 | 37 | validator.myCustomValidationMethod = method; 38 | validator.validate('myCustomValidationMethod', 1, 2, 3)('str'); 39 | 40 | method.calledWith('str', 1, 2, 3).should.be.true; 41 | }); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/test/utils/db.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var config = require('../../config.json'); 4 | var db = require('../../lib/db'); 5 | var async = require('async'); 6 | var Note = require('../../models/note'); 7 | var User = require('../../models/user'); 8 | var userFixtures = require('../fixtures/users.json'); 9 | var notesFixtures = require('../fixtures/notes.json'); 10 | var mongoose = require('mongoose'); 11 | 12 | exports.connect = function(callback) { 13 | db.connect(config.test.mongoUrl, callback); 14 | }; 15 | 16 | // empty the database 17 | exports.reset = function(callback) { 18 | async.parallel([ 19 | function emptyNotesCollection(cb) { 20 | Note.remove({}, cb); 21 | }, 22 | function emptyUsersCollection(cb) { 23 | User.remove({}, cb); 24 | } 25 | ], callback); 26 | }; 27 | 28 | // populate the database with fixtures 29 | exports.populate = function(callback) { 30 | async.each(userFixtures, function(data, next) { 31 | User.register(new User({ 32 | username: data.username, 33 | email: data.email, 34 | name: data.name 35 | }), data.password, next) 36 | }, function(err) { 37 | if (err) { return callback(err); } 38 | 39 | User.findOne({ username: 'johndoe' }, function(err, user) { 40 | if (err) { return callback(err); } 41 | 42 | async.each(notesFixtures, function(data, next) { 43 | var note = new Note(data); 44 | note.userId = user._id; 45 | note.save(next); 46 | }, callback); 47 | }); 48 | }); 49 | }; 50 | 51 | // connect to, reset and populate database with fixtures 52 | exports.setupDatabase = function(callback) { 53 | var resetAndPopulate = function(err) { 54 | if (err) { return callback(err); } 55 | 56 | exports.reset(function(err) { 57 | if (err) { return callback(err); } 58 | 59 | exports.populate(callback); 60 | }); 61 | }; 62 | 63 | if (mongoose.connection.db) { 64 | return resetAndPopulate(); 65 | } else { 66 | exports.connect(); 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /chapter03/SmartNotes/test/utils/helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var should = require('should'); 4 | var sinon = require('sinon'); 5 | var validator = require('../../lib/validator'); 6 | 7 | exports.getMongooseStub = function() { 8 | var mongoose = {}; 9 | 10 | mongoose.Schema = sinon.stub(); 11 | mongoose.Schema.ObjectId = 'ObjectId'; 12 | mongoose.Schema.prototype.plugin = sinon.stub(); 13 | mongoose.model = sinon.stub(); 14 | 15 | return mongoose; 16 | }; 17 | 18 | // asserts that the schema has been called with a certain property && value 19 | exports.shouldDefineSchemaProperty = function(Schema, property) { 20 | sinon.assert.called(Schema.withArgs(sinon.match(property))); 21 | }; 22 | 23 | exports.shouldBeRequired = function(Schema, property) { 24 | var obj = {}; 25 | obj[property] = { 26 | required: true 27 | }; 28 | exports.shouldDefineSchemaProperty(Schema, obj); 29 | }; 30 | 31 | exports.shouldBeUnique = function(Schema, property) { 32 | var obj = {}; 33 | obj[property] = { 34 | unique: true 35 | }; 36 | exports.shouldDefineSchemaProperty(Schema, obj); 37 | }; 38 | 39 | // checks the type of the property 40 | exports.shouldBeA = function(Schema, property, type) { 41 | var obj = {}; 42 | obj[property] = { 43 | type: type 44 | }; 45 | exports.shouldDefineSchemaProperty(Schema, obj); 46 | }; 47 | 48 | exports.shouldDefaultTo = function(Schema, property, defaultValue) { 49 | var obj = {}; 50 | obj[property] = { 51 | default: defaultValue 52 | }; 53 | exports.shouldDefineSchemaProperty(Schema, obj); 54 | }; 55 | 56 | exports.shouldBeBetween = function(Schema, property, opts) { 57 | var obj = {}; 58 | obj[property] = { 59 | min: opts.min, 60 | max: opts.max 61 | }; 62 | exports.shouldDefineSchemaProperty(Schema, obj); 63 | }; 64 | 65 | exports.shouldValidateThat = function(Schema, property) { 66 | var args = Array.prototype.slice.call(arguments, 2); 67 | var obj = {}; 68 | obj[property] = { 69 | validate: validator.validate.apply(validator, args) 70 | }; 71 | exports.shouldDefineSchemaProperty(Schema, obj); 72 | }; 73 | 74 | // when using an array of validation functions 75 | exports.shouldValidateMany = function(Schema, property, validation1, validation2) { 76 | var obj = {}; 77 | obj[property] = { 78 | validate: [{ 79 | validator: validator.validate.apply(validator, validation1.args), 80 | msg: validation1.msg 81 | }, { 82 | validator: validator.validate.apply(validator, validation2.args), 83 | msg: validation2.msg 84 | }] 85 | }; 86 | exports.shouldDefineSchemaProperty(Schema, obj); 87 | }; 88 | 89 | exports.shouldRegisterSchema = function(Model, Schema, name) { 90 | Model.calledWith(name).should.be.true; 91 | Model.args[0][1].should.be.an.instanceOf(Schema); 92 | }; 93 | 94 | exports.shouldLoadPlugin = function(Schema, plugin) { 95 | sinon.assert.called(Schema.prototype.plugin.withArgs(plugin)); 96 | }; 97 | -------------------------------------------------------------------------------- /chapter03/etags/etags.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | var crypto = require('crypto'); 5 | var handleEtag = function(req, res, next) { 6 | res.cachable = function(options, isStaleCallback) { 7 | var isJson; 8 | 9 | if (!options.etag && !options.content) { 10 | throw new Error('Please provide either etag or content'); 11 | } 12 | 13 | if (options.etag) { 14 | res.set({ 'ETag': options.etag }); 15 | } else { 16 | if (typeof options.content === 'object') { 17 | isJson = true; 18 | options.content = JSON.stringify(options.content); 19 | } 20 | 21 | var hash = crypto.createHash('md5'); 22 | hash.update(options.content); 23 | res.set({ 'ETag': hash.digest('hex') }); 24 | 25 | if (!isStaleCallback) { 26 | isStaleCallback = function() { 27 | if (isJson) { res.set({ 'Content-Type': 'application/json' }) } 28 | res.send(options.content); 29 | }; 30 | } 31 | } 32 | 33 | // 304 Not Modified 34 | if (req.fresh) { 35 | // remove content headers 36 | if (res._headers) { 37 | Object.keys(res._headers).forEach(function(header) { 38 | if (header.indexOf('content') === 0) { 39 | res.removeHeader(header); 40 | } 41 | }); 42 | } 43 | 44 | res.statusCode = 304; 45 | return res.end(); 46 | } else { 47 | isStaleCallback(); 48 | } 49 | }; 50 | 51 | next(); 52 | }; 53 | 54 | app.get('/fruits/:id', handleEtag, function(req, res, next) { 55 | res.cachable({ content: { id: req.params.id } }); 56 | }); 57 | 58 | app.get('/apples/:id', handleEtag, function(req, res, next) { 59 | res.cachable({ content: 'apple with id ' + req.params.id }); 60 | }); 61 | 62 | app.get('/oranges/:id', handleEtag, function(req, res, next) { 63 | // .. 64 | var etag = 'AbcAsaDAAsD123'; 65 | 66 | res.cachable({ etag: etag }, function() { 67 | // .. 68 | res.send('I have an orange'); 69 | }); 70 | }); 71 | 72 | app.listen(3333); 73 | -------------------------------------------------------------------------------- /chapter03/etags/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "etags", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "etags.js", 6 | "dependencies": { 7 | "express": "~4.5.1" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 14 | "license": "ISC" 15 | } 16 | -------------------------------------------------------------------------------- /chapter04/different-engines/ejs-server.js: -------------------------------------------------------------------------------- 1 | require('ejs').filters.toUpper = function(str) { return str.toUpperCase(); } 2 | 3 | var express = require('express') 4 | , cons = require('consolidate') 5 | , app = express(); 6 | 7 | // assign the ejs engine to .html files 8 | app.engine('html', cons.ejs); 9 | 10 | // set .html as the default extension 11 | app.set('view engine', 'html'); 12 | app.set('views', __dirname + '/views'); 13 | 14 | 15 | var users = [{ 16 | firstName: 'John', 17 | lastName: 'Doe', 18 | gender: 'male' 19 | }, { 20 | firstName: 'Jane', 21 | lastName: 'Doe', 22 | gender: 'female' 23 | }]; 24 | 25 | app.get('/users/:index', function(req, res) { 26 | res.render('index.ejs', { 27 | user: users[parseInt(req.params.index) || 0] 28 | }); 29 | }); 30 | 31 | app.listen(7777); 32 | -------------------------------------------------------------------------------- /chapter04/different-engines/jade-server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , cons = require('consolidate') 3 | , app = express(); 4 | 5 | // assign the jade engine to .html files 6 | app.engine('html', cons.jade); 7 | 8 | // set .html as the default extension 9 | app.set('view engine', 'html'); 10 | app.set('views', __dirname + '/views'); 11 | 12 | var getSalutation = function() { 13 | return (this.gender === 'male') ? 'Hello sir' : 'Hello madam'; 14 | }; 15 | var getFullName = function() { 16 | return this.firstName + ' ' + this.lastName; 17 | }; 18 | 19 | var users = [{ 20 | firstName: 'John', 21 | lastName: 'Doe', 22 | gender: 'male' 23 | }, { 24 | firstName: 'Jane', 25 | lastName: 'Doe', 26 | gender: 'female' 27 | }]; 28 | 29 | app.get('/users/:index', function(req, res){ 30 | res.render('index-jade.jade', { 31 | user: users[parseInt(req.params.index) || 0], 32 | salutation: getSalutation, 33 | name: getFullName 34 | }); 35 | }); 36 | 37 | app.listen(7777); 38 | -------------------------------------------------------------------------------- /chapter04/different-engines/json2html-server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var json2html = require('node-json2html').transform; 3 | var app = express(); 4 | 5 | var users = [{ 6 | firstName: 'John', 7 | lastName: 'Doe', 8 | gender: 'male' 9 | }, { 10 | firstName: 'Jane', 11 | lastName: 'Doe', 12 | gender: 'female' 13 | }]; 14 | 15 | app.get('/users/:index', function(req, res){ 16 | var data = { 17 | tag: "p", 18 | html: function() { 19 | var str = ''; 20 | 21 | if (this) { 22 | if (this.gender === 'male') { 23 | str += 'Hello sir '; 24 | } else { 25 | str += 'Hello madam '; 26 | } 27 | 28 | str += this.firstName + ' ' + this.lastName; 29 | return str; 30 | } 31 | } 32 | }; 33 | 34 | res.send(json2html(users[parseInt(req.params.index) || 0], data)); 35 | }); 36 | 37 | app.listen(7777); 38 | -------------------------------------------------------------------------------- /chapter04/different-engines/mustache-server.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | , cons = require('consolidate') 3 | , app = express(); 4 | 5 | // assign the mustache engine to .html files 6 | app.engine('html', cons.mustache); 7 | 8 | // set .html as the default extension 9 | app.set('view engine', 'html'); 10 | app.set('views', __dirname + '/views'); 11 | 12 | var users = [{ 13 | firstName: 'John', 14 | lastName: 'Doe', 15 | gender: 'male' 16 | }, { 17 | firstName: 'Jane', 18 | lastName: 'Doe', 19 | gender: 'female' 20 | }]; 21 | 22 | var getSalutation = function() { 23 | return (this.gender === 'male') ? 'Hello sir' : 'Hello madam'; 24 | }; 25 | var getFullName = function() { 26 | return this.firstName + ' ' + this.lastName; 27 | }; 28 | 29 | app.get('/users/:index', function(req, res){ 30 | res.render('index', { 31 | user: users[parseInt(req.params.index) || 0], 32 | salutation: getSalutation, 33 | name: getFullName 34 | }); 35 | }); 36 | 37 | app.listen(7777); 38 | -------------------------------------------------------------------------------- /chapter04/different-engines/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "different-engines", 3 | "version": "0.0.0", 4 | "description": "Using different template engines", 5 | "main": "ejs-server.js", 6 | "dependencies": { 7 | "jade": "~1.2.0", 8 | "express": "~3.4.8", 9 | "express-plates": "~0.1.4", 10 | "mustache": "~0.8.1", 11 | "json2html": "~0.0.8", 12 | "node-json2html": "~0.4.1", 13 | "plates": "~0.4.8", 14 | "ejs": "~0.8.5", 15 | "consolidate": "~0.10.0" 16 | }, 17 | "devDependencies": {}, 18 | "scripts": { 19 | "test": "echo \"Error: no test specified\" && exit 1" 20 | }, 21 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 22 | "license": "MIT" 23 | } 24 | -------------------------------------------------------------------------------- /chapter04/different-engines/views/index-jade.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | body 4 | - if (user) { 5 | - if (user.gender === 'male') { 6 | |Hello sir 7 | - } else { 8 | |Hello madam 9 | - } 10 | =user.firstName + ' ' + user.lastName 11 | - } 12 | -------------------------------------------------------------------------------- /chapter04/different-engines/views/index.ejs: -------------------------------------------------------------------------------- 1 | <% if (user) { %> 2 | <% if (user.gender === 'male') { %> 3 | Hello sir 4 | <% } else { %> 5 | Hello madam 6 | <% } %> 7 | <%-: (user.firstName + ' ' + user.lastName) | toUpper %> 8 | <% } %> 9 | -------------------------------------------------------------------------------- /chapter04/different-engines/views/index.html: -------------------------------------------------------------------------------- 1 | {{#user}} 2 | {{salutation}} {{name}} 3 | {{/user}} 4 | -------------------------------------------------------------------------------- /chapter04/integrating-template-engine/engine.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var _ = require('lodash'); 6 | 7 | exports.cache = {}; 8 | 9 | exports.getPartials = function(template, currentPath) { 10 | return template.replace(/<% include (.*) %>/g, function(arg1, filePath) { 11 | var fullPath = path.resolve(currentPath, filePath); 12 | var content = fs.readFileSync(fullPath, 'utf8'); 13 | 14 | if (/<% include (.*) %>/.test(content)) { 15 | return exports.getPartials(content, path.dirname(fullPath)); 16 | } 17 | 18 | return content; 19 | }); 20 | }; 21 | 22 | exports.compileTemplate = function(template, viewsPath) { 23 | template = exports.getPartials(template, viewsPath); 24 | 25 | return _.template(template); 26 | }; 27 | 28 | // Render a template with data 29 | exports.render = function(filePath, isCacheEnabled, data) { 30 | var layoutPath, compiledFn; 31 | 32 | if (!isCacheEnabled || (isCacheEnabled && !exports.cache[filePath])) { 33 | var content = fs.readFileSync(filePath, 'utf8').replace(/<% extends (.*) %>/, function(arg1, p) { 34 | layoutPath = path.resolve(data.settings.views, p); 35 | return ''; 36 | }); 37 | 38 | if (layoutPath) { 39 | content = fs.readFileSync(layoutPath, 'utf8').replace('<% body %>', content); 40 | } 41 | 42 | // compile template 43 | content = exports.compileTemplate(content, data.settings.views); 44 | compiledFn = content; 45 | 46 | // cache the compiled template if caching enabled 47 | if (isCacheEnabled && !exports.cache[filePath]) { 48 | exports.cache[filePath] = content; 49 | } 50 | } else { 51 | // compiled function can be found in cache 52 | compiledFn = exports.cache[filePath]; 53 | } 54 | 55 | // evaluate the compiled function 56 | return compiledFn(data || {}); 57 | }; 58 | 59 | exports.renderFile = function(filePath, data, callback) { 60 | var isCacheEnabled = !!data.settings['view cache']; 61 | 62 | try { 63 | var tmpl = exports.render(filePath, isCacheEnabled, data); 64 | return setImmediate(callback.bind(null, null, tmpl)); 65 | } 66 | catch(err) { 67 | return setImmediate(callback.bind(null, err)); 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /chapter04/integrating-template-engine/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "integrating-template-engine", 3 | "version": "0.0.0", 4 | "description": "Integrating a custom template engine with Express", 5 | "main": "server.js", 6 | "dependencies": { 7 | "watch": "~0.9.0", 8 | "debug": "~0.7.4", 9 | "lodash": "~2.4.1", 10 | "express": "~3.4.8" 11 | }, 12 | "devDependencies": {}, 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1", 15 | "start": "node server.js" 16 | }, 17 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /chapter04/integrating-template-engine/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | var tmpl = require('./engine'); 7 | 8 | app.locals.APP_NAME = 'Sample Express App'; 9 | 10 | app.set('views', __dirname + '/views'); 11 | app.set('view engine', 'html'); 12 | app.engine('html', tmpl.renderFile); 13 | 14 | app.get('/', function(req, res, next) { 15 | res.render('home', { pageTitle: 'home' }); 16 | }); 17 | 18 | app.get('/now', function(req, res, next) { 19 | res.render('now', { pageTitle: 'now' }); 20 | }); 21 | 22 | var watch = require('watch') 23 | watch.createMonitor(__dirname + '/views', function(monitor) { 24 | monitor.on("changed", function(file) { 25 | tmpl.cache = {}; 26 | }); 27 | }); 28 | 29 | app.listen(7777); 30 | -------------------------------------------------------------------------------- /chapter04/integrating-template-engine/simple.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | app.locals.APP_NAME = 'Sample Express App'; 7 | 8 | app.set('views', __dirname + '/views'); 9 | app.set('view engine', 'html'); 10 | app.engine('html', function() { console.log(arguments); }); 11 | 12 | app.get('/', function(req, res, next) { 13 | res.render('home', { 14 | firstLocal: 1, 15 | secondLocal: 2 16 | }); 17 | }); 18 | 19 | app.listen(7777); 20 | -------------------------------------------------------------------------------- /chapter04/integrating-template-engine/views/footer.html: -------------------------------------------------------------------------------- 1 |
2 | Copyright might be here. 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /chapter04/integrating-template-engine/views/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= APP_NAME %> 6 | 7 | 8 | 9 |

<%= pageTitle %>

10 | -------------------------------------------------------------------------------- /chapter04/integrating-template-engine/views/home.html: -------------------------------------------------------------------------------- 1 | <% extends layout.html %> 2 | 3 |

This is the homepage.

4 | -------------------------------------------------------------------------------- /chapter04/integrating-template-engine/views/layout.html: -------------------------------------------------------------------------------- 1 | <% include header.html %> 2 | <% body %> 3 | <% include footer.html %> 4 | -------------------------------------------------------------------------------- /chapter04/integrating-template-engine/views/now.html: -------------------------------------------------------------------------------- 1 | <% extends layout.html %> 2 | 3 |

Current time:

4 | 5 |

6 | <%= new Date() %> 7 |

8 | -------------------------------------------------------------------------------- /chapter04/layouts-with-jade/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layouts-with-jade", 3 | "version": "0.0.0", 4 | "description": "Using layouts with Jade", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~3.4.8", 8 | "jade": "~1.3.0" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "node server.js" 14 | }, 15 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /chapter04/layouts-with-jade/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var jade = require('jade'); 3 | var app = express(); 4 | 5 | app.set('views', __dirname + '/views'); 6 | 7 | app.get('/', function(req, res, next) { 8 | res.render('index.jade'); 9 | }); 10 | 11 | app.listen(7777); 12 | -------------------------------------------------------------------------------- /chapter04/layouts-with-jade/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block head 4 | title App homepage 5 | 6 | block content 7 | h1 Hello && welcome to the homepage! 8 | -------------------------------------------------------------------------------- /chapter04/layouts-with-jade/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | block head 5 | body 6 | #container 7 | block content 8 | -------------------------------------------------------------------------------- /chapter04/updating-templates-production/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "updating-templates-production", 3 | "version": "0.0.0", 4 | "description": "How to update cached templates", 5 | "main": "server.js", 6 | "dependencies": { 7 | "swig": "~1.3.2", 8 | "watch": "~0.9.0", 9 | "jade": "~1.1.5", 10 | "express-hbs": "~0.7.9", 11 | "consolidate": "~0.10.0", 12 | "ejs": "~0.8.5", 13 | "express": "~3.4.8" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1", 18 | "start": "node server.js" 19 | }, 20 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /chapter04/updating-templates-production/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | var ejs = require('ejs'); 5 | var jade = require('jade'); 6 | var hbs = require('express-hbs'); 7 | var swig = require('swig'); 8 | 9 | app.set('views', __dirname + '/views'); 10 | 11 | app.engine('hbs', hbs.express3({ 12 | partialsDir: [__dirname + '/views'] 13 | })); 14 | 15 | app.engine('swig', swig.renderFile); 16 | 17 | app.get('/', function(req, res, next) { 18 | res.render('example.ejs'); 19 | }); 20 | 21 | app.get('/jade', function(req, res, next) { 22 | res.render('example.jade'); 23 | }); 24 | 25 | app.get('/hbs', function(req, res, next) { 26 | res.render('example.hbs'); 27 | }); 28 | 29 | app.get('/swig', function(req, res, next) { 30 | res.render('example.swig'); 31 | }); 32 | 33 | app.get('/clean-cache', function(req, res, next) { 34 | swig.invalidateCache(); 35 | ejs.clearCache(); 36 | jade.cache = {}; 37 | hbs.cache = {}; 38 | 39 | res.end('Cache cleared'); 40 | }); 41 | 42 | /* 43 | var watch = require('watch') 44 | watch.createMonitor(__dirname + '/views', function(monitor) { 45 | monitor.on("changed", function(file) { 46 | swig.invalidateCache(); 47 | ejs.clearCache(); 48 | jade.cache = {}; 49 | hbs.cache = {}; 50 | }); 51 | }); 52 | */ 53 | 54 | app.listen(7777); 55 | -------------------------------------------------------------------------------- /chapter04/updating-templates-production/views/example.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EJS 6 | 7 | 8 |

EJS template engine

9 | 10 | 11 | -------------------------------------------------------------------------------- /chapter04/updating-templates-production/views/example.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HBS 6 | 7 | 8 |

HBS template engine

9 | 10 | 11 | -------------------------------------------------------------------------------- /chapter04/updating-templates-production/views/example.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title Jade 5 | body 6 | h1 Jade template engine 7 | -------------------------------------------------------------------------------- /chapter04/updating-templates-production/views/example.swig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Swig 6 | 7 | 8 |

Swig template engine

9 | 10 | 11 | -------------------------------------------------------------------------------- /chapter04/view-caching/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "view-caching", 3 | "version": "0.0.0", 4 | "description": "View caching example", 5 | "main": "server.js", 6 | "dependencies": { 7 | "jade": "~1.3.0", 8 | "express": "~3.4.8" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "node server.js" 14 | }, 15 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /chapter04/view-caching/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var jade = require('jade'); 3 | var app = express(); 4 | 5 | app.engine('jade', jade.renderFile); 6 | app.set('views', __dirname + '/views'); 7 | 8 | var users = require('./users.json'); 9 | 10 | app.get('/users', function(req, res, next) { 11 | res.render('users.jade', { users: users }); 12 | }); 13 | 14 | app.listen(7777); 15 | -------------------------------------------------------------------------------- /chapter04/view-caching/views/users.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title Sample page that lists users details 5 | body 6 | h1 Sample page that lists users details 7 | ul 8 | each user in users 9 | li 10 | p(class="user-item") 11 | strong Name: 12 | | #{user.name} 13 | p(class="user-item") 14 | strong Age: 15 | | #{user.age} 16 | p(class="user-item") 17 | strong Gender: 18 | | #{user.gender} 19 | p(class="user-item") 20 | strong Friends: 21 | ul 22 | each friend in user.friends 23 | li=friend.name 24 | -------------------------------------------------------------------------------- /chapter04/view-helpers-examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "view-helpers-examples", 3 | "version": "0.0.0", 4 | "description": "View helpers example", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~3.4.8", 8 | "ejs": "~0.8.5", 9 | "jade": "~1.3.0" 10 | }, 11 | "devDependencies": {}, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1", 14 | "start": "node server.js" 15 | }, 16 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /chapter04/view-helpers-examples/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var ejs = require('ejs'); 3 | var app = express(); 4 | 5 | // assign the ejs engine to .html files 6 | app.engine('html', ejs.renderFile); 7 | 8 | // set .html as the default extension 9 | app.set('view engine', 'html'); 10 | app.set('views', __dirname + '/views'); 11 | 12 | app.get('/', function(req, res, next) { 13 | res.render('index'); 14 | }); 15 | 16 | app.get('/search', function(req, res, next) { 17 | res.render('search'); 18 | }); 19 | 20 | app.listen(7777); 21 | -------------------------------------------------------------------------------- /chapter04/view-helpers-examples/views/footer.html: -------------------------------------------------------------------------------- 1 |
2 | Mastering Express 3 |
4 | 5 | 6 | -------------------------------------------------------------------------------- /chapter04/view-helpers-examples/views/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello app 6 | 7 | 8 |
9 |

Hello app!

10 |
11 | -------------------------------------------------------------------------------- /chapter04/view-helpers-examples/views/index.html: -------------------------------------------------------------------------------- 1 | <%- include header.html %> 2 | Welcome from the main page! 3 | <%- include footer.html %> 4 | -------------------------------------------------------------------------------- /chapter04/view-helpers-examples/views/search.html: -------------------------------------------------------------------------------- 1 | <%- include header.html %> 2 | 3 |
4 | 5 | 6 |
7 | 8 | <%- include footer.html %> 9 | -------------------------------------------------------------------------------- /chapter05/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MovieApp", 3 | "version": "0.0.1", 4 | "description": "Search for your favourite movies", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.6.1", 8 | "moviedb": "~0.1.1", 9 | "ejs": "~0.8.5", 10 | "body-parser": "~1.0.0" 11 | }, 12 | "devDependencies": {}, 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1", 15 | "start": "node server.js" 16 | }, 17 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /chapter05/app/public/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | min-height: 90%; 6 | width: 960px; 7 | margin: 10px auto; 8 | position: relative; 9 | } 10 | img { 11 | border: 1px solid #CCC; 12 | padding: 5px; 13 | margin-right: 5px; 14 | vertical-align: top; 15 | } 16 | ul { 17 | list-style-type: none; 18 | margin: 0; 19 | padding-left: 25px; 20 | } 21 | ul li { 22 | padding: 5px 0; 23 | line-height: 22px; 24 | font-size: 17px; 25 | } 26 | .movie .poster, .movie .content { 27 | float: left; 28 | } 29 | .movie .content { 30 | width: 500px; 31 | } 32 | .movie .attribute, .movie .info { 33 | display: block; 34 | padding: 5px; 35 | } 36 | .movie .attribute { 37 | background: #F2F2F2; 38 | font-weight: bold; 39 | } 40 | .person { 41 | border-bottom: 1px dashed #CCC; 42 | padding: 0 0 15px; 43 | } 44 | .cast .info { 45 | max-height: 350px; 46 | overflow: scroll; 47 | } 48 | .trailer .attribute { 49 | color: #00F; 50 | text-decoration: underline; 51 | } 52 | .trailer .info { 53 | display: none; 54 | } 55 | .trailer:hover { 56 | cursor: pointer; 57 | } 58 | #main-search input { 59 | font-size: 30px; 60 | } 61 | #main-search input[type=submit] { 62 | padding: 3px 15px; background:#ccc; border:0 none; 63 | cursor: pointer; 64 | -webkit-border-radius: 5px; 65 | border-radius: 5px; 66 | margin-bottom: 50px; 67 | } 68 | .links { 69 | position: absolute; 70 | right: 105px; 71 | top: 10px; 72 | } 73 | .source { 74 | border-top: 1px solid #CCC; 75 | border-radius: 3px; 76 | display: inline-block; 77 | padding: 5px; 78 | } 79 | -------------------------------------------------------------------------------- /chapter05/app/routes/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var STATUS_CODES = require('http').STATUS_CODES; 4 | 5 | // 500 - Internal Server Error 6 | exports.handleInternalError = function(err, req, res, next) { 7 | var html = ''; 8 | 9 | if (err.code === 404 || /not found/.test(err.message)) { 10 | return exports.handleNotFound(req, res, next); 11 | } else if (err.code && STATUS_CODES[err.code]) { 12 | html = '

' + err.code + ' - ' + STATUS_CODES[err.code] + '

'; 13 | html += '

' + err.message + '

'; 14 | 15 | res.send(err.code, html); 16 | } else { 17 | console.error(err.stack); 18 | res.send(500, '

500 - Internal Server Error

'); 19 | } 20 | }; 21 | 22 | exports.handleNotFound = function(req, res, next) { 23 | res.send(404, '

404 - Page Not Found

'); 24 | }; 25 | -------------------------------------------------------------------------------- /chapter05/app/routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.movies = require('./movies'); 4 | exports.errors = require('./errors'); 5 | -------------------------------------------------------------------------------- /chapter05/app/routes/movies.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.search = function(req, res, next) { 4 | res.render('search', { 5 | pageTitle: 'Search for movies' 6 | }); 7 | }; 8 | 9 | exports.index = function(req, res, next) { 10 | if (!req.query.title) { 11 | var err = new Error('Missing search param'); 12 | err.code = 422; 13 | return next(err); 14 | } 15 | 16 | req.movie.search(req.query.title, function(err, movies) { 17 | if (err) { return next(err); } 18 | 19 | res.render('movies', { 20 | pageTitle: 'Search results for ' + req.query.title, 21 | movies: movies 22 | }); 23 | }); 24 | }; 25 | 26 | exports.show = function(req, res, next) { 27 | if (!/^\d+$/.test(req.params.id)) { 28 | var err = new Error('Bad movie id'); 29 | err.code = 422; 30 | return next(err); 31 | } 32 | 33 | req.movie.getMovie(req.params.id, function(err, movie) { 34 | if (err) { return next(err); } 35 | 36 | res.render('movie', { 37 | pageTitle: movie.title, 38 | movie: movie 39 | }); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /chapter05/app/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bodyParser = require('body-parser'); 4 | var express = require('express'); 5 | var app = express(); 6 | 7 | app.set('view engine', 'html'); 8 | app.set('views', __dirname + '/views'); 9 | app.engine('html', require('ejs').renderFile); 10 | 11 | app.use(bodyParser()); 12 | app.use(express.static(__dirname + '/public')); 13 | 14 | var Movie = require('./models/movie'); 15 | var movie = new Movie(process.env.API_KEY); 16 | 17 | app.use(function(req, res, next) { 18 | req.movie = movie; 19 | next(); 20 | }); 21 | 22 | var routes = require('./routes'); 23 | app.get('/', routes.movies.search); 24 | app.get('/movies', routes.movies.index); 25 | app.get('/movies/:id', routes.movies.show); 26 | app.all('*', routes.errors.handleNotFound); 27 | 28 | app.use(routes.errors.handleInternalError); 29 | 30 | app.listen(7777); 31 | -------------------------------------------------------------------------------- /chapter05/app/views/layout/footer.html: -------------------------------------------------------------------------------- 1 |
2 | TMDb (http://www.themoviedb.org/) is the source of the data. 3 |
4 | 5 | 6 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /chapter05/app/views/layout/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= pageTitle %> 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /chapter05/app/views/movie.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 |

<%= movie.title %>

4 | 5 | 9 | 10 |
11 |
12 | poster 13 |
14 | 15 |
16 |
    17 |
  • 18 | Release date 19 | 20 | <%= movie.release_date %> 21 | 22 |
  • 23 | 24 |
  • 25 | Rating 26 | 27 | <%= movie.vote_average %> 28 | 29 |
  • 30 | 31 |
  • 32 | Genres 33 | 34 | <%=: movie.genres | map:'name' | join %> 35 | 36 |
  • 37 | 38 | <% if (movie.trailers && movie.trailers.youtube && movie.trailers.youtube[0]) { %> 39 |
  • 40 | Watch trailer 41 | 42 | 45 | 46 |
  • 47 | <% } else { %> 48 |
  • 49 | 50 | No trailer 51 | 52 |
  • 53 | <% } %> 54 | 55 |
  • 56 | Overview 57 | 58 | <%= movie.overview %> 59 | 60 |
  • 61 | 62 |
  • 63 | Cast 64 | 65 | <% movie.cast.forEach(function(person) { %> 66 |

    67 | <% if (person.details.profile_path) { %> 68 | 69 | <% } %> 70 | 71 | <% if (person.details.homepage) { %> 72 | <%= person.name %> 73 | (<%= person.character %>) 74 | <% } else { %> 75 | <%= person.name %> (<%= person.character %>) 76 | <% } %> 77 |

    78 | <% }); %> 79 |
    80 |
  • 81 | 82 |
83 |
84 |
85 | 86 | <%- include layout/footer.html %> 87 | -------------------------------------------------------------------------------- /chapter05/app/views/movies.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 | <% if (movies.results) { %> 4 | 5 |

<%= pageTitle %>

6 | 7 | 10 | 11 |
12 | <% movies.results.forEach(function(movie) { %> 13 |

14 | <% if (movie.poster_path) { %> 15 | 16 | <% } %> 17 | 18 | <%= movie.title %> (<%= movie.release_date %>) 19 |

20 | <% }) %> 21 |
22 | 23 | <% } else { %> 24 | 25 |

No results found

26 | 27 | <% } %> 28 | 29 | <%- include layout/footer.html %> 30 | -------------------------------------------------------------------------------- /chapter05/app/views/search.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 |

Search for movies using the form below

4 | 5 | 9 | 10 | <%- include layout/footer.html %> 11 | -------------------------------------------------------------------------------- /chapter05/dry_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MovieApp", 3 | "version": "0.0.1", 4 | "description": "Search for your favourite movies", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.0.0-rc3", 8 | "moviedb": "~0.1.1", 9 | "ejs": "~0.8.5", 10 | "body-parser": "~1.0.0", 11 | "errto": "~0.2.1", 12 | "after": "~0.8.1", 13 | "async-series": "0.0.1", 14 | "xtend": "~2.1.2", 15 | "custom-err": "0.0.2" 16 | }, 17 | "devDependencies": {}, 18 | "scripts": { 19 | "test": "echo \"Error: no test specified\" && exit 1", 20 | "start": "node server.js" 21 | }, 22 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /chapter05/dry_app/public/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | body { 5 | min-height: 90%; 6 | width: 960px; 7 | margin: 10px auto; 8 | position: relative; 9 | } 10 | img { 11 | border: 1px solid #CCC; 12 | padding: 5px; 13 | margin-right: 5px; 14 | vertical-align: top; 15 | } 16 | ul { 17 | list-style-type: none; 18 | margin: 0; 19 | padding-left: 25px; 20 | } 21 | ul li { 22 | padding: 5px 0; 23 | line-height: 22px; 24 | font-size: 17px; 25 | } 26 | .movie .poster, .movie .content { 27 | float: left; 28 | } 29 | .movie .content { 30 | width: 500px; 31 | } 32 | .movie .attribute, .movie .info { 33 | display: block; 34 | padding: 5px; 35 | } 36 | .movie .attribute { 37 | background: #F2F2F2; 38 | font-weight: bold; 39 | } 40 | .person { 41 | border-bottom: 1px dashed #CCC; 42 | padding: 0 0 15px; 43 | } 44 | .cast .info { 45 | max-height: 350px; 46 | overflow: scroll; 47 | } 48 | .trailer .attribute { 49 | color: #00F; 50 | text-decoration: underline; 51 | } 52 | .trailer .info { 53 | display: none; 54 | } 55 | .trailer:hover { 56 | cursor: pointer; 57 | } 58 | #main-search input { 59 | font-size: 30px; 60 | } 61 | #main-search input[type=submit] { 62 | padding: 3px 15px; background:#ccc; border:0 none; 63 | cursor: pointer; 64 | -webkit-border-radius: 5px; 65 | border-radius: 5px; 66 | margin-bottom: 50px; 67 | } 68 | .links { 69 | position: absolute; 70 | right: 105px; 71 | top: 10px; 72 | } 73 | .source { 74 | border-top: 1px solid #CCC; 75 | border-radius: 3px; 76 | display: inline-block; 77 | padding: 5px; 78 | } 79 | -------------------------------------------------------------------------------- /chapter05/dry_app/routes/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var STATUS_CODES = require('http').STATUS_CODES; 4 | 5 | // 500 - Internal Server Error 6 | exports.handleInternalError = function(err, req, res, next) { 7 | var html = ''; 8 | 9 | if (err.code === 404 || /not found/.test(err.message)) { 10 | return exports.handleNotFound(req, res, next); 11 | } else if (err.code && STATUS_CODES[err.code]) { 12 | html = '

' + err.code + ' - ' + STATUS_CODES[err.code] + '

'; 13 | html += '

' + err.message + '

'; 14 | 15 | res.send(err.code, html); 16 | } else { 17 | console.error(err.stack); 18 | res.send(500, '

500 - Internal Server Error

'); 19 | } 20 | }; 21 | 22 | exports.handleNotFound = function(req, res, next) { 23 | res.send(404, '

404 - Page Not Found

'); 24 | }; 25 | -------------------------------------------------------------------------------- /chapter05/dry_app/routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.movies = require('./movies'); 4 | exports.errors = require('./errors'); 5 | -------------------------------------------------------------------------------- /chapter05/dry_app/routes/movies.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var errTo = require('errto'); 4 | var Err = require('custom-err'); 5 | 6 | exports.search = function(req, res, next) { 7 | res.render('search', { 8 | pageTitle: 'Search for movies' 9 | }); 10 | }; 11 | 12 | exports.index = function(req, res, next) { 13 | if (!req.query.title) { 14 | return next(Err('Missing search param', { code: 422 })); 15 | } 16 | 17 | req.movie.search(req.query.title, errTo(next, function(movies) { 18 | res.render('movies', { 19 | pageTitle: 'Search results for ' + req.query.title, 20 | movies: movies 21 | }); 22 | })); 23 | }; 24 | 25 | exports.show = function(req, res, next) { 26 | if (!/^\d+$/.test(req.params.id)) { 27 | return next(Err('Bad movie id', { code: 422 })); 28 | } 29 | 30 | req.movie.getMovie(req.params.id, errTo(next, function(movie) { 31 | res.render('movie', { 32 | pageTitle: movie.title, 33 | movie: movie 34 | }); 35 | })); 36 | }; 37 | -------------------------------------------------------------------------------- /chapter05/dry_app/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bodyParser = require('body-parser'); 4 | var express = require('express'); 5 | var app = express(); 6 | 7 | app.set('view engine', 'html'); 8 | app.set('views', __dirname + '/views'); 9 | app.engine('html', require('ejs').renderFile); 10 | 11 | app.use(bodyParser()); 12 | app.use(express.static(__dirname + '/public')); 13 | 14 | var Movie = require('./models/movie'); 15 | var movie = new Movie(process.env.API_KEY); 16 | 17 | app.use(function(req, res, next) { 18 | req.movie = movie; 19 | next(); 20 | }); 21 | 22 | var routes = require('./routes'); 23 | app.get('/', routes.movies.search); 24 | app.get('/movies', routes.movies.index); 25 | app.get('/movies/:id', routes.movies.show); 26 | app.all('*', routes.errors.handleNotFound); 27 | 28 | app.use(routes.errors.handleInternalError); 29 | 30 | app.listen(7777); 31 | -------------------------------------------------------------------------------- /chapter05/dry_app/views/layout/footer.html: -------------------------------------------------------------------------------- 1 |
2 | TMDb (http://www.themoviedb.org/) is the source of the data. 3 |
4 | 5 | 6 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /chapter05/dry_app/views/layout/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= pageTitle %> 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /chapter05/dry_app/views/movie.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 |

<%= movie.title %>

4 | 5 | 9 | 10 |
11 |
12 | poster 13 |
14 | 15 |
16 |
    17 |
  • 18 | Release date 19 | 20 | <%= movie.release_date %> 21 | 22 |
  • 23 | 24 |
  • 25 | Rating 26 | 27 | <%= movie.vote_average %> 28 | 29 |
  • 30 | 31 |
  • 32 | Genres 33 | 34 | <%=: movie.genres | map:'name' | join %> 35 | 36 |
  • 37 | 38 | <% if (movie.trailers && movie.trailers.youtube && movie.trailers.youtube[0]) { %> 39 |
  • 40 | Watch trailer 41 | 42 | 45 | 46 |
  • 47 | <% } else { %> 48 |
  • 49 | 50 | No trailer 51 | 52 |
  • 53 | <% } %> 54 | 55 |
  • 56 | Overview 57 | 58 | <%= movie.overview %> 59 | 60 |
  • 61 | 62 |
  • 63 | Cast 64 | 65 | <% movie.cast.forEach(function(person) { %> 66 |

    67 | <% if (person.details.profile_path) { %> 68 | 69 | <% } %> 70 | 71 | <% if (person.details.homepage) { %> 72 | <%= person.name %> 73 | (<%= person.character %>) 74 | <% } else { %> 75 | <%= person.name %> (<%= person.character %>) 76 | <% } %> 77 |

    78 | <% }); %> 79 |
    80 |
  • 81 | 82 |
83 |
84 |
85 | 86 | <%- include layout/footer.html %> 87 | -------------------------------------------------------------------------------- /chapter05/dry_app/views/movies.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 | <% if (movies.results) { %> 4 | 5 |

<%= pageTitle %>

6 | 7 | 10 | 11 |
12 | <% movies.results.forEach(function(movie) { %> 13 |

14 | <% if (movie.poster_path) { %> 15 | 16 | <% } %> 17 | 18 | <%= movie.title %> (<%= movie.release_date %>) 19 |

20 | <% }) %> 21 |
22 | 23 | <% } else { %> 24 | 25 |

No results found

26 | 27 | <% } %> 28 | 29 | <%- include layout/footer.html %> 30 | -------------------------------------------------------------------------------- /chapter05/dry_app/views/search.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 |

Search for movies using the form below

4 | 5 | 9 | 10 | <%- include layout/footer.html %> 11 | -------------------------------------------------------------------------------- /chapter06/WallApp/lib/die.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var niceErr = require('nice-error'); 4 | 5 | module.exports = function(err) { 6 | err.timestamp = Date.now(); 7 | console.error(niceErr(err)); 8 | process.exit(1); 9 | }; 10 | -------------------------------------------------------------------------------- /chapter06/WallApp/lib/errorHandler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var stackTrace = require('stack-trace'); 5 | var asyncEach = require('async-each'); 6 | var errTo = require('errto'); 7 | var niceErr = require('nice-error'); 8 | 9 | function handleErrors(err, req, res, next) { 10 | var stack = stackTrace.parse(err); 11 | 12 | console.error(niceErr(err)); 13 | 14 | asyncEach(stack, function getContentInfo(item, cb) { 15 | // exclude core node modules and node modules 16 | if (/\//.test(item.fileName) && !/node_modules/.test(item.fileName)) { 17 | fs.readFile(item.fileName, 'utf-8', errTo(cb, function(content) { 18 | var start = item.lineNumber - 5; 19 | if (start < 0) { start = 0; } 20 | var end = item.lineNumber + 4; 21 | var snippet = content.split('\n').slice(start, end); 22 | // decorate the error line 23 | snippet[snippet.length - 5] = '' + snippet[snippet.length - 5] + ''; 24 | item.content = snippet.join('\n'); 25 | 26 | cb(null, item); 27 | })); 28 | } else { 29 | cb(); 30 | } 31 | }, function(e, items) { 32 | items = items.filter(function(item) { return !!item; }); 33 | 34 | // if something bad happened while processing the stacktrace 35 | // make sure to return something useful 36 | if (e) { 37 | console.error(e); 38 | 39 | return res.send(err.stack); 40 | } 41 | 42 | var html = '

' + err.message + '

    '; 43 | 44 | items.forEach(function(item) { 45 | html += '
  • at ' + item.functionName || 'anonymous'; 46 | html += ' (' + item.fileName + ':' + item.lineNumber + ':' + item.columnNumber + ')'; 47 | html += '

    ' + item.content + '

    '; 48 | html += '

  • '; 49 | }); 50 | 51 | html += '
'; 52 | 53 | res.send(html); 54 | }); 55 | } 56 | 57 | module.exports = handleErrors; 58 | -------------------------------------------------------------------------------- /chapter06/WallApp/lib/primus.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Primus = require('primus'); 4 | 5 | module.exports = function startPrimus(server, opts) { 6 | opts = opts || {}; 7 | 8 | var primus = new Primus(server, { 9 | transformer: opts.transformer || 'websockets', 10 | parser: opts.parser || 'json', 11 | pathname: opts.pathname || '/primus' 12 | }); 13 | 14 | return function broadcast(msg) { 15 | primus.write(msg); 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /chapter06/WallApp/lib/validator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var validator = require('validator'); 4 | var extend = require('extend'); 5 | var memoize = require('memoizejs'); 6 | 7 | var customValidator = extend({}, validator); 8 | 9 | customValidator.validate = function(method) { 10 | if (!customValidator[method]) { 11 | throw new Error('validator method does not exist'); 12 | } 13 | 14 | // get an array of the arguments except the first one (the method name) 15 | var args = Array.prototype.slice.call(arguments, 1); 16 | 17 | return function(value) { 18 | return customValidator[method].apply(customValidator, Array.prototype.concat(value, args)); 19 | }; 20 | }; 21 | 22 | customValidator.validate = memoize(customValidator.validate); 23 | 24 | module.exports = customValidator; 25 | -------------------------------------------------------------------------------- /chapter06/WallApp/models/post.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | var validator = require('../lib/validator'); 6 | 7 | var Post = new Schema({ 8 | content: { 9 | type: String, 10 | required: true, 11 | validate: validator.validate('isLength', 2, 255) 12 | }, 13 | author: { 14 | type: Schema.Types.ObjectId, 15 | ref: 'User', 16 | required: true 17 | }, 18 | createdAt: { 19 | type: Number, 20 | default: Date.now 21 | } 22 | }); 23 | 24 | Post.statics.getWith = function(opts, callback) { 25 | if (typeof opts === 'function') { 26 | callback = opts; 27 | opts = {}; 28 | } 29 | 30 | opts.limit = opts.limit || 20; 31 | 32 | var query = this.find(); 33 | 34 | if (opts.older) { 35 | query = query.where('createdAt').lte(opts.older); 36 | } else if (opts.newer) { 37 | query = query.where('createdAt').gte(opts.newer); 38 | } 39 | 40 | query.limit(opts.limit).populate({ 41 | path: 'author', 42 | select: 'username email' 43 | }) 44 | .sort('-createdAt') 45 | .exec(callback); 46 | }; 47 | 48 | module.exports = mongoose.model('Post', Post); 49 | -------------------------------------------------------------------------------- /chapter06/WallApp/models/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | var passportLocalMongoose = require('passport-local-mongoose'); 6 | var validator = require('../lib/validator'); 7 | 8 | var User = new Schema({ 9 | username: { 10 | type: String, 11 | required: true, 12 | unique: true, 13 | validate: [{ 14 | validator: validator.validate('isAlphanumeric'), 15 | msg: 'username must be alphanumeric' 16 | }, { 17 | validator: validator.validate('isLength', 4, 255), 18 | msg: 'username must have 4-255 chars' 19 | }] 20 | }, 21 | email: { 22 | type: String, 23 | required: true, 24 | unique: true, 25 | validate: validator.validate('isEmail') 26 | } 27 | }); 28 | 29 | User.plugin(passportLocalMongoose); 30 | 31 | module.exports = mongoose.model('User', User); 32 | -------------------------------------------------------------------------------- /chapter06/WallApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wall", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "ejs": "~1.0.0", 9 | "body-parser": "~1.0.1", 10 | "express": "~4.0.0-rc4", 11 | "passport-local-mongoose": "~0.3.0", 12 | "passport-local": "~1.0.0", 13 | "passport": "~0.2.0", 14 | "mongoose": "~3.8.8", 15 | "morgan": "~1.0.0", 16 | "method-override": "~1.0.0", 17 | "serve-static": "~1.0.3", 18 | "cookie-session": "~1.0.1", 19 | "extend": "~1.2.1", 20 | "validator": "~3.5.1", 21 | "memoizejs": "~0.1.0", 22 | "connect-flash": "~0.1.1", 23 | "errto": "~0.2.1", 24 | "nodejs-gravatar": "~1.0.2", 25 | "moment": "~2.5.1", 26 | "primus": "~2.1.2", 27 | "ws": "~0.4.31", 28 | "stack-trace": "0.0.9", 29 | "async-each": "~0.1.4", 30 | "nice-error": "0.0.1" 31 | }, 32 | "scripts": { 33 | "test": "echo \"Error: no test specified\" && exit 1", 34 | "start": "node server.js" 35 | }, 36 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 37 | "license": "MIT" 38 | } 39 | -------------------------------------------------------------------------------- /chapter06/WallApp/public/css/core.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding-top: 25px; 3 | } 4 | .notice { 5 | padding: 10px; 6 | margin: 10px 0; 7 | } 8 | .post { 9 | border: 1px solid #CCC; 10 | border-radius: 2px; 11 | box-shadow: 1px 1px 3px #DEDEDE; 12 | margin: 0 0 10px 0; 13 | padding: 5px; 14 | overflow: hidden; 15 | } 16 | .post .thumbnail { 17 | float: left; 18 | width: 65px; 19 | height: 65px; 20 | border: none; 21 | margin-bottom: 0; 22 | } 23 | .post .content { 24 | padding-left: 65px; 25 | } 26 | .post .body { 27 | vertical-align: middle; 28 | font-size: 17px; 29 | } 30 | .post .meta { 31 | color: #999; 32 | } 33 | .counter { 34 | color: #999; 35 | margin: 10px 0; 36 | } 37 | #posts .loading { 38 | background: url('/img/ajax-loader.gif') no-repeat 10px center #DEDEDE; 39 | display: inline-block; 40 | border-radius: 5px; 41 | padding: 10px 30px; 42 | margin-bottom: 10px; 43 | } 44 | #head .col-md-4 { 45 | padding-left: 0; 46 | padding-right: 0; 47 | } 48 | #post-updates { 49 | margin: 0; 50 | display: none; 51 | line-height: 32px; 52 | } 53 | #post-updates, #post-updates > * { 54 | color: cadetblue; 55 | } 56 | -------------------------------------------------------------------------------- /chapter06/WallApp/public/img/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessioalex/mastering_express_code/a8202b6dcffd33e6716722f83d63bacbbc638454/chapter06/WallApp/public/img/ajax-loader.gif -------------------------------------------------------------------------------- /chapter06/WallApp/routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.session = require('./session'); 4 | exports.users = require('./users'); 5 | exports.posts = require('./posts'); 6 | -------------------------------------------------------------------------------- /chapter06/WallApp/routes/posts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var errTo = require('errto'); 4 | var niceErr = require('nice-error'); 5 | 6 | exports.index = function(req, res, next) { 7 | var opts = {}; 8 | var tpl = 'index'; 9 | 10 | if (req.query.partial) { 11 | opts.older = req.query.older; 12 | tpl = '_posts'; 13 | } 14 | 15 | req.Post.getWith(opts, errTo(next, function(posts) { 16 | res.render(tpl, { 17 | posts: posts, 18 | user: req.user, 19 | successMsg: req.flash('success')[0], 20 | errorMsg: req.flash('error')[0] 21 | }); 22 | })); 23 | }; 24 | 25 | exports.create = function(req, res, next) { 26 | var post = new req.Post({ 27 | content: req.body.content, 28 | author: req.user._id 29 | }); 30 | 31 | post.save(function(err) { 32 | if (err) { 33 | if (err.name === 'ValidationError') { 34 | req.flash('error', 'Could not publish the post, please make sure it has a length of 2-255 chars'); 35 | } else { 36 | return next(err); 37 | } 38 | } else { 39 | req.flash('success', 'Successfully published the post'); 40 | // creating another var so we can populate the author details 41 | var _post = { 42 | _id: post._id, 43 | content: post.content, 44 | createdAt: post.createdAt, 45 | author: { 46 | username: req.user.username, 47 | email: req.user.email 48 | } 49 | }; 50 | 51 | res.render('_posts', { 52 | posts: [_post] 53 | }, function(err, content) { 54 | if (!err) { 55 | // Note: this works fine for a single process, but when having 56 | // more processes a message bus (like Redis for example) is needed 57 | // (to listen for new events emitted by different processes and 58 | // broadcast them to the clients) 59 | return req.broadcast(content); 60 | } 61 | console.error(niceErr(err)); 62 | }); 63 | } 64 | 65 | res.redirect('/'); 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /chapter06/WallApp/routes/session.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.new = function(req, res) { 4 | res.render('login', { user : req.user }); 5 | }; 6 | 7 | exports.create = function(req, res) { 8 | res.redirect('/'); 9 | }; 10 | 11 | exports.destroy = function(req, res) { 12 | req.logout(); 13 | res.redirect('/'); 14 | }; 15 | -------------------------------------------------------------------------------- /chapter06/WallApp/routes/users.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var passport = require('passport'); 4 | var User = require('../models/user'); 5 | 6 | exports.new = function(req, res, next) { 7 | res.render('register'); 8 | }; 9 | 10 | exports.create = function(req, res, next) { 11 | var newUser = new User({ 12 | username : req.body.username, 13 | email : req.body.email 14 | }); 15 | 16 | User.register(newUser, req.body.password, function(err, user) { 17 | var errMessage; 18 | 19 | if (err) { 20 | // failed validation || duplicate key shouldn't result in a 500 error page 21 | // we should display the form with an error message instead 22 | if (err.name === 'BadRequestError' || err.name === 'ValidationError' || err.name === 'MongoError') { 23 | // showing specific messages for some situations 24 | if (err.name === 'MongoError' && err.code === 11000) { 25 | errMessage = 'username/email already exists'; 26 | } else if (err.name === 'ValidationError') { 27 | errMessage = 'Validation failed for the following fields: ' + Object.keys(err.errors).join(', '); 28 | } 29 | 30 | return res.render("register", { 31 | error: errMessage || err.message 32 | }); 33 | } else { 34 | return next(err); 35 | } 36 | } 37 | 38 | // auto-login the newly created user 39 | passport.authenticate('local')(req, res, function() { 40 | res.redirect('/'); 41 | }); 42 | }); 43 | }; 44 | 45 | exports.ensureAuthenticated = function(req, res, next) { 46 | if (req.isAuthenticated()) { return next(); } 47 | 48 | res.redirect('/login') 49 | } 50 | -------------------------------------------------------------------------------- /chapter06/WallApp/views/_posts.html: -------------------------------------------------------------------------------- 1 | <% posts.forEach(function(post) { %> 2 |
> 3 | 4 |
5 |
6 | 7 | @<%- post.author.username %> 8 | 9 | 10 | <%- moment(post.createdAt).fromNow() %> 11 | 12 |
13 | 14 |
15 | <%= post.content %> 16 |
17 | 18 |
19 | 20 |
21 |
22 | <% }) %> 23 | -------------------------------------------------------------------------------- /chapter06/WallApp/views/index.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 | 32 | 33 |
34 |
35 | <% if (successMsg) { %> 36 |

37 | <%= successMsg %> 38 |

39 | <% } else if (errorMsg) { %> 40 |

41 | <%= errorMsg %> 42 |

43 | <% } %> 44 | 45 | <% if (user) { %> 46 |
47 |
48 |

New post

49 | 50 |
51 | 53 | 54 |

55 | 255 chars remaining 56 |

57 |
58 | 59 |
60 | 61 |
62 |
63 |
64 | <% } %> 65 | 66 |
67 | 68 |
69 |

Posts

70 | 71 | <% if (!posts.length) { %> 72 |

Be the first to write a post!

73 | <% } else { %> 74 | <% include _posts.html %> 75 | <% } %> 76 |
77 |
78 | 79 | <%- include layout/footer.html %> 80 | -------------------------------------------------------------------------------- /chapter06/WallApp/views/layout/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /chapter06/WallApp/views/layout/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%- locals.title || 'WallApp' %> 5 | 6 | 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /chapter06/WallApp/views/login.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 |
4 |
5 |

Login to proceed

6 |
7 | 8 |
9 |
10 | 11 | 13 |
14 |
15 | 16 | 18 |
19 | 20 |
21 |   22 | Cancel 23 |
24 |
25 |
26 |
27 | 28 | <%- include layout/footer.html %> 29 | -------------------------------------------------------------------------------- /chapter06/WallApp/views/register.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 |
4 |
5 |

Create user

6 |
7 | 8 | <% if (locals.error) { %> 9 |

<%= locals.error %>

10 | <% } %> 11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 | 23 |
24 |   25 | Cancel 26 |
27 |
28 |
29 |
30 | 31 | <%- include layout/footer.html %> 32 | -------------------------------------------------------------------------------- /chapter06/custom-error-handler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-error-handler", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.0.0-rc4", 8 | "stack-trace": "0.0.9", 9 | "async-each": "~0.1.4", 10 | "errto": "~0.2.1" 11 | }, 12 | "devDependencies": { 13 | }, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1", 16 | "start": "node server.js" 17 | }, 18 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 19 | "license": "MIT" 20 | } 21 | -------------------------------------------------------------------------------- /chapter06/custom-error-handler/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var stackTrace = require('stack-trace'); 5 | var asyncEach = require('async-each'); 6 | var errTo = require('errto'); 7 | var express = require('express'); 8 | var app = express(); 9 | 10 | function getSampleError() { 11 | return new Error('sample error'); 12 | } 13 | 14 | app.use(function(req, res, next) { 15 | if (req.url === '/favicon.ico') { return res.end(); } 16 | next(getSampleError()); 17 | }); 18 | 19 | app.use(function(err, req, res, next) { 20 | var stack = stackTrace.parse(err); 21 | 22 | asyncEach(stack, function getContentInfo(item, cb) { 23 | // exclude core node modules and node modules 24 | if (/\//.test(item.fileName) && !/node_modules/.test(item.fileName)) { 25 | fs.readFile(item.fileName, 'utf-8', errTo(cb, function(content) { 26 | var start = item.lineNumber - 5; 27 | if (start < 0) { start = 0; } 28 | var end = item.lineNumber + 4; 29 | var snippet = content.split('\n').slice(start, end); 30 | // decorate the error line 31 | snippet[snippet.length - 5] = '' + snippet[snippet.length - 5] + ''; 32 | item.content = snippet.join('\n'); 33 | 34 | cb(null, item); 35 | })); 36 | } else { 37 | cb(); 38 | } 39 | }, function(e, items) { 40 | items = items.filter(function(item) { return !!item; }); 41 | 42 | // if something bad happened while processing the stacktrace 43 | // make sure to return something useful 44 | if (e) { 45 | console.error(e); 46 | 47 | return res.send(err.stack); 48 | } 49 | 50 | var html = '

' + err.message + '

    '; 51 | 52 | items.forEach(function(item) { 53 | html += '
  • at ' + item.functionName || 'anonymous'; 54 | html += ' (' + item.fileName + ':' + item.lineNumber + ':' + item.columnNumber + ')'; 55 | html += '

    ' + item.content + '

    '; 56 | html += '

  • '; 57 | }); 58 | 59 | html += '
'; 60 | 61 | res.send(html); 62 | }); 63 | }); 64 | 65 | app.listen(7777); 66 | -------------------------------------------------------------------------------- /chapter07/cache-system/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cache-system", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "cache.js", 6 | "dependencies": { 7 | "ms": "~0.6.2" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /chapter07/etag-cached-data/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var handleEtag = function(req, res, next) { 4 | res.cachable = function(etag, isStaleCallback) { 5 | if (!etag) { 6 | throw new Error('Etag required'); 7 | } 8 | 9 | res.set({ 'ETag': etag }); 10 | 11 | // 304 Not Modified 12 | if (req.fresh) { 13 | // remove content headers 14 | if (res._headers) { 15 | Object.keys(res._headers).forEach(function(header) { 16 | if (header.indexOf('content') === 0) { 17 | res.removeHeader(header); 18 | } 19 | }); 20 | } 21 | 22 | res.statusCode = 304; 23 | return res.end(); 24 | } else { 25 | // load dynamic content now 26 | isStaleCallback(); 27 | } 28 | }; 29 | 30 | next(); 31 | }; 32 | 33 | var express = require('express'); 34 | var app = express(); 35 | 36 | var getEtag = function(key, cb) { 37 | return setImmediate(function() { 38 | cb(null, '4ALOzWNKcFh6OImOu5t68l0C2os='); 39 | }); 40 | }; 41 | 42 | app.get('/cached-data', handleEtag, function(req, res, next) { 43 | getEtag('cached-data', function(err, etag) { 44 | if (err) { return next(err); } 45 | 46 | res.cachable(etag, function() { 47 | // the second time you visit the page this won't get called 48 | console.log('loading dynamic content'); 49 | res.send('Big content loaded from database/cache/filesystem here.'); 50 | }); 51 | }); 52 | }); 53 | 54 | app.listen(7777); 55 | -------------------------------------------------------------------------------- /chapter07/static-resources/assets/v0.1/sample.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #DEDEDE; 3 | } 4 | -------------------------------------------------------------------------------- /chapter07/static-resources/assets/v0.1/sample.js: -------------------------------------------------------------------------------- 1 | console.log('just a static resource'); 2 | -------------------------------------------------------------------------------- /chapter07/static-resources/certs/nodeapp.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIChzCCAfACCQDu/CnYlaxpKzANBgkqhkiG9w0BAQUFADCBhzELMAkGA1UEBhMC 3 | Uk8xDjAMBgNVBAgTBUFyZ2VzMRAwDgYDVQQHEwdQaXRlc3RpMREwDwYDVQQKEwhB 4 | bGVzc2lvUzEZMBcGA1UEAxMQQWxleGFuZHJ1VmxhZHV0dTEoMCYGCSqGSIb3DQEJ 5 | ARYZYWxlc3Npby5pam9vbWxhQGdtYWlsLmNvbTAeFw0xNDA0MjQxOTQyMzFaFw0x 6 | NTA0MjQxOTQyMzFaMIGHMQswCQYDVQQGEwJSTzEOMAwGA1UECBMFQXJnZXMxEDAO 7 | BgNVBAcTB1BpdGVzdGkxETAPBgNVBAoTCEFsZXNzaW9TMRkwFwYDVQQDExBBbGV4 8 | YW5kcnVWbGFkdXR1MSgwJgYJKoZIhvcNAQkBFhlhbGVzc2lvLmlqb29tbGFAZ21h 9 | aWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrToOm6Ewep4UIwXpR 10 | BfG4rWTpqD6f4Qs8um5nDsJuWOHHxkyP9J9J7f/odKouMYl69+RECPkHpHkDykuu 11 | PZixbAA5l/CC9KYv+flKdfiFje8EAUyTtTgV8phIl1/I7f5od2kMKkFFIX4UjAFP 12 | qmwRoah1sp4tDDbRIHHcmhbBPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAGdMRrkM 13 | D/azGcDlC3zTLrCgb3IN1d2jDwXFYxx2RvOVcNAXcMxkJCYnNdjqU70nmBUWpHow 14 | 6v68LlWNaIlu0vgw1GKk9xoWpoVrAYjUgS+htuRNpQN/HHnnnMZQn5Ma6THCbABj 15 | g6kc/DV8lBsahNSuiuLTuboqJ4kXmUFUYDK/ 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /chapter07/static-resources/certs/nodeapp.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIB+TCCAWICAQAwgYcxCzAJBgNVBAYTAlJPMQ4wDAYDVQQIEwVBcmdlczEQMA4G 3 | A1UEBxMHUGl0ZXN0aTERMA8GA1UEChMIQWxlc3Npb1MxGTAXBgNVBAMTEEFsZXhh 4 | bmRydVZsYWR1dHUxKDAmBgkqhkiG9w0BCQEWGWFsZXNzaW8uaWpvb21sYUBnbWFp 5 | bC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKtOg6boTB6nhQjBelEF 6 | 8bitZOmoPp/hCzy6bmcOwm5Y4cfGTI/0n0nt/+h0qi4xiXr35EQI+QekeQPKS649 7 | mLFsADmX8IL0pi/5+Up1+IWN7wQBTJO1OBXymEiXX8jt/mh3aQwqQUUhfhSMAU+q 8 | bBGhqHWyni0MNtEgcdyaFsE/AgMBAAGgMTAWBgkqhkiG9w0BCQcxCRMHcm9uYWxk 9 | bzAXBgkqhkiG9w0BCQIxChMIQWxlc3Npb1MwDQYJKoZIhvcNAQEFBQADgYEAd+zB 10 | XJyLT5jK9KJH7nfeE56H9nDtlItAximXhksz9FoY3WRyocBxvtXNWycpUkvzZCBm 11 | Us12bjPDKqn2YgFOMXLqnSp+btFhGUN8MAjiy81Gn8lNiqe58GJ/iXjitQfnVtuh 12 | ibv1EWDaAKRYDP4oF1IvqQp2czqMR/gFOAvm8qg= 13 | -----END CERTIFICATE REQUEST----- 14 | -------------------------------------------------------------------------------- /chapter07/static-resources/certs/nodeapp.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQCrToOm6Ewep4UIwXpRBfG4rWTpqD6f4Qs8um5nDsJuWOHHxkyP 3 | 9J9J7f/odKouMYl69+RECPkHpHkDykuuPZixbAA5l/CC9KYv+flKdfiFje8EAUyT 4 | tTgV8phIl1/I7f5od2kMKkFFIX4UjAFPqmwRoah1sp4tDDbRIHHcmhbBPwIDAQAB 5 | AoGAb3jZ5oSG8/Oid/4esBakIr6CSiG3DqaMf2n5aYqLLu8bjLoXHB3cgWkj+4nf 6 | K8wEYEZq/XKxSi3WqYa8FJf0gX4jr9QeBbcvucN3baNKd4D72UhfknNk7/7o1k+G 7 | fJ6l2cxp2WR9OGXjGSAFW/qqbEGGzQQM9fi71e6D42DcwrECQQDiGg9qXo4Dschf 8 | XIH+OCOHTqYfyVuyCXyi3ADOsg+5TPVfBjyKN0uljZx/GvDGIn+IBxb8n3Wmi/fn 9 | QlhizPtTAkEAwfWLXcMrbamXPGI2SuvQr9D29CcIf+bwmDvMgYoTy37RgeUr3SIE 10 | UZkMbG1gRbM9iA2x91Stis1x/spYFxBQ5QJBAMo2znSujkqllPQcszIfGT9pStAA 11 | 8V5gd7TcGGjD/aYXOxg6Zqii3af55+4RtScvMWoCFT/oiXtjkVqmDpCGjXsCQQC6 12 | 3LhnHdfiL+gfabNK3QPRzu2M7WoX50NWNw5goslNXSG1Fjf7NIGap2u7rmh43iVi 13 | xWb2lOMY/bNcuE7D8ZktAkBC7/KyfI3xWxjk5Ep84opFnAJs5MkQeaN8z9HV3obN 14 | 70Blgs1Thh16cYctFluMSB1QKyTX/Le0VyTazH245sV5 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /chapter07/static-resources/certs/nodeapp.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQCrToOm6Ewep4UIwXpRBfG4rWTpqD6f4Qs8um5nDsJuWOHHxkyP 3 | 9J9J7f/odKouMYl69+RECPkHpHkDykuuPZixbAA5l/CC9KYv+flKdfiFje8EAUyT 4 | tTgV8phIl1/I7f5od2kMKkFFIX4UjAFPqmwRoah1sp4tDDbRIHHcmhbBPwIDAQAB 5 | AoGAb3jZ5oSG8/Oid/4esBakIr6CSiG3DqaMf2n5aYqLLu8bjLoXHB3cgWkj+4nf 6 | K8wEYEZq/XKxSi3WqYa8FJf0gX4jr9QeBbcvucN3baNKd4D72UhfknNk7/7o1k+G 7 | fJ6l2cxp2WR9OGXjGSAFW/qqbEGGzQQM9fi71e6D42DcwrECQQDiGg9qXo4Dschf 8 | XIH+OCOHTqYfyVuyCXyi3ADOsg+5TPVfBjyKN0uljZx/GvDGIn+IBxb8n3Wmi/fn 9 | QlhizPtTAkEAwfWLXcMrbamXPGI2SuvQr9D29CcIf+bwmDvMgYoTy37RgeUr3SIE 10 | UZkMbG1gRbM9iA2x91Stis1x/spYFxBQ5QJBAMo2znSujkqllPQcszIfGT9pStAA 11 | 8V5gd7TcGGjD/aYXOxg6Zqii3af55+4RtScvMWoCFT/oiXtjkVqmDpCGjXsCQQC6 12 | 3LhnHdfiL+gfabNK3QPRzu2M7WoX50NWNw5goslNXSG1Fjf7NIGap2u7rmh43iVi 13 | xWb2lOMY/bNcuE7D8ZktAkBC7/KyfI3xWxjk5Ep84opFnAJs5MkQeaN8z9HV3obN 14 | 70Blgs1Thh16cYctFluMSB1QKyTX/Le0VyTazH245sV5 15 | -----END RSA PRIVATE KEY----- 16 | -----BEGIN CERTIFICATE----- 17 | MIIChzCCAfACCQDu/CnYlaxpKzANBgkqhkiG9w0BAQUFADCBhzELMAkGA1UEBhMC 18 | Uk8xDjAMBgNVBAgTBUFyZ2VzMRAwDgYDVQQHEwdQaXRlc3RpMREwDwYDVQQKEwhB 19 | bGVzc2lvUzEZMBcGA1UEAxMQQWxleGFuZHJ1VmxhZHV0dTEoMCYGCSqGSIb3DQEJ 20 | ARYZYWxlc3Npby5pam9vbWxhQGdtYWlsLmNvbTAeFw0xNDA0MjQxOTQyMzFaFw0x 21 | NTA0MjQxOTQyMzFaMIGHMQswCQYDVQQGEwJSTzEOMAwGA1UECBMFQXJnZXMxEDAO 22 | BgNVBAcTB1BpdGVzdGkxETAPBgNVBAoTCEFsZXNzaW9TMRkwFwYDVQQDExBBbGV4 23 | YW5kcnVWbGFkdXR1MSgwJgYJKoZIhvcNAQkBFhlhbGVzc2lvLmlqb29tbGFAZ21h 24 | aWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrToOm6Ewep4UIwXpR 25 | BfG4rWTpqD6f4Qs8um5nDsJuWOHHxkyP9J9J7f/odKouMYl69+RECPkHpHkDykuu 26 | PZixbAA5l/CC9KYv+flKdfiFje8EAUyTtTgV8phIl1/I7f5od2kMKkFFIX4UjAFP 27 | qmwRoah1sp4tDDbRIHHcmhbBPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAGdMRrkM 28 | D/azGcDlC3zTLrCgb3IN1d2jDwXFYxx2RvOVcNAXcMxkJCYnNdjqU70nmBUWpHow 29 | 6v68LlWNaIlu0vgw1GKk9xoWpoVrAYjUgS+htuRNpQN/HHnnnMZQn5Ma6THCbABj 30 | g6kc/DV8lBsahNSuiuLTuboqJ4kXmUFUYDK/ 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /chapter07/static-resources/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-resources", 3 | "version": "0.0.1", 4 | "description": "Serving static resources", 5 | "main": "server.js", 6 | "dependencies": { 7 | "compression": "~1.0.1", 8 | "morgan": "~1.0.0", 9 | "express": "~4.0.0", 10 | "versionator": "~0.4.0", 11 | "serve-static": "~1.0.4", 12 | "ejs": "~1.0.0", 13 | "buffet": "~0.6.4", 14 | "cluster-master": "~0.2.0" 15 | }, 16 | "devDependencies": {}, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1", 19 | "start": "node server.js" 20 | }, 21 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 22 | "license": "MIT" 23 | } 24 | -------------------------------------------------------------------------------- /chapter07/static-resources/public/sample.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #DEDEDE; 3 | } 4 | -------------------------------------------------------------------------------- /chapter07/static-resources/public/sample.js: -------------------------------------------------------------------------------- 1 | console.log('just a static resource'); 2 | -------------------------------------------------------------------------------- /chapter07/static-resources/server-cluster.js: -------------------------------------------------------------------------------- 1 | var clusterMaster = require("cluster-master"); 2 | 3 | clusterMaster({ 4 | exec: "server.js", 5 | env: { NODE_ENV: "production" }, 6 | size: process.env.SIZE || null 7 | }); 8 | -------------------------------------------------------------------------------- /chapter07/static-resources/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* 4 | var fs = require('fs'); 5 | var fsStat = fs.stat; 6 | fs.stat = function(path, cb) { 7 | console.log('fs.stat: ' + path); 8 | fsStat(path, cb); 9 | }; 10 | */ 11 | 12 | var express = require('express'); 13 | var app = express(); 14 | var ejs = require('ejs'); 15 | var versionator = require('versionator'); 16 | var serve = require('serve-static'); 17 | var compress = require('compression')(); 18 | var buffet = require('buffet'); 19 | 20 | // when running behind NGiNX 21 | app.set('trust proxy', true); 22 | 23 | app.set('view engine', 'html'); 24 | app.engine('html', require('ejs').renderFile); 25 | 26 | app.version = process.env.VERSION || '0.1'; 27 | var versionate = versionator.createBasic('v' + app.version); 28 | app.locals.getResourcePath = versionate.versionPath; 29 | 30 | // enable gzip compression 31 | app.use(compress); 32 | 33 | // remove the version from the URL so the serve middleware works as expected 34 | app.use('/assets', versionate.middleware); 35 | 36 | if (process.env.NODE_ENV === 'production') { 37 | app.use('/assets', buffet({ 38 | maxAge: (1000 * 60 * 60 * 24 * 31) 39 | })); 40 | } else { 41 | // serving static files (that expire after a month) 42 | app.use('/assets', serve(__dirname + '/public', { 43 | maxAge: (1000 * 60 * 60 * 24 * 31) 44 | })); 45 | } 46 | 47 | app.get('/', function(req, res, next) { 48 | res.render('home'); 49 | }); 50 | 51 | app.listen(process.env.PORT || 7777); 52 | -------------------------------------------------------------------------------- /chapter07/static-resources/stud.conf: -------------------------------------------------------------------------------- 1 | # stud(8), The Scalable TLS Unwrapping Daemon's configuration 2 | # Listening address. REQUIRED. 3 | # type: string 4 | # syntax: [HOST]:PORT 5 | frontend = "[*]:443" 6 | # Upstream server address. REQUIRED. 7 | # type: string 8 | # syntax: [HOST]:PORT. 9 | backend = "[127.0.0.1]:80" 10 | pem-file = "certs/nodeapp.pem" 11 | # EOF 12 | -------------------------------------------------------------------------------- /chapter07/static-resources/views/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello Static Resources 6 | 7 | 8 | 9 | Hello Static Resources! 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter07/streaming-templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "streaming-templates", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.1.0", 8 | "trumpet": "~1.6.3" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "node server.js" 14 | }, 15 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /chapter07/streaming-templates/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var stream = require('stream'); 6 | var trumpet = require('trumpet'); 7 | var fs = require('fs'); 8 | 9 | var template = fs.readFileSync('./template.html', 'utf8'); 10 | 11 | var data = ['Store A - socks $1', 'Store B - socks $2', 'Store C - socks $10', 'Store D - socks 100$']; 12 | 13 | var getData = function() { 14 | var readableStream = new stream.Readable(); 15 | readableStream.setEncoding('utf8'); 16 | readableStream._read = function(size) {}; 17 | var counter = 0; 18 | 19 | var interval = setInterval(function() { 20 | if (counter >= data.length) { 21 | readableStream.push(null); 22 | return clearInterval(interval); 23 | } 24 | readableStream.push(data[counter]); 25 | counter++; 26 | }, 1000); 27 | 28 | return readableStream; 29 | }; 30 | 31 | app.get('/', function(req, res, next) { 32 | var tr = trumpet(); 33 | // writable stream 34 | var ws = tr.select('ul').createWriteStream(); 35 | 36 | setImmediate(function() { 37 | tr.write(template); 38 | tr.end(); 39 | }); 40 | 41 | getData().on('data', function(data) { 42 | ws.write('
  • ' + data + '
  • '); 43 | }).on('end', function() { 44 | ws.end(); 45 | }); 46 | 47 | tr.pipe(res); 48 | }); 49 | 50 | app.listen(7777); 51 | -------------------------------------------------------------------------------- /chapter07/streaming-templates/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Clothes aggregator 6 | 7 | 8 |

    Socks prices

    9 | 10 |
    11 | 12 |
      13 |
    14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter07/using-streams/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var express = require('express'); 5 | var app = express(); 6 | 7 | // non-stream version 8 | // app.get('/big-file.txt', function(req, res, next) { 9 | // fs.readFile(__dirname + '/big-file.txt', 'utf8', function(err, content) { 10 | // if (err) { return res.status(500).send(‘Internal Error’); } 11 | // 12 | // res.send(content); 13 | // }); 14 | // }); 15 | 16 | // streaming version 17 | app.get('/big-file.txt', function(req, res, next) { 18 | fs.createReadStream(__dirname + '/big-file.txt').on('error', function(err) { 19 | // handle error 20 | }).pipe(res); 21 | }); 22 | 23 | // stream benefits: 24 | // - lower memory usage: we just load a chunk into memory, process it and move on to the next chunk 25 | // - finishing tasks earlier: 26 | // instead of waiting for the whole thing to load into memory and then process it as a whole 27 | // we load chunk by chunk and process it right away, so that when we finish the work with the final chunk we are done 28 | 29 | app.listen(7777); 30 | -------------------------------------------------------------------------------- /chapter08/collecting-metrics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collecting-metrics", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.1.1", 8 | "measured": "~0.1.6", 9 | "usage": "~0.3.9" 10 | }, 11 | "devDependencies": {}, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1", 14 | "start": "node server.js" 15 | }, 16 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /chapter08/collecting-metrics/public/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Realtime Monitoring Dashboard 6 | 7 | 8 | 9 | 10 | 11 | 12 | 48 | 49 | 50 |

    Realtime metrics

    51 | 52 |
    53 |
    54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /chapter08/collecting-metrics/public/js/lib/jquery.flot.symbol.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin that adds some extra symbols for plotting points. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The symbols are accessed as strings through the standard symbol options: 7 | 8 | series: { 9 | points: { 10 | symbol: "square" // or "diamond", "triangle", "cross" 11 | } 12 | } 13 | 14 | */ 15 | 16 | (function ($) { 17 | function processRawData(plot, series, datapoints) { 18 | // we normalize the area of each symbol so it is approximately the 19 | // same as a circle of the given radius 20 | 21 | var handlers = { 22 | square: function (ctx, x, y, radius, shadow) { 23 | // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 24 | var size = radius * Math.sqrt(Math.PI) / 2; 25 | ctx.rect(x - size, y - size, size + size, size + size); 26 | }, 27 | diamond: function (ctx, x, y, radius, shadow) { 28 | // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) 29 | var size = radius * Math.sqrt(Math.PI / 2); 30 | ctx.moveTo(x - size, y); 31 | ctx.lineTo(x, y - size); 32 | ctx.lineTo(x + size, y); 33 | ctx.lineTo(x, y + size); 34 | ctx.lineTo(x - size, y); 35 | }, 36 | triangle: function (ctx, x, y, radius, shadow) { 37 | // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) 38 | var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); 39 | var height = size * Math.sin(Math.PI / 3); 40 | ctx.moveTo(x - size/2, y + height/2); 41 | ctx.lineTo(x + size/2, y + height/2); 42 | if (!shadow) { 43 | ctx.lineTo(x, y - height/2); 44 | ctx.lineTo(x - size/2, y + height/2); 45 | } 46 | }, 47 | cross: function (ctx, x, y, radius, shadow) { 48 | // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 49 | var size = radius * Math.sqrt(Math.PI) / 2; 50 | ctx.moveTo(x - size, y - size); 51 | ctx.lineTo(x + size, y + size); 52 | ctx.moveTo(x - size, y + size); 53 | ctx.lineTo(x + size, y - size); 54 | } 55 | }; 56 | 57 | var s = series.points.symbol; 58 | if (handlers[s]) 59 | series.points.symbol = handlers[s]; 60 | } 61 | 62 | function init(plot) { 63 | plot.hooks.processDatapoints.push(processRawData); 64 | } 65 | 66 | $.plot.plugins.push({ 67 | init: init, 68 | name: 'symbols', 69 | version: '1.0' 70 | }); 71 | })(jQuery); 72 | -------------------------------------------------------------------------------- /chapter08/health-endpoint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "health-endpoint", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.1.1" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "start": "node server.js" 13 | }, 14 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /chapter08/health-endpoint/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var http = require('http'); 4 | var express = require('express'); 5 | var app = express(); 6 | 7 | var os = require('os'); 8 | var hostname = os.hostname(); 9 | 10 | var addStatusEndpoint = function(key, server) { 11 | return function(req, res, next) { 12 | if (!req.query.key || req.query.key !== key) { 13 | res.status(401).send('401 - Unauthorized'); 14 | } else { 15 | server.getConnections(function(err, count) { 16 | if (err) { 17 | return res.status(500).send('Error getting connections' + err.message); 18 | } 19 | 20 | res.send({ 21 | hostname: hostname, 22 | pid: process.pid, 23 | uptime: process.uptime(), 24 | memoryUsage: process.memoryUsage(), 25 | activeConnections: count 26 | }); 27 | }); 28 | } 29 | } 30 | }; 31 | 32 | var server = http.createServer(app).listen(process.env.PORT || 7777); 33 | 34 | app.get('/app-status', addStatusEndpoint('long_secret_key', server)); 35 | 36 | // sample response: 37 | /* 38 | { 39 | hostname: "MBP.local", 40 | pid: 39673, 41 | uptime: 93, 42 | memoryUsage: { 43 | rss: 21397504, 44 | heapTotal: 16571136, 45 | heapUsed: 6632784 46 | }, 47 | activeConnections: 2 48 | } 49 | */ 50 | -------------------------------------------------------------------------------- /chapter08/logging/lib/customStream.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Streams2 Writable when available (Node > 0.10.x) 4 | // or falling back to using a polyfill 5 | var util = require('util'); 6 | var stream = require('stream'); 7 | var Writable = stream.Writable || require('readable-stream').Writable; 8 | var request = require('request'); 9 | 10 | function LogStream(options) { 11 | options = options || {}; 12 | 13 | this._url = options.url; 14 | this._attemptInterval = 1000; 15 | this._maxAttempts = 3; 16 | 17 | Writable.call(this, options); 18 | } 19 | util.inherits(LogStream, Writable); 20 | 21 | LogStream.prototype._write = function(chunk, enc, cb) { 22 | this._sendLog(chunk, 1, cb); 23 | }; 24 | 25 | LogStream.prototype._sendLog = function(data, attempt, cb) { 26 | var _this = this; 27 | 28 | if (attempt > this._maxAttempts) { 29 | // silently ignore and loose the data, not the best option though 30 | return cb(); 31 | } 32 | 33 | request({ 34 | headers: { 35 | 'Content-Type': 'application/json' 36 | }, 37 | method: 'POST', 38 | body: data, 39 | url: this._url 40 | }, function(err, res, body) { 41 | if (err || (res.statusCode !== 200 && res.statusCode !== 201)) { 42 | setTimeout(function() { 43 | attempt++; 44 | _this._sendLog(data, attempt, cb); 45 | }, _this._attemptInterval); 46 | } else { 47 | cb(); 48 | } 49 | }); 50 | }; 51 | 52 | module.exports = LogStream; 53 | -------------------------------------------------------------------------------- /chapter08/logging/lib/getHrTime.js: -------------------------------------------------------------------------------- 1 | // get high resolution time (in miliseconds) 2 | module.exports = function getHrTime() { 3 | // ts = [seconds, nanoseconds] 4 | var ts = process.hrtime(); 5 | // convert seconds to miliseconds and nanoseconds to miliseconds as well 6 | return (ts[0] * 1000) + (ts[1] / 1000000); 7 | }; 8 | -------------------------------------------------------------------------------- /chapter08/logging/lib/logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bunyan = require('bunyan'); 4 | var logger; 5 | 6 | var serializers = {}; 7 | 8 | serializers.req = function(req) { 9 | return { 10 | reqId: req.reqId, 11 | method: req.method, 12 | url: req.originalUrl, 13 | headers: req.headers, 14 | ip: req.ip 15 | }; 16 | }; 17 | 18 | exports.serializers = serializers; 19 | 20 | exports.createLogger = function(opts) { 21 | opts = opts || {}; 22 | 23 | // var LogStream = require('./customStream'); 24 | // var logStream = new LogStream({ url: 'http://site.tld/appName/logs?key=SECRET' }); 25 | 26 | if (!logger) { 27 | logger = bunyan.createLogger({ 28 | name: opts.appName, 29 | serializers: { 30 | req: serializers.req, 31 | res: bunyan.stdSerializers.res, 32 | err: bunyan.stdSerializers.err 33 | }, 34 | streams: [{ 35 | type: 'rotating-file', 36 | path: opts.logFile, 37 | period: '1d', // daily rotation 38 | count: 3, // keep 3 back copies 39 | level: opts.level || 'info' 40 | }] 41 | // streams: [{ 42 | // stream: logStream, 43 | // level: opts.level || 'info' 44 | // }] 45 | }); 46 | } 47 | 48 | return logger; 49 | }; 50 | -------------------------------------------------------------------------------- /chapter08/logging/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logging", 3 | "version": "0.0.0", 4 | "description": "Logging with Bunyan", 5 | "main": "server.js", 6 | "dependencies": { 7 | "cuid": "~1.2.4", 8 | "verror": "~1.4.0", 9 | "server-destroy": "~1.0.0", 10 | "express": "~4.1.1", 11 | "request": "~2.34.0", 12 | "bunyan": "~0.22.3", 13 | "readable-stream": "~1.0.27-1" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1", 18 | "start": "node server.js" 19 | }, 20 | "keywords": [ 21 | "logging", 22 | "express", 23 | "bunyan" 24 | ], 25 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 26 | "license": "MIT" 27 | } 28 | -------------------------------------------------------------------------------- /chapter08/network-traffic/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | function getByteStats(socket, cb) { 7 | var bytesRead, bytesWritten; 8 | 9 | // header 'Connection' is set to 'keep-alive', meaning we reuse the socket 10 | if (!socket.destroyed) { 11 | bytesRead = socket.bytesRead - (socket.___previousBytesRead || 0); 12 | bytesWritten = socket.bytesWritten - (socket.___previousBytesWritten || 0); 13 | 14 | // HACK: remember previously read/written bytes 15 | // since we're dealing with the same socket 16 | // (header 'Connection' set to 'keep-alive') 17 | socket.___previousBytesRead = socket.bytesRead; 18 | socket.___previousBytesWritten = socket.bytesWritten; 19 | } else { 20 | // header 'Connection' is set to 'closed', meaning the socket is destroyed 21 | bytesRead = socket.bytesRead; 22 | bytesWritten = socket.bytesWritten; 23 | } 24 | 25 | cb({ read: bytesRead, written: bytesWritten }); 26 | } 27 | 28 | var totalBytesRead = 0; 29 | var totalBytesWritten = 0; 30 | app.use(function(req, res, next) { 31 | var cb = function(bytes) { 32 | totalBytesRead += bytes.read; 33 | totalBytesWritten += bytes.written; 34 | }; 35 | 36 | res.once('close', getByteStats.bind(null, req.socket, cb)); 37 | res.once('finish', getByteStats.bind(null, req.socket, cb)); 38 | 39 | next(); 40 | }); 41 | 42 | app.get('/bytes', function(req, res, next) { 43 | res.json({ 44 | read: totalBytesRead, 45 | written: totalBytesWritten 46 | }); 47 | }); 48 | 49 | app.get('*', function(req, res, next) { 50 | // res.writeHead(200, { 'Connection': 'close' }); 51 | require('fs').createReadStream(__filename).pipe(res); 52 | }); 53 | 54 | app.listen(process.env.PORT || 7777); 55 | -------------------------------------------------------------------------------- /chapter08/slowest-endpoints/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slowest-endpoints", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.1.1" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "start": "node server.js" 13 | }, 14 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /chapter08/slowest-endpoints/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | var slowestEndPoints = []; 7 | var fastestOfTheSlowest = 0; 8 | 9 | var getHrTime = function() { 10 | // ts = [seconds, nanoseconds] 11 | var ts = process.hrtime(); 12 | // convert seconds to miliseconds and nanoseconds to miliseconds as well 13 | return (ts[0] * 1000) + (ts[1] / 1000000); 14 | }; 15 | 16 | app.use(function(req, res, next) { 17 | res._startTime = getHrTime(); 18 | 19 | var writeHead = res.writeHead; 20 | 21 | res.writeHead = function() { 22 | var min = Infinity; 23 | var index; 24 | var responseTime = getHrTime() - res._startTime; 25 | 26 | // we want to store the 10 slowest endpoints 27 | if (slowestEndPoints.length < 10) { 28 | slowestEndPoints.push({ 29 | url: req.url, 30 | responseTime: responseTime 31 | }); 32 | } else { 33 | if (fastestOfTheSlowest === 0) { 34 | fastestOfTheSlowest = Infinity; 35 | // this will happen only once, after the first 10 elements are inserted 36 | // in the array and the 11th is compared 37 | slowestEndPoints.forEach(function(endpoint) { 38 | fastestOfTheSlowest = Math.min(endpoint.responseTime, fastestOfTheSlowest); 39 | }); 40 | } 41 | 42 | // is the response time slower than the fastest response time in the array? 43 | if (responseTime > fastestOfTheSlowest) { 44 | slowestEndPoints.forEach(function(endPoint, i) { 45 | if (endPoint.responseTime === fastestOfTheSlowest) { 46 | // remember what array item should be replaced 47 | index = i; 48 | } else { 49 | // searching for the next fastest response time 50 | min = Math.min(endPoint.responseTime, min); 51 | } 52 | }); 53 | 54 | slowestEndPoints[index] = { 55 | url: req.url, 56 | responseTime: responseTime 57 | }; 58 | fastestOfTheSlowest = min; 59 | } 60 | } 61 | 62 | writeHead.apply(res, arguments); 63 | }; 64 | 65 | next(); 66 | }); 67 | 68 | var getRandomNrBetween = function(low, high) { 69 | return Math.floor(Math.random() * (high - low + 1) + low); 70 | }; 71 | 72 | app.get('/slowest-endpoints', function(req, res, next) { 73 | // display in descending order 74 | res.send(slowestEndPoints.sort(function(a, b) { 75 | if (a.responseTime > b.responseTime) { 76 | return -1; 77 | } else if (a.responseTime < b.responseTime) { 78 | return 1; 79 | } else { 80 | return 0; 81 | } 82 | })); 83 | }); 84 | 85 | app.get('*', function(req, res, next) { 86 | setTimeout(function() { 87 | res.end('ok'); 88 | }, getRandomNrBetween(100, 1000)); 89 | }); 90 | 91 | app.listen(process.env.PORT || 7777); 92 | -------------------------------------------------------------------------------- /chapter08/time-spent-in-function/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var getRandomNrBetween = function(low, high) { 4 | return Math.floor(Math.random() * (high - low + 1) + low); 5 | }; 6 | 7 | var getHrTime = function() { 8 | // ts = [seconds, nanoseconds] 9 | var ts = process.hrtime(); 10 | // convert seconds to miliseconds and nanoseconds to miliseconds as well 11 | return (ts[0] * 1000) + (ts[1] / 1000000); 12 | }; 13 | 14 | var wrapAsyncFn = function(func, callback) { 15 | return function() { 16 | var args = Array.prototype.slice.call(arguments); 17 | var startTime = getHrTime(); 18 | 19 | // last argument should be the function callback 20 | var funcCallback = args.pop(); 21 | 22 | // put our own wrapper instead of the original function callback 23 | args.push(function() { 24 | var endTime = getHrTime(); 25 | funcCallback.apply(this, arguments); 26 | callback(endTime - startTime); 27 | }); 28 | 29 | func.apply(null, args); 30 | }; 31 | }; 32 | 33 | var wrapSyncFn = function(func, callback) { 34 | return function() { 35 | var startTime = getHrTime(); 36 | func.apply(null, arguments); 37 | var endTime = getHrTime(); 38 | callback(endTime - startTime); 39 | }; 40 | }; 41 | 42 | var printTime = function(fnName, time) { 43 | console.log('%s took %s milliseconds to return', fnName, time); 44 | }; 45 | 46 | var queryDbSampleFn = function(userId, cb) { 47 | return setTimeout(function() { 48 | cb(null, { user: 'John', fullname: 'John Doe' }); 49 | }, getRandomNrBetween(300, 1000)); 50 | }; 51 | 52 | queryDbSampleFn = wrapAsyncFn(queryDbSampleFn, printTime.bind(null, 'queryDbSampleFn')); 53 | queryDbSampleFn(32, function(err, data) {}); 54 | 55 | var calculateSum = function(lastNr) { 56 | var sum = 0; 57 | 58 | for (var i = 0; i <= lastNr; i++) { 59 | sum += i; 60 | } 61 | 62 | return sum; 63 | }; 64 | 65 | calculateSum = wrapSyncFn(calculateSum, printTime.bind(null, 'calculateSum')); 66 | calculateSum(9000000); 67 | -------------------------------------------------------------------------------- /chapter09/debugger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "debugger", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "Faker": "~0.7.2", 8 | "cookie-parser": "~1.1.0", 9 | "express": "~4.2.0", 10 | "morgan": "~1.1.0", 11 | "express-session": "~1.1.0" 12 | }, 13 | "devDependencies": {}, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1", 16 | "start": "node server.js" 17 | }, 18 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 19 | "license": "MIT" 20 | } 21 | -------------------------------------------------------------------------------- /chapter09/debugger/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var morgan = require('morgan'); 6 | var cookieParser = require('cookie-parser'); 7 | var session = require('express-session'); 8 | var Faker = require('Faker'); 9 | 10 | app.use(morgan('dev')); 11 | app.use(cookieParser()); // required before session. 12 | app.use(session({ 13 | secret: 'secret keyword' 14 | })); 15 | 16 | app.get('/', function(req, res, next) { 17 | debugger; 18 | 19 | if (req.session.name) { 20 | res.redirect('/whoami'); 21 | } 22 | 23 | debugger; 24 | 25 | var secretIdentity = { 26 | name: Faker.Name.findName(), 27 | email: Faker.Internet.email() 28 | }; 29 | 30 | req.session.name = secretIdentity.name; 31 | req.session.email = secretIdentity.email; 32 | 33 | var tmpl = 'We will call you ' + secretIdentity.name; 34 | tmpl += ' from now on and email you at ' + secretIdentity.email; 35 | tmpl += '
    Reset your identity by going to the following URL: '; 36 | tmpl += '/refresh'; 37 | 38 | res.send(tmpl); 39 | }); 40 | 41 | app.get('/whoami', function(req, res, next) { 42 | res.send('Name ' + req.session.name + ' | Email: ' + req.session.email); 43 | }); 44 | 45 | app.get('/refresh', function(req, res, next) { 46 | req.session.destroy(function(err) { 47 | if (err) { return next(err); } 48 | 49 | res.redirect('/'); 50 | }); 51 | }); 52 | 53 | app.listen(process.env.PORT || 7777); 54 | 55 | // $ node debug server.js 56 | // then 'cont' 57 | // then visit the / page in the browser 58 | // then 'repl' => tada, access to context 59 | // 60 | // http://nodejs.org/api/debugger.html 61 | -------------------------------------------------------------------------------- /chapter09/debugging-routes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "debugging-routes", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.2.0", 8 | "morgan": "~1.0.1" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "node server.js" 14 | }, 15 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /chapter09/debugging-routes/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var inspect = require('util').inspect; 6 | var morgan = require('morgan'); 7 | 8 | var users = [{ name: 'John Doe', age: 27 }]; 9 | 10 | app.use(morgan()); 11 | 12 | app.route('/users').get(function(req, res, next) { 13 | res.send(users); 14 | }).post(function(req, res, next) { 15 | res.send('ok'); 16 | }); 17 | 18 | app._router.stack.forEach(function(item) { 19 | if (item.route) { 20 | console.log('Route: %s', inspect(item.route, { depth: 5 })); 21 | } else { 22 | console.log('Middleware: %s', item.handle.name || 'anonymous'); 23 | } 24 | console.log('--------------------'); 25 | }); 26 | 27 | app.listen(process.env.PORT || 7777); 28 | -------------------------------------------------------------------------------- /chapter09/error-handler/README.md: -------------------------------------------------------------------------------- 1 | - Run the following commands in the project root to generate the timezones JSON file: 2 | 3 | chmod +x lib/get-data 4 | ./lib/get-data 5 | node node_modules/timezone-js/src/node-preparse.js tz/ > all_cities.json 6 | -------------------------------------------------------------------------------- /chapter09/error-handler/get-data: -------------------------------------------------------------------------------- 1 | ##!/bin/bash 2 | 3 | # NOTE: Run from your webroot 4 | 5 | # Create the /tz directory 6 | mkdir tz 7 | 8 | # Download the latest Olson files 9 | curl ftp://ftp.iana.org/tz/tzdata-latest.tar.gz -o tz/tzdata-latest.tar.gz 10 | 11 | # Expand the files 12 | tar -xvzf tz/tzdata-latest.tar.gz -C tz 13 | 14 | # Optionally, you can remove the downloaded archives. 15 | rm tz/tzdata-latest.tar.gz 16 | 17 | # Preprocess to a JSON file 18 | node node_modules/timezone-js/src/node-preparse.js tz/ > all_cities.json 19 | 20 | # Remove the tz data 21 | rm -rf tz/ 22 | -------------------------------------------------------------------------------- /chapter09/error-handler/lib/error-handler/public/main.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | 4 | var slice = Array.prototype.slice; 5 | 6 | function openFile(url) { 7 | var request = new XMLHttpRequest(); 8 | request.open('GET', url, true); 9 | 10 | request.onload = function() { 11 | if (request.status === 200) { 12 | // Success, do nothing 13 | } else { 14 | // Error 15 | alert('Could not open file in editor: ' + request.responseText); 16 | } 17 | }; 18 | 19 | request.onerror = function() { 20 | // There was a connection error of some sort 21 | }; 22 | 23 | request.send(); 24 | } 25 | 26 | var $ = function(selector) { 27 | return slice.call(document.querySelectorAll(selector)); 28 | }; 29 | 30 | // suboptimal since we aren't using event delegation, but it will do fine 31 | // for our use case 32 | $('.open-in-editor').forEach(function(el) { 33 | el.addEventListener('click', function(evt) { 34 | evt.preventDefault(); 35 | 36 | openFile(el.getAttribute('href')); 37 | }); 38 | }); 39 | 40 | }()); 41 | -------------------------------------------------------------------------------- /chapter09/error-handler/lib/error-handler/public/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= err.name %>:<%= err.message %> 6 | 7 | 8 | 9 | 10 |

    <%= err.name %>:<%= err.message %>

    11 | 12 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /chapter09/error-handler/lib/tz.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var timezoneJS = require('timezone-js'); 4 | var tz = timezoneJS.timezone; 5 | var zoneData = require('../all_cities.json'); 6 | 7 | var zones = Object.keys(zoneData.zones); 8 | 9 | tz.loadingScheme = tz.loadingSchemes.MANUAL_LOAD; 10 | tz.loadZoneDataFromObject(zoneData); 11 | 12 | module.exports = { 13 | timezoneJS: timezoneJS, 14 | zones: zones 15 | }; 16 | -------------------------------------------------------------------------------- /chapter09/error-handler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TimezoneApp", 3 | "version": "0.0.0", 4 | "description": "Display the time based on the timezone", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.2.0", 8 | "timezone-js": "~0.4.10", 9 | "async-each": "~0.1.4", 10 | "stack-trace": "0.0.9", 11 | "highlight.js": "https://github.com/alessioalex/highlight.js/releases/download/npm-v0.1.0/highlight.js.tgz", 12 | "ejs": "~1.0.0", 13 | "debug": "~0.8.1" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1", 18 | "start": "node server.js" 19 | }, 20 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /chapter09/error-handler/public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | } 4 | #time { 5 | font-size: 100px; 6 | font-family: monospace; 7 | border: 1px solid #262626; 8 | display: inline-block; 9 | padding: 0.5em; 10 | } 11 | select { 12 | font-size: 1em; 13 | width: 255px; 14 | height: 50px; 15 | } 16 | -------------------------------------------------------------------------------- /chapter09/error-handler/routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.timezones = require('./timezones'); 4 | -------------------------------------------------------------------------------- /chapter09/error-handler/routes/timezones.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var tz = require('../lib/tz'); 4 | var timezoneJS = tz.timezoneJS; 5 | var debug = require('debug')('timezone-app:routes:timezones'); 6 | 7 | exports.index = function(req, res, next) { 8 | res.render('home', { 9 | zones: tz.zones 10 | }); 11 | }; 12 | 13 | exports.show = function(req, res, next) { 14 | var place = req.url.slice(1); 15 | 16 | debug('showing time for %s', place); 17 | 18 | var time = new timezoneJS.Date(Date.now(), place).toString(); 19 | res.render('time', { 20 | time: time, 21 | timezone: place 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /chapter09/error-handler/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var errorHandler = require('./lib/error-handler'); 6 | var ENV = process.env.NODE_ENV || 'development'; 7 | var debug = require('debug')('timezone-app:main'); 8 | 9 | app.set('view engine', 'html'); 10 | app.set('views', __dirname + '/views'); 11 | app.engine('html', require('ejs').renderFile); 12 | 13 | var routes = require('./routes'); 14 | 15 | // no favicon 16 | app.get('/favicon.ico', function(req, res, next) { 17 | res.status(404).end(); 18 | }); 19 | 20 | app.use(express.static(__dirname + '/public')); 21 | 22 | if (ENV === 'development') { 23 | app.get('/open-editor/*', errorHandler.openEditor); 24 | } 25 | 26 | app.get('/', routes.timezones.index); 27 | app.get('/*', routes.timezones.show); 28 | 29 | if (ENV === 'development') { 30 | app.use(errorHandler.displayDetails); 31 | } 32 | 33 | app.listen(process.env.PORT || 7777); 34 | debug('application started on port %s', process.env.PORT || 7777); 35 | -------------------------------------------------------------------------------- /chapter09/error-handler/views/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | What's the time in ..? 6 | 7 | 8 | 9 |

    Choose a timezone

    10 | 11 | 17 | 18 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /chapter09/error-handler/views/time.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Time in <%= timezone %> now 6 | 7 | 8 | 9 |

    Time in <%= timezone %> now

    10 | 11 |
    12 | 13 | 14 | 15 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter09/heapdump-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heapdump-app", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "heapdump": "~0.2.7", 8 | "express": "~4.2.0" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "node server.js" 14 | }, 15 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /chapter09/heapdump-app/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | var leaks = []; 7 | function Leak() {}; 8 | 9 | var heapdump = require('heapdump'); 10 | var memoryThreshold = 50; 11 | 12 | setInterval(function () { 13 | // get RSS in bytes and transform into megabytes 14 | var memoryUsage = process.memoryUsage().rss / 1024 / 1024; 15 | 16 | if (memoryUsage > memoryThreshold) { 17 | // write to disk 18 | heapdump.writeSnapshot(); 19 | // increase memory threshold 20 | memoryThreshold += 100; 21 | } 22 | }, 60000); 23 | 24 | app.use(function(req, res, next) { 25 | for (var i = 0; i < 1000; i++) { 26 | leaks.push(new Leak()); 27 | } 28 | 29 | res.send('Memory usage: ' + (process.memoryUsage().rss / 1024 / 1024)); 30 | }); 31 | 32 | app.listen(process.env.PORT || 7777); 33 | -------------------------------------------------------------------------------- /chapter09/removing-leftovers/clean-server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | var express = require('express'); 5 | var app = express(); 6 | 7 | app.use(function(req, res, next) { 8 | req.session = { user: 'John', email: 'john@example.com' }; 9 | next(); 10 | }); 11 | 12 | app.get('/', function(req, res, next) { 13 | 14 | 15 | res.send('ok'); 16 | }); 17 | 18 | app.listen(5555); 19 | -------------------------------------------------------------------------------- /chapter09/removing-leftovers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "removing-leftovers", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "clean-server.js", 6 | "dependencies": { 7 | "debug": "~0.8.1", 8 | "express": "~4.2.0" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "node server.js" 14 | }, 15 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /chapter09/removing-leftovers/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var debug = require('debug')('myapp:main'); 4 | var express = require('express'); 5 | var app = express(); 6 | 7 | app.use(function(req, res, next) { 8 | req.session = { user: 'John', email: 'john@example.com' }; 9 | next(); 10 | }); 11 | 12 | app.get('/', function(req, res, next) { 13 | debug('user %s visited /', req.session.user); 14 | 15 | res.send('ok'); 16 | }); 17 | 18 | app.listen(process.env.PORT || 7777); 19 | 20 | // Remote debug statements: 21 | // 22 | // $ groundskeeper -n debug < server.js > clean-server.js 23 | -------------------------------------------------------------------------------- /chapter09/using-replify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "using-replify", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "express": "~4.2.0", 8 | "replify": "~1.2.0" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "start": "node server.js" 14 | }, 15 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /chapter09/using-replify/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var replify = require('replify'); 4 | var express = require('express'); 5 | var app = express(); 6 | 7 | app.use(function(req, res, next) { 8 | res.send('all good'); 9 | }); 10 | 11 | app.listen(process.env.PORT || 7777); 12 | replify('replify-app-' + process.pid, app); 13 | console.log('Use the command below to connect to the REPL:'); 14 | console.log('rc /tmp/repl/replify-app-' + process.pid + '.sock'); 15 | -------------------------------------------------------------------------------- /chapter10/csrf-app/external-page.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | app.use(function(req, res, next) { 7 | var html = '
    '; 8 | html += ''; 9 | html += '
    '; 10 | html += '' 11 | 12 | res.send(html); 13 | }); 14 | 15 | app.listen(4000); 16 | console.log('now open http://localhost:4000/ and see what happens'); 17 | -------------------------------------------------------------------------------- /chapter10/csrf-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csrf-app", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "body-parser": "~1.3.0", 8 | "cookie-session": "~1.0.2", 9 | "csurf": "~1.2.0", 10 | "ejs": "~1.0.0", 11 | "express": "~4.4.0", 12 | "session": "~0.1.0" 13 | }, 14 | "devDependencies": {}, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1", 17 | "start": "node server.js" 18 | }, 19 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /chapter10/csrf-app/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var session = require('cookie-session'); 5 | var bodyParser = require('body-parser'); 6 | var csrf = require('csurf'); 7 | 8 | var app = express(); 9 | 10 | // view setup 11 | app.set('view engine', 'html'); 12 | app.set('views', __dirname + '/views'); 13 | app.engine('html', require('ejs').renderFile); 14 | 15 | app.use(session({ 16 | secret: 'a3Bys4#$2jTs' 17 | })); 18 | app.use(bodyParser()); 19 | app.use(csrf()); 20 | 21 | // in memory store this time 22 | var orders = []; 23 | 24 | app.use(function(req, res, next) { 25 | if (req.method === 'GET') { 26 | res.locals.csrf = function() { 27 | return ""; 28 | } 29 | } 30 | 31 | next(); 32 | }); 33 | 34 | app.get('/', function(req, res, next) { 35 | res.render('index'); 36 | }); 37 | 38 | app.post('/orders', function(req, res, next) { 39 | orders.push({ 40 | details: req.body.order, 41 | placed: new Date() 42 | }); 43 | 44 | res.redirect('/orders'); 45 | }); 46 | 47 | app.get('/orders', function(req, res, next) { 48 | res.render('orders', { 49 | orders: orders 50 | }); 51 | }); 52 | 53 | app.listen(3000); 54 | -------------------------------------------------------------------------------- /chapter10/csrf-app/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Place your order 6 | 7 | 8 |

    Order anything!

    9 | 10 |
    11 | 12 | 13 | <%- csrf() %> 14 |
    15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /chapter10/csrf-app/views/orders.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Your orders 6 | 7 | 8 |

    Orders

    9 | 10 |
      11 | <% orders.forEach(function(order) { %> 12 |
    • 13 | <%= order.details %> - <%- order.placed %> 14 |
    • 15 | <% }) %> 16 |
    17 | 18 | 19 | -------------------------------------------------------------------------------- /chapter10/reauthenticating-user/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reauthenticating-user", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "cookie-session": "~1.0.2", 8 | "body-parser": "~1.3.0", 9 | "method-override": "~2.0.0", 10 | "express": "~4.4.0", 11 | "ejs": "~1.0.0", 12 | "csurf": "~1.2.0" 13 | }, 14 | "devDependencies": {}, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1", 17 | "start": "node server.js" 18 | }, 19 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /chapter10/reauthenticating-user/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var session = require('cookie-session'); 6 | var bodyParser = require('body-parser'); 7 | var methodOverride = require('method-override'); 8 | var csrf = require('csurf'); 9 | 10 | // view setup 11 | app.set('view engine', 'html'); 12 | app.set('views', __dirname + '/views'); 13 | app.engine('html', require('ejs').renderFile); 14 | 15 | app.use(session({ 16 | secret: 'aqEosdP3%osn', 17 | maxAge: (30 * 60 * 1000) // expires in 30 minutes 18 | })); 19 | app.use(bodyParser()); 20 | app.use(methodOverride(function(req, res) { 21 | if (req.body && typeof req.body === 'object' && '_method' in req.body) { 22 | var method = req.body._method; 23 | delete req.body._method; 24 | 25 | return method; 26 | } 27 | })); 28 | app.use(csrf()); 29 | app.use(function(req, res, next) { 30 | if (req.method === 'GET') { 31 | res.locals.csrf = function() { 32 | return ""; 33 | } 34 | } 35 | 36 | next(); 37 | }); 38 | 39 | // using this only in development instead of a real db 40 | var users = { 41 | john: 'password' 42 | }; 43 | 44 | app.get('/', function(req, res, next) { 45 | if (!req.session.user) { 46 | return res.redirect('/login'); 47 | } 48 | 49 | res.render('home'); 50 | }); 51 | 52 | app.get('/login', function(req, res, next) { 53 | res.render('login'); 54 | }); 55 | 56 | app.post('/login', function(req, res, next) { 57 | if (users[req.body.username] === req.body.password) { 58 | req.session.loggedInTime = new Date().getTime(); 59 | req.session.user = req.body.username; 60 | res.redirect('/'); 61 | } else { 62 | res.redirect('/login?login=unsuccessful'); 63 | } 64 | }); 65 | 66 | app.get('/sensitive-data', function(req, res, next) { 67 | if (!req.session.user) { 68 | return res.redirect('/login'); 69 | } 70 | 71 | var ago = (Date.now() - req.session.loggedInTime); 72 | 73 | if (ago <= 60000) { 74 | res.send('really sensitive data here'); 75 | } else { 76 | res.redirect('/login'); 77 | } 78 | }); 79 | 80 | app['delete']('/logout', function(req, res, next) { 81 | req.session = null; 82 | res.redirect('/login'); 83 | }); 84 | 85 | app.listen(7777); 86 | -------------------------------------------------------------------------------- /chapter10/reauthenticating-user/views/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Homepage 6 | 7 | 8 |

    Welcome user

    9 | 10 |

    If you are seeing this page it means that you are logged in!

    11 | 12 |
    13 | 14 | 15 | <%- csrf( )%> 16 |
    17 | 18 | 19 | -------------------------------------------------------------------------------- /chapter10/reauthenticating-user/views/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Login page 6 | 7 | 8 | 9 |

    Login form

    10 | 11 |
    12 |
    13 | 14 | 15 |
    16 | 17 |
    18 | 19 | 20 |
    21 | 22 |
    23 | <%- csrf( )%> 24 | 25 |
    26 |
    27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /chapter10/root-downgrade/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var http = require('http'); 4 | var express = require('express'); 5 | var app = express(); 6 | var PORT = process.env.PORT || 7777; 7 | 8 | app.get('*', function(req, res, next) { 9 | res.send({ 10 | uid: process.getuid(), 11 | gid: process.getgid() 12 | }); 13 | }); 14 | 15 | http.createServer(app).listen(PORT, function() { 16 | console.log("Express server listening on port " + PORT); 17 | downgradeFromRoot(); 18 | }); 19 | 20 | function downgradeFromRoot() { 21 | if (process.env.SUDO_GID && process.env.SUDO_UID) { 22 | process.setgid(parseInt(process.env.SUDO_GID, 10)); 23 | process.setuid(parseInt(process.env.SUDO_UID, 10)); 24 | } 25 | } 26 | 27 | // Running this example: 28 | // 29 | // sudo PORT=80 node basic.js 30 | -------------------------------------------------------------------------------- /chapter10/uploading-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uploading-files", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "connect-multiparty": "~1.0.4", 8 | "gm": "~1.16.0", 9 | "express": "~4.4.0" 10 | }, 11 | "devDependencies": {}, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1", 14 | "start": "node server.js" 15 | }, 16 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /chapter10/uploading-files/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | var multipart = require('connect-multiparty'); 6 | var gm = require('gm'); 7 | var fs = require('fs'); 8 | 9 | // view setup 10 | app.set('view engine', 'html'); 11 | app.set('views', __dirname + '/views'); 12 | app.engine('html', require('ejs').renderFile); 13 | 14 | app.get('/', function(req, res, next) { 15 | res.render('home'); 16 | }); 17 | 18 | app.post('/files', multipart(), function(req, res, next) { 19 | if (!req.files.file) { 20 | return res.send('File missing'); 21 | } 22 | 23 | gm(req.files.file.path).identify(function(err, data) { 24 | if (err) { return next(err); } 25 | 26 | fs.unlink(req.files.file.path, function() { /* ignored the error */ }); 27 | 28 | res.send(data); 29 | }); 30 | }); 31 | 32 | app.listen(7777); 33 | -------------------------------------------------------------------------------- /chapter10/uploading-files/views/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Uploading files 6 | 7 | 8 |

    Upload file

    9 | 10 |
    11 |
    12 | 13 | 14 |
    15 |
    16 | 17 |
    18 |
    19 | 20 | 21 | -------------------------------------------------------------------------------- /chapter10/xss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xss", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "ejs": "~1.0.0", 8 | "express": "~4.4.0", 9 | "secure-filters": "~1.0.5" 10 | }, 11 | "devDependencies": {}, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1", 14 | "start": "node server.js" 15 | }, 16 | "author": "Alexandru Vladutu (http://careers.stackoverflow.com/alessioalex)", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /chapter10/xss/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var app = express(); 5 | 6 | var ejs = require('secure-filters').configure(require('ejs')); 7 | 8 | // view setup 9 | app.set('view engine', 'html'); 10 | app.set('views', __dirname + '/views'); 11 | app.engine('html', ejs.renderFile); 12 | 13 | var users = { 14 | 1: { 15 | name: 'John Doe', 16 | alias: 'john', 17 | description: '', 18 | color: '#CCC;" onload="javascript:alert(\'yet another hack!\')', 19 | config: { 20 | motto: "" 21 | }, 22 | id: 1 23 | } 24 | }; 25 | 26 | app.use('/users/:id', function(req, res, next) { 27 | res.render('user-secure', { 28 | user: users[req.params.id] 29 | }); 30 | }); 31 | 32 | app.listen(7777); 33 | -------------------------------------------------------------------------------- /chapter10/xss/views/user-secure.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | User <%- user.id %> 6 | 7 | 8 | 9 |

    Details for user <%- user.id %>

    10 | 11 |
      12 |
    • Name: <%- user.name %>
    • 13 |
    • Alias: <%- user.alias %>
    • 14 |
    • Description: <%= user.description %>
    • 15 |
    16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /chapter10/xss/views/user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | User <%- user.id %> 6 | 7 | 8 | 9 |

    Details for user <%- user.id %>

    10 | 11 |
      12 |
    • Name: <%- user.name %>
    • 13 |
    • Alias: <%- user.alias %>
    • 14 |
    • Description: <%- user.description %>
    • 15 |
    16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /chapter11/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var path = require('path'); 5 | var favicon = require('static-favicon'); 6 | // var logger = require('morgan'); 7 | var cookieParser = require('cookie-parser'); 8 | var session = require('cookie-session'); 9 | var bodyParser = require('body-parser'); 10 | var multiparty = require('connect-multiparty'); 11 | var Err = require('custom-err'); 12 | var csrf = require('csurf'); 13 | var ejs = require('secure-filters').configure(require('ejs')); 14 | var csrfHelper = require('./lib/middleware/csrf-helper'); 15 | 16 | var homeRouter = require('./routes/index'); 17 | var filesRouter = require('./routes/files'); 18 | 19 | var config = require('./config.json'); 20 | var app = express(); 21 | var ENV = app.get('env'); 22 | 23 | // view engine setup 24 | app.engine('html', ejs.renderFile); 25 | app.set('views', path.join(__dirname, 'views')); 26 | app.set('view engine', 'html'); 27 | 28 | app.use(favicon()); 29 | // if (ENV === 'development') { 30 | // app.use(logger('dev')); 31 | // } 32 | app.use(bodyParser.json()); 33 | app.use(bodyParser.urlencoded()); 34 | // Limit uploads to X Mb 35 | app.use(multiparty({ 36 | maxFilesSize: 1024 * 1024 * config.maxSize 37 | })); 38 | app.use(cookieParser()); 39 | app.use(session({ 40 | keys: ['rQo2#0s!qkE', 'Q.ZpeR49@9!szAe'] 41 | })); 42 | app.use(csrf()); 43 | // add CSRF helper 44 | app.use(csrfHelper); 45 | 46 | app.use('/', homeRouter); 47 | app.use('/files', filesRouter); 48 | 49 | app.use(express.static(path.join(__dirname, 'public'))); 50 | 51 | /// catch 404 and forward to error handler 52 | app.use(function(req, res, next) { 53 | next(Err('Not Found', { status: 404 })); 54 | }); 55 | 56 | /// error handlers 57 | 58 | // development error handler 59 | // will print stacktrace 60 | if (ENV === 'development') { 61 | app.use(function(err, req, res, next) { 62 | res.status(err.status || 500); 63 | res.render('error', { 64 | message: err.message, 65 | error: err 66 | }); 67 | }); 68 | } 69 | 70 | // production error handler 71 | // no stacktraces leaked to user 72 | app.use(function(err, req, res, next) { 73 | res.status(err.status || 500); 74 | res.render('error', { 75 | message: err.message, 76 | error: {} 77 | }); 78 | }); 79 | 80 | module.exports = app; 81 | -------------------------------------------------------------------------------- /chapter11/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var pkg = require('../package.json'); 3 | var debug = require('debug')(pkg.name + ':main'); 4 | var app = require('../app'); 5 | 6 | app.set('port', process.env.PORT || 3000); 7 | 8 | var server = app.listen(app.get('port'), function() { 9 | debug('Express server listening on port ' + server.address().port); 10 | }); 11 | -------------------------------------------------------------------------------- /chapter11/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filesDir": "files", 3 | "maxSize": 5 4 | } 5 | -------------------------------------------------------------------------------- /chapter11/coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /chapter11/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "rules": { 6 | "quotes": 0, 7 | "no-unused-vars": [2, {"vars": "all", "args": "none"}], 8 | "no-underscore-dangle": 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter11/files/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alessioalex/mastering_express_code/a8202b6dcffd33e6716722f83d63bacbbc638454/chapter11/files/.gitkeep -------------------------------------------------------------------------------- /chapter11/lib/hash.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var bcrypt = require('bcrypt'); 4 | var errTo = require('errto'); 5 | 6 | var Hash = {}; 7 | 8 | Hash.generate = function(password, cb) { 9 | bcrypt.genSalt(10, errTo(cb, function(salt) { 10 | bcrypt.hash(password, salt, errTo(cb, function(hash) { 11 | cb(null, hash); 12 | })); 13 | })); 14 | }; 15 | 16 | Hash.compare = function(password, hash, cb) { 17 | bcrypt.compare(password, hash, cb); 18 | }; 19 | 20 | module.exports = Hash; 21 | -------------------------------------------------------------------------------- /chapter11/lib/middleware/csrf-helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(req, res, next) { 4 | res.locals.csrf = function() { 5 | return ""; 6 | }; 7 | 8 | next(); 9 | }; 10 | -------------------------------------------------------------------------------- /chapter11/models/file.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var _ = require('lodash'); 6 | var cuid = require('cuid'); 7 | var errTo = require('errto'); 8 | var Err = require('custom-err'); 9 | var hash = require('../lib/hash'); 10 | var pkg = require('../package.json'); 11 | var config = require('../config.json'); 12 | var debug = require('debug')(pkg.name + ':fileModel'); 13 | 14 | function File(options, id) { 15 | this.id = id || cuid(); 16 | this.meta = _.pick(options, ['name', 'type', 'size', 'hash', 'uploadedAt']); 17 | this.meta.uploadedAt = this.meta.uploadedAt || new Date(); 18 | } 19 | 20 | File.prototype.save = function(path, password, cb) { 21 | var _this = this; 22 | 23 | this.move(path, errTo(cb, function() { 24 | if (!password) { return _this.saveMeta(cb); } 25 | 26 | hash.generate(password, errTo(cb, function(hashedPassword) { 27 | _this.meta.hash = hashedPassword; 28 | 29 | _this.saveMeta(cb); 30 | })); 31 | })); 32 | }; 33 | 34 | File.prototype.move = function(path, cb) { 35 | fs.rename(path, this.path, cb); 36 | }; 37 | 38 | File.prototype.saveMeta = function(cb) { 39 | fs.writeFile(this.path + '.json', JSON.stringify(this.meta), cb); 40 | }; 41 | 42 | File.prototype.isPasswordProtected = function() { 43 | return !!this.meta.hash; 44 | }; 45 | 46 | File.prototype.authenticate = function(password, cb) { 47 | hash.compare(password, this.meta.hash, cb); 48 | }; 49 | 50 | Object.defineProperty(File.prototype, "path", { 51 | get: function() { 52 | return File.dir + '/' + this.id; 53 | } 54 | }); 55 | 56 | File.exists = function(id, cb) { 57 | fs.exists(File.dir + '/' + id, function(exists) { 58 | if (!exists) { 59 | return cb(Err('No such file', { status: 404 })); 60 | } 61 | 62 | cb(); 63 | }); 64 | }; 65 | 66 | File.readMeta = function(id, cb) { 67 | fs.readFile(File.dir + '/' + id + '.json', 'utf8', errTo(cb, function(content) { 68 | cb(null, JSON.parse(content)); 69 | })); 70 | }; 71 | 72 | File.find = function(id, cb) { 73 | File.exists(id, errTo(cb, function() { 74 | File.readMeta(id, errTo(cb, function(meta) { 75 | cb(null, new File(meta, id)); 76 | })); 77 | })); 78 | }; 79 | 80 | File.dir = path.join(__dirname, '/../', config.filesDir); 81 | 82 | debug('filesDir', File.dir); 83 | 84 | module.exports = File; 85 | -------------------------------------------------------------------------------- /chapter11/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-uploading-service", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "unit-tests": "mocha --reporter=spec test/unit", 8 | "functional-tests": "mocha --reporter=spec --timeout=10000 --slow=2000 test/functional", 9 | "coverage": "node node_modules/istanbul/lib/cli.js cover node_modules/.bin/_mocha test/* -- --reporter=spec", 10 | "lint": "eslint . -c eslint.json", 11 | "test": "npm run unit-tests && npm run functional-tests" 12 | }, 13 | "dependencies": { 14 | "express": "~4.2.0", 15 | "static-favicon": "~1.0.0", 16 | "morgan": "~1.0.0", 17 | "cookie-parser": "~1.0.1", 18 | "body-parser": "~1.0.0", 19 | "debug": "~0.7.4", 20 | "ejs": "~0.8.5", 21 | "connect-multiparty": "~1.0.5", 22 | "cuid": "~1.2.4", 23 | "bcrypt": "~0.7.8", 24 | "basic-auth-connect": "~1.0.0", 25 | "errto": "~0.2.1", 26 | "custom-err": "0.0.2", 27 | "lodash": "~2.4.1", 28 | "csurf": "~1.2.2", 29 | "cookie-session": "~1.0.2", 30 | "secure-filters": "~1.0.5", 31 | "supertest": "~0.13.0", 32 | "async": "~0.9.0" 33 | }, 34 | "devDependencies": { 35 | "proxyquire": "~1.0.1", 36 | "should": "~4.0.4", 37 | "mocha": "~1.20.1", 38 | "sinon": "~1.10.2", 39 | "cheerio": "~0.17.0", 40 | "rimraf": "~2.2.8", 41 | "pre-commit": "0.0.8", 42 | "istanbul": "~0.2.11", 43 | "eslint": "~0.6.2", 44 | "complexity-report": "~1.0.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /chapter11/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 | } 9 | 10 | table { 11 | border: 1px solid #CCC; 12 | } 13 | 14 | table, td { 15 | padding: 5px; 16 | } 17 | 18 | th { 19 | text-align: right; 20 | border-right: 1px dotted #DEDEDE; 21 | padding-right: 5px; 22 | } 23 | -------------------------------------------------------------------------------- /chapter11/routes/files.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var basicAuth = require('basic-auth-connect'); 5 | var errTo = require('errto'); 6 | var pkg = require('../package.json'); 7 | var File = require('../models/file'); 8 | var debug = require('debug')(pkg.name + ':filesRoute'); 9 | 10 | var router = express.Router(); 11 | 12 | router.param('id', function(req, res, next, id) { 13 | File.find(id, errTo(next, function(file) { 14 | debug('file', file); 15 | 16 | // populate req.file, will need it later 17 | req.file = file; 18 | 19 | if (file.isPasswordProtected()) { 20 | // Password protected file, check for password using HTTP basic auth 21 | basicAuth(function(user, pwd, fn) { 22 | if (!pwd) { return fn(); } 23 | 24 | // ignore user 25 | file.authenticate(pwd, errTo(next, function(match) { 26 | if (match) { 27 | return fn(null, file.id); 28 | } 29 | 30 | fn(); 31 | })); 32 | })(req, res, next); 33 | } else { 34 | // Not password protected, proceeed normally 35 | next(); 36 | } 37 | })); 38 | }); 39 | 40 | router.get('/', function(req, res, next) { 41 | res.render('files/new', { title: 'Upload file' }); 42 | }); 43 | 44 | router.get('/:id.html', function(req, res, next) { 45 | res.render('files/show', { 46 | id: req.params.id, 47 | meta: req.file.meta, 48 | isPasswordProtected: req.file.isPasswordProtected(), 49 | title: 'Download file ' + req.file.meta.name 50 | }); 51 | }); 52 | 53 | router.get('/download/:id', function(req, res, next) { 54 | res.download(req.file.path, req.file.meta.name); 55 | }); 56 | 57 | router.post('/', function(req, res, next) { 58 | var tempFile = req.files.file; 59 | if (!tempFile.size) { return res.redirect('/files'); } 60 | 61 | var file = new File(tempFile); 62 | 63 | file.save(tempFile.path, req.body.password, errTo(next, function() { 64 | res.redirect('/files/' + file.id + '.html'); 65 | })); 66 | }); 67 | 68 | module.exports = router; 69 | -------------------------------------------------------------------------------- /chapter11/routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var express = require('express'); 4 | var router = express.Router(); 5 | 6 | /* home page. */ 7 | router.get('/', function(req, res) { 8 | res.render('index', { title: 'File upload service' }); 9 | }); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /chapter11/test/functional/files-routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var request = require('supertest'); 5 | var should = require('should'); 6 | var async = require('async'); 7 | var cheerio = require('cheerio'); 8 | var rimraf = require('rimraf'); 9 | var app = require('../../app'); 10 | 11 | function uploadFile(agent, password, done) { 12 | agent 13 | .get('/files') 14 | .expect(200) 15 | .end(function(err, res) { 16 | (err == null).should.be.true; 17 | 18 | var $ = cheerio.load(res.text); 19 | var csrfToken = $('form input[name=_csrf]').val(); 20 | 21 | csrfToken.should.not.be.empty; 22 | 23 | var req = agent 24 | .post('/files') 25 | .field('_csrf', csrfToken) 26 | .attach('file', __filename); 27 | 28 | if (password) { 29 | req = req.field('password', password); 30 | } 31 | 32 | req 33 | .expect(302) 34 | .expect('Location', /files\/(.*)\.html/) 35 | .end(function(err, res) { 36 | (err == null).should.be.true; 37 | 38 | var fileUid = res.headers['location'].match(/files\/(.*)\.html/)[1]; 39 | 40 | done(null, fileUid); 41 | }); 42 | }); 43 | } 44 | 45 | describe('Files-Routes', function(done) { 46 | after(function() { 47 | var filesDir = __dirname + '/../../files'; 48 | rimraf.sync(filesDir); 49 | fs.mkdirSync(filesDir); 50 | }); 51 | 52 | describe("Uploading a file", function() { 53 | it("should upload a file without password protecting it", function(done) { 54 | var agent = request.agent(app); 55 | 56 | uploadFile(agent, null, done); 57 | }); 58 | 59 | it("should upload a file and password protect it", function(done) { 60 | var agent = request.agent(app); 61 | var pwd = 'sample-password'; 62 | 63 | uploadFile(agent, pwd, function(err, filename) { 64 | async.parallel([ 65 | function getWithoutPwd(next) { 66 | agent 67 | .get('/files/' + filename + '.html') 68 | .expect(401) 69 | .end(function(err, res) { 70 | (err == null).should.be.true; 71 | next(); 72 | }); 73 | }, 74 | function getWithPwd(next) { 75 | agent 76 | .get('/files/' + filename + '.html') 77 | .set('Authorization', 'Basic ' + new Buffer(':' + pwd).toString('base64')) 78 | .expect(200) 79 | .end(function(err, res) { 80 | (err == null).should.be.true; 81 | next(); 82 | }); 83 | } 84 | ], function(err) { 85 | (err == null).should.be.true; 86 | done(); 87 | }); 88 | }); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /chapter11/views/error.html: -------------------------------------------------------------------------------- 1 |

    <%- message %>

    2 | 3 |
    <%- error.stack || '' %>
    4 | -------------------------------------------------------------------------------- /chapter11/views/files/new.html: -------------------------------------------------------------------------------- 1 | <%- include ../layout/header.html %> 2 | 3 |
    4 |
    5 | 6 | 7 |
    8 | 9 |
    10 | 11 | 12 |
    13 | 14 |
    15 | <%- csrf() %> 16 | 17 |
    18 |
    19 | 20 | <%- include ../layout/footer.html %> 21 | -------------------------------------------------------------------------------- /chapter11/views/files/show.html: -------------------------------------------------------------------------------- 1 | <%- include ../layout/header.html %> 2 | 3 |

    4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
    Name<%= meta.name %>
    Type<%= meta.type %>
    Size<%= meta.size %> bytes
    Uploaded at<%= meta.uploadedAt %>
    19 |

    20 | 21 |

    22 | Download file | 23 | Upload new file 24 |

    25 | 26 |

    27 | To share this file with your friends use the current link. 28 | <% if (isPasswordProtected) { %> 29 |
    30 | Don't forget to tell them the file password as well! 31 | <% } %> 32 |

    33 | 34 | <%- include ../layout/footer.html %> 35 | -------------------------------------------------------------------------------- /chapter11/views/index.html: -------------------------------------------------------------------------------- 1 | <%- include layout/header.html %> 2 | 3 | Upload file! 4 | 5 | <%- include layout/footer.html %> 6 | -------------------------------------------------------------------------------- /chapter11/views/layout/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /chapter11/views/layout/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= locals.title || '' %> 5 | 6 | 7 | 8 | <% if (locals.title) { %>

    <%= title %>

    <% } %> 9 | --------------------------------------------------------------------------------