├── README.md ├── .gitignore ├── routes ├── index.js └── events.js ├── package.json ├── server.js └── models └── analyticEvent.js /README.md: -------------------------------------------------------------------------------- 1 | # Example Hapi.js & MongoDB Example -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | bower_components 4 | build 5 | test/coverage 6 | config/env.js 7 | newrelic_agent.log 8 | coverage.html 9 | dist 10 | temp 11 | coverage.html -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Add your other routes below. 3 | * Each model might have a file that declares its 4 | * routes, such as the Events model. 5 | * 6 | * @param server 7 | */ 8 | exports.init = function(server) { 9 | console.log('Loading routes'); 10 | 11 | require('./events')(server); 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "analytics-engine", 3 | "version": "0.0.1", 4 | "description": "A simple example of how to build a very rudamentary API with NodeJS and MongoDB", 5 | "main": "server.js", 6 | "dependencies": { 7 | "hapi": "3.0.0", 8 | "mongoose": "~3.8.7", 9 | "boom": "^2.2.2", 10 | "joi": "^2.8.0" 11 | }, 12 | "keywords": [ 13 | "hapi", 14 | "mongo", 15 | "mongoose", 16 | "analytics" 17 | ], 18 | "author": "Donn Felker", 19 | "license": "MIT" 20 | } 21 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 2 | var Hapi = require('hapi'); 3 | var server = new Hapi.Server('localhost', 8000); 4 | var routes = require('./routes'); 5 | var Mongoose = require('mongoose'); 6 | 7 | // MongoDB Connection 8 | Mongoose.connect('mongodb://localhost/felkerlytics'); 9 | 10 | var rootHandler = function(request, reply) { 11 | reply({ message: "Hello from Felkerlytics!"}); 12 | }; 13 | 14 | // Set root route 15 | server.route({ 16 | method: 'GET', 17 | path: '/', 18 | handler: rootHandler 19 | }); 20 | 21 | routes.init(server); 22 | 23 | server.start(function () { 24 | console.log('Server started at: ' + server.info.uri); 25 | }); 26 | -------------------------------------------------------------------------------- /models/analyticEvent.js: -------------------------------------------------------------------------------- 1 | var Mongoose = require('mongoose'); 2 | var Schema = Mongoose.Schema; 3 | 4 | // The data schema for an event that we're tracking in our analytics engine 5 | var analyticEventSchema = new Schema({ 6 | category : { type: String, required: true, trim: true }, 7 | action : { type: String, required: true, trim: true }, 8 | label : { type: String, trim: true }, 9 | source : { type: String, required: true,trim: true }, // Usually the IP address 10 | dateCreated : { type: Date, required: true, default: Date.now } 11 | }); 12 | 13 | var analyticEvent = Mongoose.model('analytic_event', analyticEventSchema); 14 | 15 | module.exports = { 16 | AnalyticEvent: analyticEvent 17 | }; 18 | -------------------------------------------------------------------------------- /routes/events.js: -------------------------------------------------------------------------------- 1 | var Boom = require('boom'); // HTTP Errors 2 | var Joi = require('joi'); // Validation 3 | var AnalyticEvent = require('../models/analyticEvent').AnalyticEvent; // Mongoose ODM 4 | 5 | // Exports = exports? Huh? Read: http://stackoverflow.com/a/7142924/5210 6 | module.exports = exports = function (server) { 7 | 8 | console.log('Loading events routes'); 9 | exports.index(server); 10 | exports.create(server); 11 | exports.show(server); 12 | exports.remove(server); 13 | }; 14 | 15 | /** 16 | * GET /events 17 | * Gets all the events from MongoDb and returns them. 18 | * 19 | * @param server - The Hapi Server 20 | */ 21 | exports.index = function (server) { 22 | // GET /events 23 | server.route({ 24 | method: 'GET', 25 | path: '/events', 26 | handler: function (request, reply) { 27 | AnalyticEvent.find({}, function (err, events) { 28 | if (!err) { 29 | reply(events); 30 | } else { 31 | reply(Boom.badImplementation(err)); // 500 error 32 | } 33 | }); 34 | } 35 | }); 36 | }; 37 | 38 | /** 39 | * POST /new 40 | * Creates a new event in the datastore. 41 | * 42 | * @param server - The Hapi Serve 43 | */ 44 | exports.create = function (server) { 45 | // POST /events 46 | var event; 47 | 48 | server.route({ 49 | method: 'POST', 50 | path: '/events', 51 | handler: function (request, reply) { 52 | 53 | event = new AnalyticEvent(); 54 | event.category = request.payload.category; 55 | event.action = request.payload.action; 56 | event.label = request.payload.label; 57 | event.source = request.info.remoteAddress; 58 | 59 | event.save(function (err) { 60 | if (!err) { 61 | reply(event).created('/events/' + event._id); // HTTP 201 62 | } else { 63 | reply(Boom.forbidden(getErrorMessageFrom(err))); // HTTP 403 64 | } 65 | }); 66 | } 67 | }); 68 | }; 69 | 70 | /** 71 | * GET /events/{id} 72 | * Gets the event based upon the {id} parameter. 73 | * 74 | * @param server 75 | */ 76 | exports.show = function (server) { 77 | 78 | server.route({ 79 | method: 'GET', 80 | path: '/events/{id}', 81 | config: { 82 | validate: { 83 | path: { 84 | id: Joi.string().alphanum().min(5).required() 85 | } 86 | } 87 | }, 88 | handler: function (request, reply) { 89 | AnalyticEvent.findById(request.params.id, function (err, event) { 90 | if (!err && event) { 91 | reply(event); 92 | } else if (err) { 93 | // Log it, but don't show the user, don't want to expose ourselves (think security) 94 | console.log(err); 95 | reply(Boom.notFound()); 96 | } else { 97 | 98 | reply(Boom.notFound()); 99 | } 100 | }); 101 | } 102 | }) 103 | }; 104 | 105 | /** 106 | * DELETE /events/{id} 107 | * Deletes an event, based on the event id in the path. 108 | * 109 | * @param server - The Hapi Server 110 | */ 111 | exports.remove = function (server) { 112 | server.route({ 113 | method: 'DELETE', 114 | path: '/events/{id}', 115 | config: { 116 | validate: { 117 | path: { 118 | id: Joi.string().alphanum().min(5).required() 119 | } 120 | } 121 | }, 122 | handler: function (request, reply) { 123 | AnalyticEvent.findById(request.params.id, function(err, event) { 124 | if(!err && event) { 125 | event.remove(); 126 | reply({ message: "Event deleted successfully"}); 127 | } else if(!err) { 128 | // Couldn't find the object. 129 | reply(Boom.notFound()); 130 | } else { 131 | console.log(err); 132 | reply(Boom.badRequest("Could not delete Event")); 133 | } 134 | }); 135 | } 136 | }) 137 | }; 138 | 139 | /** 140 | * Formats an error message that is returned from Mongoose. 141 | * 142 | * @param err The error object 143 | * @returns {string} The error message string. 144 | */ 145 | function getErrorMessageFrom(err) { 146 | var errorMessage = ''; 147 | 148 | if (err.errors) { 149 | for (var prop in err.errors) { 150 | if(err.errors.hasOwnProperty(prop)) { 151 | errorMessage += err.errors[prop].message + ' ' 152 | } 153 | } 154 | 155 | } else { 156 | errorMessage = err.message; 157 | } 158 | 159 | return errorMessage; 160 | } 161 | --------------------------------------------------------------------------------