├── index.js ├── lib ├── index.js └── platform.js ├── .gitignore ├── package.json ├── sample-apps └── auto-route-binding.js ├── LICENSE └── README.md /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = exports = require('./lib') 3 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var Platform = require('./platform'); 4 | 5 | function createPlatform() { 6 | var platform = new Platform(); 7 | return platform; 8 | } 9 | 10 | module.exports = exports = createPlatform; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directory 25 | # Deployed apps should consider commenting this line out: 26 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | temp 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "facet-platform", 3 | "version": "0.1.0", 4 | "description": "Orchestration of facet modules for rapid API development", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --require test/support/env --reporter spec --check-leaks test/" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/facet/platform.git" 12 | }, 13 | "keywords": [ 14 | "facet", 15 | "facet-platform" 16 | ], 17 | "author": "Alexander Manea, Ilya Shindyapin", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/facet/platform/issues" 21 | }, 22 | "homepage": "https://facet.github.io/platform", 23 | "dependencies": { 24 | "facet-intercom": "^0.1.0", 25 | "facet-response-handler": "https://github.com/facet/response-handler/tarball/master", 26 | "mongoose": "^3.8.17", 27 | "underscore": "^1.6.0" 28 | }, 29 | "devDependencies": { 30 | "body-parser": "^1.9.2", 31 | "chai": "^1.9.2", 32 | "express": "^4.10.1", 33 | "mocha": "^1.21.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample-apps/auto-route-binding.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | app = express(), 3 | http = require('http'), 4 | bodyParser = require('body-parser'), 5 | facet = require('facet-platform')(); 6 | 7 | // set up facet modules 8 | facet 9 | .useModules({ 10 | 'gatekeeper': require('facet-gatekeeper'), 11 | 'commerce': require('facet-commerce') 12 | }) 13 | .setModuleOptions({dbServer: 'mongodb://localhost:27017'}) 14 | .init(app); 15 | 16 | app.use(bodyParser.json()); 17 | app.set('port', process.env.PORT || 9393); 18 | 19 | // route handlers 20 | // users/groups/auth 21 | app.use( '/api/v1', facet.getModule('gatekeeper').bindRoutes( express.Router(), { 22 | routes: [{ 23 | routeBase: '/users', 24 | resourceReference: 'Users' 25 | }, 26 | { 27 | routeBase: '/groups', 28 | resourceReference: 'Groups' 29 | }, 30 | { 31 | routeBase: '/auth', 32 | resourceReference: 'Auth' 33 | }] 34 | })); 35 | 36 | http.createServer(app).listen(app.get('port'), function(){ 37 | console.log('Express server listening on port ' + app.get('port')); 38 | }); 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 facet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /lib/platform.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var _ = require('underscore'), 4 | env = process.env.NODE_ENV || 'development', 5 | http = require('http'), 6 | Intercom = require('facet-intercom'), 7 | ResponseHandler = require('facet-response-handler'), 8 | Core = require('facet-core').Core; 9 | 10 | 11 | var Platform = function() { 12 | // registry for storing uninstantiated api module classes 13 | this._apiClasses = {}; 14 | 15 | // registry for storing api module instances 16 | this._apiInstances = {}; 17 | 18 | this._moduleOptions = { 19 | // currently used to disbale mongoose's index checking at startup 20 | environment: process.env.NODE_ENV || 'development', 21 | 22 | // instance of EventEmitter2 used for inter/intra module communication 23 | intercom: new Intercom(), 24 | 25 | // allow toggling api user access checking for CRUD funcs 26 | doAccessCheck: false, 27 | 28 | // defines the middleware function format to use for auto route binding 29 | middlewareType: 'connect' 30 | 31 | // supported values are basic & jwt, basic is default 32 | // apiAuthMethod: 'basic', 33 | 34 | // optional, used for jwt implementation 35 | // apiSecret: 'SOME_SECRET_STRING', 36 | }; 37 | }; 38 | 39 | 40 | 41 | Platform.prototype.setModuleOptions = function(options) { 42 | this._moduleOptions = _.extend(this._moduleOptions, options); 43 | 44 | // ensure a db key exists 45 | if( !this._moduleOptions.db ) { 46 | var mongoose = require('mongoose'); 47 | mongoose.connect( options.dbServer, { server: { socketOptions: { keepAlive: 1 } } }); 48 | mongoose.connection.on( 'error', console.error.bind( console, 'connection error:' ) ); 49 | this._moduleOptions.db = mongoose; 50 | } 51 | 52 | return this; 53 | }; 54 | 55 | 56 | Platform.prototype.useModules = function(modules) { 57 | if(!modules) { 58 | throw new Error('An object containing module aliases and classes is required.'); 59 | } 60 | 61 | for(var key in modules) { 62 | this._apiClasses[key] = modules[key]; 63 | } 64 | 65 | return this; 66 | } 67 | 68 | Platform.prototype.getModuleOptions = function() { 69 | return this._moduleOptions; 70 | }; 71 | 72 | 73 | Platform.prototype.init = function(express) { 74 | // instantiate registered modules 75 | for(var key in this._apiClasses) { 76 | var apiClass = this._apiClasses[key]; 77 | this._apiInstances[key] = new apiClass(this.getModuleOptions()); 78 | } 79 | 80 | // instantiate response listener 81 | var responseHandler = new ResponseHandler(this._moduleOptions); 82 | 83 | // run the core facetInit class to emit the nodeStack object 84 | // which contains the req, res and next for the current request 85 | var first = Object.keys(this._apiInstances)[0]; 86 | 87 | express.use(this._apiInstances[first].facetInit()); 88 | }; 89 | 90 | 91 | Platform.prototype.getModule = function(module, instantiated) { 92 | if(instantiated === undefined) { 93 | instantiated = true; 94 | } 95 | 96 | return (instantiated) ? this._apiInstances[module] : this._apiClasses[module]; 97 | } 98 | 99 | module.exports = exports = Platform; 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # Facet Platform 4 | 5 | Provides extensible common utility classes for rapid JSON API development. Offers the following functionality: 6 | 7 | * Abstration of middleware specific code for framework agnostic use 8 | * Handling of request/response lifecycle 9 | * Built in CRUD functionality via find, findOne, create, update, delete functions for any resource you create 10 | * Management of event bus (aka [Intercom](https://github.com/facet/intercom)) used for decoupled module communication 11 | 12 | ## Examples 13 | 14 | #### Creating a new resource API class 15 | 16 | See the [facet core module](http://facet.github.io/core/) for details on creating API resources. 17 | 18 | ```js 19 | var ApiCore = require('facet-core').ApiCore; 20 | 21 | var TodosAPI = function(facet.moduleOptions) { 22 | // define mongoose schema and bind events here 23 | // see other facet modules for examples: 24 | // https://github.com/facet/gatekeeper 25 | // https://github.com/facet/category 26 | // https://github.com/facet/catalog 27 | }; 28 | 29 | /** 30 | * Todos API inherits from API Core which enables CRUD functionality. 31 | * The definition of new facet classes would be done in a different 32 | * module which depends on facet-core 33 | */ 34 | util.inherits(TodosAPI, ApiCore); 35 | ``` 36 | 37 | #### Setting up a JSON API server using express 4 38 | 39 | ```js 40 | var facet = require('facet-platform')(), 41 | app = require('express')(); 42 | 43 | // set up facet modules 44 | facet 45 | .useModules({ 46 | 'todo': require('./path-to-todos') 47 | }) 48 | .setModuleOptions({dbServer: 'mongodb://localhost:27017'}) 49 | .init(app); 50 | 51 | app.use(bodyParser.json()); 52 | app.set('port', process.env.PORT || 9393); 53 | 54 | // auto route binding for CRUD routes: 55 | // GET /todos 56 | // GET /todos/:id 57 | // POST /todos 58 | // PUT /todos/:id 59 | // DELETE /todos/:id 60 | // Advanced route binding across different domains is 61 | // possible as well. See The facet-commerce for an example. 62 | app.use( '/api/v1', facet.getModule('todo').bindRoutes( express.Router(), { 63 | routeBase: '/todos' 64 | })); 65 | 66 | http.createServer(app).listen(8888, function(){ 67 | console.log('Express server listening on port 8888'); 68 | }); 69 | ``` 70 | 71 | Also checkout out the [sample app](https://github.com/facet/platform/blob/master/sample-apps/auto-route-binding.js). 72 | 73 | 74 | #### Using CRUD functions directly or in custom implementations 75 | 76 | ```js 77 | var facet = require('facet-platform'); 78 | 79 | // create a todo 80 | var importantTodo = { 81 | author: 'Action Bronson', 82 | task: 'Kick back' 83 | } 84 | 85 | todosAPI.create(importantTodo) 86 | .then(function(data) { 87 | console.log('created task: ', data); 88 | }, 89 | function(err) { 90 | console.log('booo: ', err); 91 | }) 92 | .end(); 93 | 94 | // query.conditions, query.fields, and query.options 95 | // are regular mongoose queries 96 | var findQuery = { 97 | conditions: {task: 'Kick back'}, 98 | fields: '', 99 | options: { 100 | lean: true 101 | } 102 | } 103 | 104 | // TodosAPI.find() is a wrapper for mongoose's find(), same 105 | // with findOne(), create(), remove() and update() 106 | 107 | TodosAPI.find(query, successCb, errorCb); 108 | 109 | // or via promises 110 | 111 | TodosAPI.find(query) 112 | .then(function(data) { 113 | console.log('success! ', data); 114 | }, 115 | function(err) { 116 | console.log('booo: ', err); 117 | }) 118 | .end(); 119 | ``` 120 | 121 | ## Coming Soon... 122 | 123 | * actual documentation and example apps 124 | * multitenancy support w/ multiple apps per tenant 125 | * error handling base class 126 | * logging functionlality 127 | --------------------------------------------------------------------------------