├── .editorconfig ├── .gitattributes ├── .gitignore ├── .idea ├── encodings.xml ├── generator-node-restify-mongodb.iml ├── misc.xml └── modules.xml ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── eslint.json ├── generators └── app │ ├── index.js │ └── templates │ ├── .editorconfig │ ├── .gitignore │ ├── .jshintrc │ ├── .npmignore │ ├── Gruntfile.js │ ├── app.js │ ├── app │ ├── models │ │ ├── index.js │ │ └── widget.js │ └── routes │ │ ├── index.js │ │ ├── utilities.js │ │ └── widget.js │ ├── config │ └── config.js │ ├── data │ └── widgets.json │ ├── db-connection.js │ ├── log.js │ ├── package.json │ ├── postman │ ├── Widgets.postman_collection.json │ └── localhost-restify.postman_environment.json │ └── spec │ ├── utils_spec.js │ └── widget_spec.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── preview.png └── test └── app.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | ### Node template 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | ### JetBrains template 40 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 41 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 42 | 43 | # User-specific stuff: 44 | .idea/workspace.xml 45 | .idea/tasks.xml 46 | .idea/dictionaries 47 | .idea/vcs.xml 48 | .idea/jsLibraryMappings.xml 49 | 50 | # Sensitive or high-churn files: 51 | .idea/dataSources.ids 52 | .idea/dataSources.xml 53 | .idea/dataSources.local.xml 54 | .idea/sqlDataSources.xml 55 | .idea/dynamic.xml 56 | .idea/uiDesigner.xml 57 | 58 | # Gradle: 59 | .idea/gradle.xml 60 | .idea/libraries 61 | 62 | # Mongo Explorer plugin: 63 | .idea/mongoSettings.xml 64 | 65 | ## File-based project format: 66 | *.iws 67 | 68 | ## Plugin-specific files: 69 | 70 | # IntelliJ 71 | /out/ 72 | 73 | # mpeltonen/sbt-idea plugin 74 | .idea_modules/ 75 | 76 | # JIRA plugin 77 | atlassian-ide-plugin.xml 78 | 79 | # Crashlytics plugin (for Android Studio and IntelliJ) 80 | com_crashlytics_export_strings.xml 81 | crashlytics.properties 82 | crashlytics-build.properties 83 | fabric.properties 84 | ### Node template 85 | # Logs 86 | logs 87 | *.log 88 | npm-debug.log* 89 | 90 | # Runtime data 91 | pids 92 | *.pid 93 | *.seed 94 | 95 | # Directory for instrumented libs generated by jscoverage/JSCover 96 | lib-cov 97 | 98 | # Coverage directory used by tools like istanbul 99 | coverage 100 | 101 | # nyc test coverage 102 | .nyc_output 103 | 104 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 105 | .grunt 106 | 107 | # node-waf configuration 108 | .lock-wscript 109 | 110 | # Compiled binary addons (http://nodejs.org/api/addons.html) 111 | build/Release 112 | 113 | # Dependency directories 114 | node_modules 115 | jspm_packages 116 | 117 | # Optional npm cache directory 118 | .npm 119 | 120 | # Optional REPL history 121 | .node_repl_history 122 | ### JetBrains template 123 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 124 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 125 | 126 | # User-specific stuff: 127 | .idea/workspace.xml 128 | .idea/tasks.xml 129 | .idea/dictionaries 130 | .idea/vcs.xml 131 | .idea/jsLibraryMappings.xml 132 | 133 | # Sensitive or high-churn files: 134 | .idea/dataSources.ids 135 | .idea/dataSources.xml 136 | .idea/dataSources.local.xml 137 | .idea/sqlDataSources.xml 138 | .idea/dynamic.xml 139 | .idea/uiDesigner.xml 140 | 141 | # Gradle: 142 | .idea/gradle.xml 143 | .idea/libraries 144 | 145 | # Mongo Explorer plugin: 146 | .idea/mongoSettings.xml 147 | 148 | ## File-based project format: 149 | *.iws 150 | 151 | ## Plugin-specific files: 152 | 153 | # IntelliJ 154 | /out/ 155 | 156 | # mpeltonen/sbt-idea plugin 157 | .idea_modules/ 158 | 159 | # JIRA plugin 160 | atlassian-ide-plugin.xml 161 | 162 | # Crashlytics plugin (for Android Studio and IntelliJ) 163 | com_crashlytics_export_strings.xml 164 | crashlytics.properties 165 | crashlytics-build.properties 166 | fabric.properties 167 | 168 | 169 | **.DS_Store 170 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/generator-node-restify-mongodb.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | /usr/local/bin/bower 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'node' 4 | - '8' 5 | - '7' 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Gary A. Stafford 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-node-restify-mongodb 2 | 3 | [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url] 4 | 5 | ## Introduction 6 | 7 | __Project updated November, 2017 (v0.3.0), to Restify 6.x from 4.x.__ 8 | 9 | This Yeoman generator scaffolds a basic RESTful CRUD API service, a Node application, based on [Node.js](https://nodejs.org), [Restify](http://restify.com), and [MongoDB](https://www.mongodb.com). 10 | 11 | According to their website, Restify, used most notably by [Netflix](http://techblog.netflix.com/2014/11/nodejs-in-flames.html), borrows heavily from [Express](http://expressjs.com). However, while Express is targeted at browser applications, with templating and rendering, Restify is keenly focused on building API services that are maintainable and observable. 12 | 13 | Along with Node, Restify, and MongoDB, the project also implements the following packages: [Bunyan](https://github.com/trentm/node-bunyan) (includes [DTrace](http://dtrace.org/blogs/about/) support), [Jasmine](https://github.com/mhevery/jasmine-node) (using [jasmine-node](https://github.com/mhevery/jasmine-node)), [Mongoose](http://mongoosejs.com/index.html), and [Grunt](http://gruntjs.com). 14 | 15 | Portions of this project's file structure and code are derived from what I consider the best parts of several different projects, including [generator-express](https://github.com/expressjs/generator), [generator-restify-mongo](https://github.com/lawrence-yu/generator-restify-mongo), and [generator-restify](https://github.com/chris-l/generator-restify). 16 | 17 | ## Installation 18 | 19 | To begin, install install [Yeoman](http://yeoman.io) and generator-node-restify-mongodb using [npm](https://www.npmjs.com/), using npm. The generator assumes you have pre-installed Node and MongoDB. 20 | 21 | ```shell 22 | npm install -g yo 23 | npm install -g generator-node-restify-mongodb 24 | ``` 25 | 26 | Then, generate the new project. 27 | 28 | ```bash 29 | mkdir node-restify-mongodb 30 | cd $_ 31 | yo node-restify-mongodb 32 | ``` 33 | 34 | Yeoman scaffolds the application, creating the directory structure, copying required files, and running 'npm install' to load the npm package dependencies. 35 | 36 | ![Scaffolding Project](preview.png) 37 | 38 | ## Using the Generated Application 39 | 40 | Next, import the supplied set of sample widget documents into the local development instance of MongoDB from the supplied 'data/widgets.json' file. 41 | 42 | ```bash 43 | NODE_ENV=development grunt mongoimport --verbose 44 | ``` 45 | 46 | Similar to Yeoman's Express Generator, this application contains configuration for three typical environments: 'Development' (default), 'Test', and 'Production'. If you want to import the sample widget documents into your Test or Production instances of MongoDB, first change the 'NODE_ENV' environment variable value. 47 | 48 | ```bash 49 | NODE_ENV=production grunt mongoimport --verbose 50 | ``` 51 | 52 | To start the application in a new terminal window, use the following command. 53 | 54 | ```bash 55 | npm start 56 | ``` 57 | 58 | To test the application, using jshint and the jasmine-node module, the sample documents must be imported into MongoDB and the application must be running (see above). To test the application, open a separate terminal window, and use the following command. 59 | 60 | ```bash 61 | npm test 62 | ``` 63 | 64 | The project contains a set of jasmine-node tests, split between the '/widgets' and the '/utils' endpoints. If the application is running correctly, you should see the test results displayed in the terminal window. 65 | 66 | Similarly, the following command displays a code coverage report, using the grunt, mocha, istanbul, and grunt-mocha-istanbul node modules. 67 | 68 | ```bash 69 | grunt coverage 70 | ``` 71 | 72 | Grunt uses the grunt-mocha-istanbul module to execute the same set of jasmine-node tests. Based on those tests, the application's code coverage (statement, line, function, and branch coverage) is displayed. 73 | 74 | You may test the running application, directly, by cURLing the '/widgets' endpoints. 75 | 76 | ```bash 77 | curl -X GET -H "Accept: application/json" "http://localhost:3000/widgets" 78 | ``` 79 | 80 | For more legible output, try [HTTPie](https://httpie.org/) or [prettyjson](https://www.npmjs.com/package/prettyjson). 81 | 82 | ```bash 83 | brew install httpie 84 | http http://localhost:3000/widgets 85 | http http://localhost:3000/widgets/SVHXPAWEOD 86 | ``` 87 | 88 | ```bash 89 | npm install -g prettyjson 90 | curl -X GET -H "Accept: application/json" "http://localhost:3000/widgets" --silent | prettyjson 91 | curl -X GET -H "Accept: application/json" "http://localhost:3000/widgets/SVHXPAWEOD" --silent | prettyjson 92 | ``` 93 | 94 | A much better RESTful API testing solution is [Postman](https://www.getpostman.com/). Postman provides the ability to individually configure each environment and abstract that environment-specific configuration, such as host and port, from the actual HTTP requests. 95 | 96 | ## API REST Endpoints 97 | 98 | The scaffolded application includes the following endpoints. 99 | 100 | ```javascript 101 | # widget resources 102 | var PATH = '/widgets'; 103 | server.get({path: PATH, version: VERSION}, findDocuments); 104 | server.get({path: PATH + '/:product_id', version: VERSION}, findOneDocument); 105 | server.post({path: PATH, version: VERSION}, createDocument); 106 | server.put({path: PATH, version: VERSION}, updateDocument); 107 | server.del({path: PATH + '/:product_id', version: VERSION}, deleteDocument); 108 | 109 | # utility resources 110 | var PATH = '/utils'; 111 | server.get({path: PATH + '/ping', version: VERSION}, ping); 112 | server.get({path: PATH + '/health', version: VERSION}, health); 113 | server.get({path: PATH + '/info', version: VERSION}, information); 114 | server.get({path: PATH + '/config', version: VERSION}, configuraton); 115 | server.get({path: PATH + '/env', version: VERSION}, environment); 116 | ``` 117 | 118 | ## Widget 119 | 120 | The Widget is the basic document object used throughout the application. It is used, primarily, to demonstrate Mongoose's [Model](http://mongoosejs.com/docs/models.html) and [Schema](http://mongoosejs.com/docs/guide.html). The Widget object contains the following fields, as shown in the sample widget, below. 121 | 122 | ```json 123 | { 124 | "product_id": "4OZNPBMIDR", 125 | "name": "Fapster", 126 | "color": "Orange", 127 | "size": "Medium", 128 | "price": "29.99", 129 | "inventory": 5 130 | } 131 | ``` 132 | 133 | ## MongoDB 134 | 135 | Use the [mongo shell](https://docs.mongodb.com/manual/mongo/) to access the application's MongoDB instance and display the imported sample documents. 136 | 137 | ```text 138 | mongo 139 | > show dbs 140 | > use node-restify-mongodb-development 141 | > show tables 142 | > db.widgets.find() 143 | { "_id" : ObjectId("574cf9bb0f515d7c67a87026"), "product_id" : "4OZNPBMIDR", "name" : "Fapster", "color" : "Orange", "size" : "Medium", "price" : "29.99", "inventory" : 5 } 144 | { "_id" : ObjectId("574cf9bb0f515d7c67a87027"), "product_id" : "SVHXPAWEOD", "name" : "Voonex", "color" : "Green", "size" : "Medium", "price" : "$10.99", "inventory" : 50 } 145 | { "_id" : ObjectId("574cf9bb0f515d7c67a87028"), "product_id" : "3YIRGZ6TDW", "name" : "Groopster", "color" : "Yellow", "size" : "Large", "price" : "$99.95", "inventory" : 100 } 146 | { "_id" : ObjectId("574cf9bb0f515d7c67a87029"), "product_id" : "6T2HC5MIZ9", "name" : "Chaintwist", "color" : "Purple", "size" : "Tiny", "price" : "$99.95", "inventory" : 15 } 147 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702a"), "product_id" : "ERZ1RMJFR3", "name" : "Glozzom", "color" : "Red", "size" : "Huge", "price" : "$199.98", "inventory" : 35 } 148 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702b"), "product_id" : "N43WV5234S", "name" : "Zapster", "color" : "Green", "size" : "Tiny", "price" : "$17.49", "inventory" : 65 } 149 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702c"), "product_id" : "0BVCLPDZ42", "name" : "Chaintwist", "color" : "Blue", "size" : "Medium", "price" : "$89.95", "inventory" : 55 } 150 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702d"), "product_id" : "N212QZOD9B", "name" : "Pentwist", "color" : "Yellow", "size" : "Huge", "price" : "$159.98", "inventory" : 95 } 151 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702e"), "product_id" : "RTHGP1FCGN", "name" : "Reflupper", "color" : "Red", "size" : "Large", "price" : "$12.95", "inventory" : 25 } 152 | { "_id" : ObjectId("574cf9bb0f515d7c67a8702f"), "product_id" : "GKO1SFX04M", "name" : "Jukelox", "color" : "Blue", "size" : "Small", "price" : "$25.49", "inventory" : 75 } 153 | ``` 154 | 155 | ## Environmental Variables 156 | 157 | The scaffolded application relies on several environment variables to determine its environment-specific runtime configuration. If these environment variables are present, the application defaults to using the Development environment values, as shown below, in the application's 'config/config.js' file. 158 | 159 | ```javascript 160 | var NODE_ENV = process.env.NODE_ENV || 'development'; 161 | var NODE_HOST = process.env.NODE_HOST || '127.0.0.1'; 162 | var NODE_PORT = process.env.NODE_PORT || 3000; 163 | var MONGO_HOST = process.env.MONGO_HOST || '127.0.0.1'; 164 | var MONGO_PORT = process.env.MONGO_PORT || 27017; 165 | var LOG_LEVEL = process.env.LOG_LEVEL || 'info'; 166 | var APP_NAME = 'node-restify-mongodb-'; 167 | ``` 168 | 169 | ## Future Project TODOs 170 | 171 | Future project enhancements include the following: 172 | 173 | - Add filtering, sorting, field selection and paging 174 | - Add basic HATEOAS-based response features 175 | - Add authentication and authorization to production MongoDB instance 176 | - Convert from out-dated jasmine-node to Jasmine? 177 | 178 | ## License 179 | 180 | MIT © [Gary A. Stafford](http://www.programmaticponderings.com) 181 | 182 | [coveralls-image]: https://coveralls.io/repos/garystafford/generator-node-restify-mongodb/badge.svg 183 | [coveralls-url]: https://coveralls.io/r/garystafford/generator-node-restify-mongodb 184 | [daviddm-image]: https://david-dm.org/garystafford/generator-node-restify-mongodb.svg?theme=shields.io 185 | [daviddm-url]: https://david-dm.org/garystafford/generator-node-restify-mongodb 186 | [npm-image]: https://badge.fury.io/js/generator-node-restify-mongodb.svg 187 | [npm-url]: https://npmjs.org/package/generator-node-restify-mongodb 188 | [travis-image]: https://travis-ci.org/garystafford/generator-node-restify-mongodb.svg?branch=master 189 | [travis-url]: https://travis-ci.org/garystafford/generator-node-restify-mongodb 190 | -------------------------------------------------------------------------------- /eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "eqeqeq": "off", 4 | "curly": "warn", 5 | "quotes": ["warn", "double"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Generator = require('yeoman-generator'); 3 | const chalk = require('chalk'); 4 | const yosay = require('yosay'); 5 | const mkdirp = require('mkdirp'); 6 | 7 | module.exports = class extends Generator { 8 | prompting() { 9 | // Have Yeoman greet the user. 10 | this.log(yosay( 11 | 'Welcome to the ' + chalk.red('Node-Restify-MongoDB v0.3.0') + ' Generator!' 12 | )); 13 | } 14 | 15 | install() { 16 | this.installDependencies({ 17 | bower: false, 18 | npm: true 19 | }).then(() => console.log('npm dependencies installed!')) 20 | } 21 | 22 | app() { 23 | this.fs.copy(this.templatePath('app.js'), this.destinationPath('app.js')); 24 | mkdirp('app/models'); 25 | mkdirp('app/routes'); 26 | this.fs.copy(this.templatePath('app/models/index.js'), this.destinationPath('app/models/index.js')); 27 | this.fs.copy(this.templatePath('app/models/widget.js'), this.destinationPath('app/models/widget.js')); 28 | this.fs.copy(this.templatePath('app/routes/index.js'), this.destinationPath('app/routes/index.js')); 29 | this.fs.copy(this.templatePath('app/routes/widget.js'), this.destinationPath('app/routes/widget.js')); 30 | this.fs.copy(this.templatePath('app/routes/utilities.js'), this.destinationPath('app/routes/utilities.js')); 31 | } 32 | 33 | config() { 34 | mkdirp('config'); 35 | this.fs.copy(this.templatePath('config/config.js'), this.destinationPath('config/config.js')); 36 | } 37 | 38 | data() { 39 | mkdirp('data'); 40 | this.fs.copy(this.templatePath('data/widgets.json'), this.destinationPath('data/widgets.json')); 41 | } 42 | 43 | spec() { 44 | mkdirp('spec'); 45 | this.fs.copy(this.templatePath('spec/utils_spec.js'), this.destinationPath('spec/utils_spec.js')); 46 | this.fs.copy(this.templatePath('spec/widget_spec.js'), this.destinationPath('spec/widget_spec.js')); 47 | } 48 | 49 | postman() { 50 | mkdirp('spec'); 51 | this.fs.copy(this.templatePath('postman/localhost-restify.postman_environment.json'), 52 | this.destinationPath('postman/localhost-restify.postman_environment.json')); 53 | this.fs.copy(this.templatePath('postman/Widgets.postman_collection.json'), 54 | this.destinationPath('postman/Widgets.postman_collection.json')); 55 | } 56 | 57 | grunt() { 58 | this.fs.copy(this.templatePath('Gruntfile.js'), this.destinationPath('Gruntfile.js')); 59 | } 60 | 61 | projectfiles() { 62 | this.fs.copy(this.templatePath('package.json'), this.destinationPath('package.json')); 63 | this.fs.copy(this.templatePath('db-connection.js'), this.destinationPath('db-connection.js')); 64 | this.fs.copy(this.templatePath('log.js'), this.destinationPath('log.js')); 65 | this.fs.copy(this.templatePath('.npmignore'), this.destinationPath('.npmignore')); 66 | this.fs.copy(this.templatePath('.gitignore'), this.destinationPath('.gitignore')); 67 | this.fs.copy(this.templatePath('.jshintrc'), this.destinationPath('.jshintrc')); 68 | //npm renames .gitignore in .npmignore when publishing, 69 | //so rename back to .gitignore 70 | if (this.fs.exists(this.templatePath('.npmignore'))) { 71 | this.fs.copy(this.templatePath('.npmignore'), this.destinationPath('.gitignore')); 72 | } 73 | this.fs.copy(this.templatePath('.editorconfig'), this.destinationPath('.editorconfig')); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /generators/app/templates/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /generators/app/templates/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | # Created by .ignore support plugin (hsz.mobi) 4 | ### Node template 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | ### JetBrains template 40 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 41 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 42 | 43 | # User-specific stuff: 44 | .idea/workspace.xml 45 | .idea/tasks.xml 46 | .idea/dictionaries 47 | .idea/vcs.xml 48 | .idea/jsLibraryMappings.xml 49 | 50 | # Sensitive or high-churn files: 51 | .idea/dataSources.ids 52 | .idea/dataSources.xml 53 | .idea/dataSources.local.xml 54 | .idea/sqlDataSources.xml 55 | .idea/dynamic.xml 56 | .idea/uiDesigner.xml 57 | 58 | # Gradle: 59 | .idea/gradle.xml 60 | .idea/libraries 61 | 62 | # Mongo Explorer plugin: 63 | .idea/mongoSettings.xml 64 | 65 | ## File-based project format: 66 | *.iws 67 | 68 | ## Plugin-specific files: 69 | 70 | # IntelliJ 71 | /out/ 72 | 73 | # mpeltonen/sbt-idea plugin 74 | .idea_modules/ 75 | 76 | # JIRA plugin 77 | atlassian-ide-plugin.xml 78 | 79 | # Crashlytics plugin (for Android Studio and IntelliJ) 80 | com_crashlytics_export_strings.xml 81 | crashlytics.properties 82 | crashlytics-build.properties 83 | fabric.properties 84 | -------------------------------------------------------------------------------- /generators/app/templates/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6 3 | } 4 | -------------------------------------------------------------------------------- /generators/app/templates/.npmignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional REPL history 39 | .node_repl_history 40 | ### Yeoman template 41 | node_modules/ 42 | bower_components/ 43 | 44 | build/ 45 | dist/ 46 | ### JetBrains template 47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 48 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 49 | 50 | # User-specific stuff: 51 | .idea/workspace.xml 52 | .idea/tasks.xml 53 | .idea/dictionaries 54 | .idea/vcs.xml 55 | .idea/jsLibraryMappings.xml 56 | 57 | # Sensitive or high-churn files: 58 | .idea/dataSources.ids 59 | .idea/dataSources.xml 60 | .idea/dataSources.local.xml 61 | .idea/sqlDataSources.xml 62 | .idea/dynamic.xml 63 | .idea/uiDesigner.xml 64 | 65 | # Gradle: 66 | .idea/gradle.xml 67 | .idea/libraries 68 | 69 | # Mongo Explorer plugin: 70 | .idea/mongoSettings.xml 71 | 72 | ## File-based project format: 73 | *.iws 74 | 75 | ## Plugin-specific files: 76 | 77 | # IntelliJ 78 | /out/ 79 | 80 | # mpeltonen/sbt-idea plugin 81 | .idea_modules/ 82 | 83 | # JIRA plugin 84 | atlassian-ide-plugin.xml 85 | 86 | # Crashlytics plugin (for Android Studio and IntelliJ) 87 | com_crashlytics_export_strings.xml 88 | crashlytics.properties 89 | crashlytics-build.properties 90 | fabric.properties 91 | -------------------------------------------------------------------------------- /generators/app/templates/Gruntfile.js: -------------------------------------------------------------------------------- 1 | var config = require('./config/config'); 2 | 3 | module.exports = function (grunt) { 4 | grunt.initConfig({ 5 | mongoimport: { 6 | options: { 7 | db: config.db.name, 8 | host: config.db.host, 9 | port: config.db.port, 10 | collections: [{ 11 | name: 'widgets', 12 | file: 'data/widgets.json', 13 | jsonArray: true, 14 | drop: true 15 | }] 16 | } 17 | }, 18 | exec: { 19 | jshint_test: { 20 | cmd: 'node_modules/jshint/bin/jshint --verbose app/ config/ app.js *.js' 21 | }, 22 | jasmine_test: { 23 | cmd: 'node node_modules/jasmine-node/lib/jasmine-node/cli.js --color --verbose --captureExceptions --forceexit spec' 24 | } 25 | }, 26 | mocha_istanbul: { 27 | coverage: { 28 | src: 'spec', // a folder works nicely 29 | options: { 30 | mask: '*_spec.js' 31 | } 32 | } 33 | }, 34 | istanbul_check_coverage: { 35 | default: { 36 | options: { 37 | coverageFolder: 'coverage*', // will check both coverage folders and merge the coverage results 38 | check: { 39 | lines: 80, 40 | statements: 80 41 | } 42 | } 43 | } 44 | } 45 | }); 46 | 47 | grunt.event.on('coverage', function (lcov, done) { 48 | console.log(lcov); 49 | done(); // or done(false); in case of error 50 | }); 51 | 52 | 53 | grunt.loadNpmTasks('grunt-mongoimport'); 54 | grunt.loadNpmTasks('grunt-exec'); 55 | grunt.loadNpmTasks('grunt-mocha-istanbul'); 56 | 57 | grunt.registerTask('default', ['mongoimport', 'exec:jshint_test', 'exec:jasmine_test']); 58 | grunt.registerTask('test', ['exec:jshint_test', 'exec:jasmine_test']); 59 | grunt.registerTask('coverage', ['mocha_istanbul:coverage']); 60 | }; 61 | -------------------------------------------------------------------------------- /generators/app/templates/app.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | let restify = require('restify'); 5 | let path = require('path'); 6 | 7 | let config = require(path.join(__dirname, '/config/config')); 8 | let log = require(path.join(__dirname, '/log')); 9 | let models = require(path.join(__dirname, '/app/models/')); 10 | let routes = require(path.join(__dirname, '/app/routes/')); 11 | let dbConnection = require(path.join(__dirname, '/db-connection')); 12 | 13 | dbConnection(); 14 | 15 | let server = restify.createServer({ 16 | name: config.app.name, 17 | log: log 18 | }); 19 | 20 | server.use(restify.plugins.bodyParser()); 21 | server.use(restify.plugins.queryParser()); 22 | server.use(restify.plugins.gzipResponse()); 23 | server.pre(restify.pre.sanitizePath()); 24 | server.use( 25 | function crossOrigin(req, res, next) { 26 | res.header('Access-Control-Allow-Origin', '*'); 27 | res.header('Access-Control-Allow-Headers', 'X-Requested-With'); 28 | return next(); 29 | } 30 | ); 31 | 32 | /*jslint unparam:true*/ 33 | // Default error handler. Personalize according to your needs. 34 | server.on('uncaughtException', function (req, res, route, err) { 35 | log.info('******* Begin Error *******\n%s\n*******\n%s\n******* End Error *******', route, err.stack); 36 | if (!res.headersSent) { 37 | return res.send(500, {ok: false}); 38 | } 39 | res.write('\n'); 40 | res.end(); 41 | }); 42 | 43 | // server.on('after', restify.plugins.auditLogger( 44 | // { 45 | // log: log 46 | // }) 47 | // ); 48 | 49 | models(); 50 | routes(server); 51 | 52 | server.get('/', function (req, res, next) { 53 | res.send(config.app.name); 54 | return next(); 55 | }); 56 | 57 | server.listen(config.app.port, function () { 58 | log.info('Application %s listening at %s:%s', config.app.name, config.app.address, config.app.port); 59 | }); 60 | -------------------------------------------------------------------------------- /generators/app/templates/app/models/index.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | let fs = require('fs'); 5 | let path = require('path'); 6 | 7 | module.exports = function (server) { 8 | fs.readdirSync(path.join(__dirname, '.')).forEach(function (file) { 9 | if (file.substr(-3, 3) === '.js' && file !== 'index.js') { 10 | require('./' + file.replace('.js', ''))(server); 11 | } 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /generators/app/templates/app/models/widget.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | let mongoose = require('mongoose'); 5 | require('mongoose-currency').loadType(mongoose); 6 | let uniqueValidator = require('mongoose-unique-validator'); 7 | let Schema = mongoose.Schema; 8 | let Currency = mongoose.Types.Currency; 9 | 10 | let widgetSchema = new Schema({ 11 | product_id: {type: String, required: true, index: true, unique: true}, 12 | name: {type: String, required: true}, 13 | color: { 14 | type: String, required: true, 15 | enum: ['Red', 'Blue', 'Yellow', 'Green', 'Orange', 'Purple', 'White', 'Black'] 16 | }, 17 | size: { 18 | type: String, required: true, 19 | enum: ['Huge', 'Big', 'Medium', 'Small', 'Tiny'] 20 | }, 21 | price: {type: Currency, required: true}, 22 | inventory: {type: Number, required: true, min: 0} 23 | }); 24 | 25 | widgetSchema.set('timestamps', true); // include timestamps in docs 26 | 27 | // apply the mongoose unique validator plugin to widgetSchema 28 | widgetSchema.plugin(uniqueValidator); 29 | 30 | // use mongoose currency to transform price 31 | widgetSchema.set('toJSON', { 32 | virtuals: true, 33 | transform: function (doc, ret, options) { 34 | ret.price = Number(ret.price / 100).toFixed(2); 35 | delete ret.__v; // hide 36 | delete ret._id; // hide 37 | } 38 | }); 39 | 40 | let Widget = mongoose.model('Widget', widgetSchema); 41 | 42 | module.exports = Widget; 43 | -------------------------------------------------------------------------------- /generators/app/templates/app/routes/index.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | let fs = require('fs'); 5 | 6 | module.exports = function (server) { 7 | fs.readdirSync('./app/routes').forEach(function (file) { 8 | if (file.substr(-3, 3) === '.js' && file !== 'index.js') { 9 | require('./' + file.replace('.js', ''))(server); 10 | } 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /generators/app/templates/app/routes/utilities.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | let path = require('path'); 5 | let config = require(path.join(__dirname, '../../config/config')); 6 | 7 | let PATH = '/utils'; 8 | let VERSION = '1.0.0'; 9 | 10 | module.exports = function (server) { 11 | let ping = (req, res, next) => { 12 | res.send(200, 'true'); 13 | return next(); 14 | }; 15 | server.get({path: PATH + '/ping', version: VERSION}, ping); 16 | server.get({path: PATH + '/health', version: VERSION}, health); 17 | server.get({path: PATH + '/info', version: VERSION}, information); 18 | server.get({path: PATH + '/config', version: VERSION}, configuration); 19 | server.get({path: PATH + '/env', version: VERSION}, environment); 20 | 21 | 22 | function health(req, res, next) { 23 | res.json(200, {status: 'UP'}); 24 | return next(); 25 | } 26 | 27 | // Can be found with env URI 28 | function information(req, res, next) { 29 | let app_info = { 30 | name: process.env.npm_package_name, 31 | version: process.env.npm_package_version, 32 | description: process.env.npm_package_description, 33 | author: process.env.npm_package_author 34 | }; 35 | res.send(200, app_info); 36 | return next(); 37 | } 38 | 39 | function configuration(req, res, next) { 40 | res.send(200, config); 41 | return next(); 42 | } 43 | 44 | function environment(req, res, next) { 45 | res.send(200, process.env); 46 | return next(); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /generators/app/templates/app/routes/widget.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | let mongoose = require('mongoose'); 5 | let path = require('path'); 6 | let config = require(path.join(__dirname, '../../config/config')); 7 | let Widget = mongoose.model('Widget'); 8 | 9 | let PATH = '/widgets'; 10 | let VERSION = '1.0.0'; 11 | 12 | module.exports = function (server) { 13 | server.get({path: PATH, version: VERSION}, findDocuments); 14 | server.get({path: PATH + '/:product_id', version: VERSION}, findOneDocument); 15 | server.post({path: PATH, version: VERSION}, createDocument); 16 | server.put({path: PATH, version: VERSION}, updateDocument); 17 | server.del({path: PATH + '/:product_id', version: VERSION}, deleteDocument); 18 | 19 | function findDocuments(req, res, next) { 20 | // http://mongoosejs.com/docs/api.html#model_Model.find 21 | let conditions = {}; 22 | let projection = {}; 23 | let options = {}; 24 | 25 | Widget.find(conditions, projection, options).sort({'name': 1}).exec(function (error, widgets) { 26 | if (error) { 27 | return next(error); 28 | } else { 29 | res.header('X-Total-Count', widgets.length); 30 | res.send(200, widgets); 31 | return next(); 32 | } 33 | }); 34 | } 35 | 36 | function findOneDocument(req, res, next) { 37 | // http://mongoosejs.com/docs/api.html#model_Model.findOne 38 | let conditions = {'product_id': req.params.product_id}; 39 | let projection = {}; 40 | let options = {}; 41 | 42 | Widget.findOne(conditions, projection, options, function (error, widget) { 43 | if (error) { 44 | return next(error); 45 | } else { 46 | res.send(200, widget); 47 | return next(); 48 | } 49 | }); 50 | } 51 | 52 | function createDocument(req, res, next) { 53 | let widget = new Widget({ 54 | product_id: req.body.product_id, 55 | name: req.body.name, 56 | color: req.body.color, 57 | size: req.body.size, 58 | price: req.body.price, 59 | inventory: req.body.inventory 60 | }); 61 | 62 | // http://mongoosejs.com/docs/api.html#model_Model-save 63 | widget.save(function (error, widget, numAffected) { 64 | if (error) { 65 | return next(error); 66 | } else { 67 | res.send(204); 68 | return next(); 69 | } 70 | }); 71 | } 72 | 73 | function updateDocument(req, res, next) { 74 | // http://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate 75 | let conditions = {'product_id': req.body.product_id}; 76 | let update = { 77 | 'name': req.body.name, 78 | 'color': req.body.color, 79 | 'size': req.body.size, 80 | 'price': req.body.price, 81 | 'inventory': req.body.inventory 82 | }; 83 | let options = {runValidators: true, context: 'query'}; 84 | 85 | Widget.findOneAndUpdate(conditions, update, options, function (error) { 86 | if (error) { 87 | return next(error); 88 | } else { 89 | res.send(204); 90 | return next(); 91 | } 92 | }); 93 | } 94 | 95 | function deleteDocument(req, res, next) { 96 | // http://mongoosejs.com/docs/api.html#query_Query-remove 97 | let options = {'product_id': req.params.product_id}; 98 | 99 | Widget.remove(options, function (error) { 100 | if (error) { 101 | return next(error); 102 | } else { 103 | res.send(204); 104 | return next(); 105 | } 106 | }); 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /generators/app/templates/config/config.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true */ 2 | 'use strict'; 3 | 4 | let path = require('path'); 5 | 6 | let rootPath = path.normalize(__dirname + '/..'); 7 | 8 | let NODE_ENV = process.env.NODE_ENV || 'development'; 9 | let NODE_HOST = process.env.NODE_HOST || '127.0.0.1'; 10 | let NODE_PORT = process.env.NODE_PORT || 3000; 11 | let MONGO_HOST = process.env.MONGO_HOST || '127.0.0.1'; 12 | let MONGO_PORT = process.env.MONGO_PORT || 27017; 13 | let LOG_LEVEL = process.env.LOG_LEVEL || 'info'; 14 | 15 | let APP_NAME = 'node-restify-mongodb-'; 16 | 17 | let config = { 18 | development: { 19 | root: rootPath, 20 | app: { 21 | name: APP_NAME + NODE_ENV, 22 | address: NODE_HOST, 23 | port: NODE_PORT 24 | }, 25 | db: { 26 | host: MONGO_HOST, 27 | port: MONGO_PORT, 28 | name: APP_NAME + NODE_ENV 29 | }, 30 | log: { 31 | name: APP_NAME + NODE_ENV, 32 | level: LOG_LEVEL 33 | } 34 | }, 35 | test: { 36 | root: rootPath, 37 | app: { 38 | name: APP_NAME + NODE_ENV, 39 | address: NODE_HOST, 40 | port: NODE_PORT 41 | }, 42 | db: { 43 | host: MONGO_HOST, 44 | port: MONGO_PORT, 45 | name: APP_NAME + NODE_ENV 46 | }, 47 | log: { 48 | name: APP_NAME + NODE_ENV, 49 | level: LOG_LEVEL 50 | } 51 | }, 52 | production: { 53 | root: rootPath, 54 | app: { 55 | name: APP_NAME + NODE_ENV, 56 | address: NODE_HOST, 57 | port: NODE_PORT 58 | }, 59 | db: { 60 | host: MONGO_HOST, 61 | port: MONGO_PORT, 62 | name: APP_NAME + NODE_ENV 63 | }, 64 | log: { 65 | name: APP_NAME + NODE_ENV, 66 | level: LOG_LEVEL 67 | } 68 | } 69 | }; 70 | 71 | module.exports = config[NODE_ENV]; 72 | -------------------------------------------------------------------------------- /generators/app/templates/data/widgets.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "product_id": "RTHGP1FCGN", 4 | "name": "Reflupper", 5 | "color": "Red", 6 | "size": "Large", 7 | "price": "$12.95", 8 | "inventory": 25 9 | }, 10 | { 11 | "product_id": "SVHXPAWEOD", 12 | "name": "Voonex", 13 | "color": "Green", 14 | "size": "Medium", 15 | "price": "$10.99", 16 | "inventory": 50 17 | }, 18 | { 19 | "product_id": "GKO1SFX04M", 20 | "name": "Jukelox", 21 | "color": "Blue", 22 | "size": "Small", 23 | "price": "$25.49", 24 | "inventory": 75 25 | }, 26 | { 27 | "product_id": "3YIRGZ6TDW", 28 | "name": "Groopster", 29 | "color": "Yellow", 30 | "size": "Large", 31 | "price": "$99.95", 32 | "inventory": 100 33 | }, 34 | { 35 | "product_id": "4OZNPBMIDR", 36 | "name": "Fapster", 37 | "color": "Orange", 38 | "size": "Medium", 39 | "price": "29.99", 40 | "inventory": 5 41 | }, 42 | { 43 | "product_id": "6T2HC5MIZ9", 44 | "name": "Chaintwist", 45 | "color": "Purple", 46 | "size": "Tiny", 47 | "price": "$99.95", 48 | "inventory": 15 49 | }, 50 | { 51 | "product_id": "ERZ1RMJFR3", 52 | "name": "Glozzom", 53 | "color": "Red", 54 | "size": "Huge", 55 | "price": "$199.98", 56 | "inventory": 35 57 | }, 58 | { 59 | "product_id": "0BVCLPDZ42", 60 | "name": "Chaintwist", 61 | "color": "Blue", 62 | "size": "Medium", 63 | "price": "$89.95", 64 | "inventory": 55 65 | }, 66 | { 67 | "product_id": "N43WV5234S", 68 | "name": "Zapster", 69 | "color": "Green", 70 | "size": "Tiny", 71 | "price": "$17.49", 72 | "inventory": 65 73 | }, 74 | { 75 | "product_id": "N212QZOD9B", 76 | "name": "Pentwist", 77 | "color": "Yellow", 78 | "size": "Huge", 79 | "price": "$159.98", 80 | "inventory": 95 81 | } 82 | ] 83 | -------------------------------------------------------------------------------- /generators/app/templates/db-connection.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | var mongoose = require('mongoose'); 5 | mongoose.Promise = require('bluebird'); 6 | var path = require('path'); 7 | 8 | var config = require(path.join(__dirname, '/config/config')); 9 | var log = require(path.join(__dirname, 'log')); 10 | 11 | module.exports = function () { 12 | var uri = ''.concat('mongodb://', config.db.host, ':', config.db.port, '/', config.db.name); 13 | var options = { useMongoClient: true, promiseLibrary: require('bluebird') }; 14 | mongoose.connect(uri); 15 | // mongoose.connect(uri, options); 16 | // var db = mongoose.createConnection(uri, options); 17 | 18 | // db.on('connected', function () { 19 | // log.info('Mongodb connection open to ' + db_url); 20 | // }); 21 | // db.on('error', function () { 22 | // throw new Error('unable to connect to database at ' + db_url); 23 | // }); 24 | // db.on('disconnected', function () { 25 | // log.info('Mongodb connection disconnected'); 26 | // }); 27 | // process.on('SIGINT', function () { 28 | // db.close(function () { 29 | // log.info('Mongodb connection disconnected through app termination'); 30 | // process.exit(0); 31 | // }); 32 | // }); 33 | }; 34 | -------------------------------------------------------------------------------- /generators/app/templates/log.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | var bunyan = require('bunyan'); 5 | var path = require('path'); 6 | 7 | var config = require(path.join(__dirname, '/config/config')); 8 | 9 | var log = bunyan.createLogger({ 10 | name: config.log.name, 11 | level: config.log.level, 12 | stream: process.stdout, 13 | serializers: bunyan.stdSerializers 14 | }); 15 | 16 | module.exports = log; 17 | -------------------------------------------------------------------------------- /generators/app/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-restify-mongodb", 3 | "version": "0.0.0", 4 | "description": "Basic RESTful microservice, based on Node, Restify, and MongoDB", 5 | "author": "Author Name", 6 | "license": "", 7 | "repository": "", 8 | "main": "app.js", 9 | "scripts": { 10 | "start": "node app.js | ./node_modules/bunyan/bin/bunyan", 11 | "test": "grunt test" 12 | }, 13 | "dependencies": { 14 | "bluebird": "^3.x", 15 | "bunyan": "^1.x", 16 | "mongoose": "^4.x", 17 | "mongoose-currency": "^0.x", 18 | "mongoose-unique-validator": "^1.x", 19 | "restify": "^6.x" 20 | }, 21 | "devDependencies": { 22 | "grunt": "^1.x", 23 | "grunt-exec": "^3.x", 24 | "grunt-mocha-istanbul": "^5.x", 25 | "grunt-mongoimport": "^0.x", 26 | "istanbul": "1.1.0-alpha.1", 27 | "jasmine-node": "^1.x", 28 | "jshint": "^2.x", 29 | "mocha": "^4.x", 30 | "request": "^2.x", 31 | "should": "^13.x" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /generators/app/templates/postman/Widgets.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": [], 3 | "info": { 4 | "name": "Widgets", 5 | "_postman_id": "b0554bde-d0fe-448d-3203-4bbb95e54c96", 6 | "description": "", 7 | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" 8 | }, 9 | "item": [ 10 | { 11 | "name": "/widgets", 12 | "request": { 13 | "url": "http://{{hostname}}:{{port}}/widgets", 14 | "method": "GET", 15 | "header": [ 16 | { 17 | "key": "Accept", 18 | "value": "application/json", 19 | "description": "" 20 | } 21 | ], 22 | "body": { 23 | "mode": "formdata", 24 | "formdata": [] 25 | }, 26 | "description": "" 27 | }, 28 | "response": [] 29 | }, 30 | { 31 | "name": "/widgets/N212QZOD9B", 32 | "request": { 33 | "url": "http://{{hostname}}:{{port}}/widgets/N212QZOD9B", 34 | "method": "GET", 35 | "header": [ 36 | { 37 | "key": "Accept", 38 | "value": "application/json", 39 | "description": "" 40 | } 41 | ], 42 | "body": { 43 | "mode": "formdata", 44 | "formdata": [] 45 | }, 46 | "description": "" 47 | }, 48 | "response": [] 49 | }, 50 | { 51 | "name": "/widgets/N212QZOD9B", 52 | "request": { 53 | "url": "http://{{hostname}}:{{port}}/widgets/N212QZOD9B", 54 | "method": "DELETE", 55 | "header": [ 56 | { 57 | "key": "Accept", 58 | "value": "application/json", 59 | "description": "" 60 | } 61 | ], 62 | "body": { 63 | "mode": "formdata", 64 | "formdata": [] 65 | }, 66 | "description": "" 67 | }, 68 | "response": [] 69 | }, 70 | { 71 | "name": "/widgets", 72 | "request": { 73 | "url": "http://{{hostname}}:{{port}}/widgets", 74 | "method": "POST", 75 | "header": [ 76 | { 77 | "key": "Accept", 78 | "value": "application/json", 79 | "description": "" 80 | }, 81 | { 82 | "key": "Content-Type", 83 | "value": "application/json", 84 | "description": "" 85 | } 86 | ], 87 | "body": { 88 | "mode": "raw", 89 | "raw": "{\n\t\"product_id\": \"N212QZOD9B\",\n\t\"name\": \"Pentwist\",\n\t\"color\": \"Yellow\",\n\t\"size\": \"Huge\",\n\t\"price\": \"$159.98\",\n\t\"inventory\": 95\n}" 90 | }, 91 | "description": "" 92 | }, 93 | "response": [] 94 | }, 95 | { 96 | "name": "/widgets", 97 | "request": { 98 | "url": "http://{{hostname}}:{{port}}/widgets", 99 | "method": "PUT", 100 | "header": [ 101 | { 102 | "key": "Accept", 103 | "value": "application/json", 104 | "description": "" 105 | }, 106 | { 107 | "key": "Content-Type", 108 | "value": "application/json", 109 | "description": "" 110 | } 111 | ], 112 | "body": { 113 | "mode": "raw", 114 | "raw": "{\n\t\"product_id\": \"N212QZOD9B\",\n\t\"name\": \"Pentwist\",\n\t\"color\": \"Yellow\",\n\t\"size\": \"Huge\",\n\t\"price\": \"$169.98\",\n\t\"inventory\": 91\n}" 115 | }, 116 | "description": "" 117 | }, 118 | "response": [] 119 | }, 120 | { 121 | "name": "/utils/ping", 122 | "request": { 123 | "url": "http://{{hostname}}:{{port}}/utils/ping", 124 | "method": "GET", 125 | "header": [ 126 | { 127 | "key": "Accept", 128 | "value": "text/plain", 129 | "description": "" 130 | } 131 | ], 132 | "body": { 133 | "mode": "formdata", 134 | "formdata": [] 135 | }, 136 | "description": "" 137 | }, 138 | "response": [] 139 | }, 140 | { 141 | "name": "/utils/health", 142 | "request": { 143 | "url": "http://{{hostname}}:{{port}}/utils/health", 144 | "method": "GET", 145 | "header": [ 146 | { 147 | "key": "Accept", 148 | "value": "application/json", 149 | "description": "" 150 | } 151 | ], 152 | "body": { 153 | "mode": "formdata", 154 | "formdata": [] 155 | }, 156 | "description": "" 157 | }, 158 | "response": [] 159 | }, 160 | { 161 | "name": "/utils/info", 162 | "request": { 163 | "url": "http://{{hostname}}:{{port}}/utils/info", 164 | "method": "GET", 165 | "header": [ 166 | { 167 | "key": "Accept", 168 | "value": "application/json", 169 | "description": "" 170 | } 171 | ], 172 | "body": { 173 | "mode": "formdata", 174 | "formdata": [] 175 | }, 176 | "description": "" 177 | }, 178 | "response": [] 179 | }, 180 | { 181 | "name": "/utils/config", 182 | "request": { 183 | "url": "http://{{hostname}}:{{port}}/utils/config", 184 | "method": "GET", 185 | "header": [ 186 | { 187 | "key": "Accept", 188 | "value": "application/json", 189 | "description": "" 190 | } 191 | ], 192 | "body": { 193 | "mode": "formdata", 194 | "formdata": [] 195 | }, 196 | "description": "" 197 | }, 198 | "response": [] 199 | }, 200 | { 201 | "name": "/utils/env", 202 | "request": { 203 | "url": "http://{{hostname}}:{{port}}/utils/env", 204 | "method": "GET", 205 | "header": [ 206 | { 207 | "key": "Accept", 208 | "value": "application/json", 209 | "description": "" 210 | } 211 | ], 212 | "body": { 213 | "mode": "formdata", 214 | "formdata": [] 215 | }, 216 | "description": "" 217 | }, 218 | "response": [] 219 | } 220 | ] 221 | } -------------------------------------------------------------------------------- /generators/app/templates/postman/localhost-restify.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "c1707fb1-1ecf-c120-8a2b-49e544b7a615", 3 | "name": "localhost-restify", 4 | "values": [ 5 | { 6 | "key": "hostname", 7 | "value": "localhost", 8 | "type": "text", 9 | "enabled": true, 10 | "hovered": false 11 | }, 12 | { 13 | "key": "port", 14 | "value": "3000", 15 | "type": "text", 16 | "enabled": true, 17 | "hovered": false 18 | } 19 | ], 20 | "team": null, 21 | "timestamp": 1466485702100, 22 | "synced": false, 23 | "syncedFilename": "", 24 | "isDeleted": false 25 | } -------------------------------------------------------------------------------- /generators/app/templates/spec/utils_spec.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | //////////// VARIABLES /////////////////// 5 | 6 | let request = require('request'); 7 | let should = require('should'); 8 | let path = require('path'); 9 | 10 | let config = require(path.join(__dirname, '../config/config')); 11 | let base_url = ''.concat('http://', config.app.address, ':', config.app.port); 12 | 13 | 14 | //////////// TESTS /////////////////////// 15 | 16 | describe('Utility URIs', function () { 17 | describe('GET /utils/ping', function () { 18 | let url = base_url + '/utils/ping'; 19 | 20 | let options = { 21 | method: 'GET', 22 | url: url, 23 | headers: { 24 | accept: 'text/plain' 25 | } 26 | }; 27 | 28 | it('should respond with a status code of 200', function (done) { 29 | request(options, function (error, response, body) { 30 | response.statusCode.should.be.exactly(200); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('should respond with \'true\'', function (done) { 36 | request(options, function (error, response, body) { 37 | body.should.be.exactly('true'); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | 43 | describe('GET /utils/health', function () { 44 | let url = base_url + '/utils/health'; 45 | 46 | let options = { 47 | method: 'GET', 48 | url: url, 49 | headers: { 50 | accept: 'application/json' 51 | } 52 | }; 53 | 54 | it('should respond with a status code of 200', function (done) { 55 | request(options, function (error, response, body) { 56 | response.statusCode.should.be.exactly(200); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('should respond with a \'status\' of \'UP\'', function (done) { 62 | request(options, function (error, response, body) { 63 | let status = JSON.parse(body); 64 | status.should.have.a.property('status', 'UP'); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | 70 | describe('GET /utils/info', function () { 71 | let url = base_url + '/utils/info'; 72 | 73 | let options = { 74 | method: 'GET', 75 | url: url, 76 | headers: { 77 | accept: 'application/json' 78 | } 79 | }; 80 | 81 | it('should respond with a status code of 200', function (done) { 82 | request(options, function (error, response, body) { 83 | response.statusCode.should.be.exactly(200); 84 | done(); 85 | }); 86 | }); 87 | 88 | it('should respond with exactly (1) \'info\' object containing (3) properties', function (done) { 89 | request(options, function (error, response, body) { 90 | let info = JSON.parse(body); 91 | Object.keys(info).should.have.a.lengthOf(3); 92 | done(); 93 | }); 94 | }); 95 | }); 96 | 97 | describe('GET /utils/config', function () { 98 | let url = base_url + '/utils/config'; 99 | 100 | let options = { 101 | method: 'GET', 102 | url: url, 103 | headers: { 104 | accept: 'application/json' 105 | } 106 | }; 107 | 108 | it('should respond with a status code of 200', function (done) { 109 | request(options, function (error, response, body) { 110 | response.statusCode.should.be.exactly(200); 111 | done(); 112 | }); 113 | }); 114 | 115 | it('should respond with exactly (1) non-null \'config\' object', function (done) { 116 | request(options, function (error, response, body) { 117 | let config = JSON.parse(body); 118 | config.should.be.an.instanceof(Object).and.not.null; 119 | done(); 120 | }); 121 | }); 122 | }); 123 | 124 | describe('GET /utils/env', function () { 125 | let url = base_url + '/utils/env'; 126 | 127 | let options = { 128 | method: 'GET', 129 | url: url, 130 | headers: { 131 | accept: 'application/json' 132 | } 133 | }; 134 | 135 | it('should respond with a status code of 200', function (done) { 136 | request(options, function (error, response, body) { 137 | response.statusCode.should.be.exactly(200); 138 | done(); 139 | }); 140 | }); 141 | 142 | it('should respond with exactly (1) non-null \'env\' object', function (done) { 143 | request(options, function (error, response, body) { 144 | let env = JSON.parse(body); 145 | env.should.be.an.instanceof(Object).and.not.null; 146 | done(); 147 | }); 148 | }); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /generators/app/templates/spec/widget_spec.js: -------------------------------------------------------------------------------- 1 | /*jslint node: true */ 2 | 'use strict'; 3 | 4 | //////////// VARIABLES /////////////////// 5 | 6 | let request = require('request'); 7 | let mongoose = require('mongoose'); 8 | let should = require('should'); 9 | let path = require('path'); 10 | 11 | let config = require(path.join(__dirname, '../config/config')); 12 | let base_url = ''.concat('http://', config.app.address, ':', config.app.port); 13 | 14 | let dbConnection = require(path.join(__dirname, '../db-connection')); 15 | dbConnection(); 16 | 17 | let widget_model = require(path.join(__dirname, '../app/models/widget')); 18 | widget_model(); 19 | let Widget = mongoose.model('Widget'); 20 | 21 | let log = require(path.join(__dirname, '../log')); 22 | 23 | 24 | //////////// HELPER FUNCTIONS //////////// 25 | 26 | function saveWidget(widget) { 27 | widget.save(function (error) { 28 | if (error) { 29 | log.error(error); 30 | } 31 | }); 32 | } 33 | 34 | function removeWidgets(options) { 35 | Widget.remove(options, function (error) { 36 | if (error) { 37 | log.error(error); 38 | } 39 | }); 40 | } 41 | 42 | 43 | //////////// TESTS /////////////////////// 44 | 45 | // find all widgets 46 | describe('Widget URIs', function () { 47 | describe('GET /widgets', function () { 48 | beforeEach(function () { 49 | removeWidgets({}); 50 | 51 | saveWidget(new Widget({ 52 | product_id: 'YMIYW2VROS', 53 | name: 'TestWidget_YMIYW2VROS', 54 | color: 'Black', 55 | size: 'Huge', 56 | price: '$99.99', 57 | inventory: 96 58 | })); 59 | 60 | saveWidget(new Widget({ 61 | product_id: 'FGBRYL6XSF', 62 | name: 'TestWidget_FGBRYL6XSF', 63 | color: 'Red', 64 | size: 'Huge', 65 | price: '$127.49', 66 | inventory: 1205 67 | })); 68 | 69 | saveWidget(new Widget({ 70 | product_id: '5H7HW8Y1E2', 71 | name: 'TestWidget_5H7HW8Y1E2', 72 | color: 'Black', 73 | size: 'Tiny', 74 | price: '$1.49', 75 | inventory: 0 76 | })); 77 | }); 78 | 79 | afterEach(function () { 80 | removeWidgets({}); 81 | }); 82 | 83 | let url = base_url + '/widgets'; 84 | 85 | let options = { 86 | method: 'GET', 87 | url: url, 88 | headers: { 89 | accept: 'application/json' 90 | } 91 | }; 92 | 93 | it('should respond with a status code of 200', function (done) { 94 | request(options, function (error, response, body) { 95 | response.statusCode.should.be.exactly(200); 96 | done(); 97 | }); 98 | }); 99 | 100 | it('should respond with exactly (3) widget objects in an array', function (done) { 101 | request(options, function (error, response, body) { 102 | let widget = JSON.parse(body); 103 | widget.should.be.an.instanceof(Array).and.have.a.lengthOf(3); 104 | done(); 105 | }); 106 | }); 107 | 108 | it('should respond with an \'x-total-count\' header containing a value of \'3\'', function (done) { 109 | request(options, function (error, response, body) { 110 | response.headers.should.have.a.property('x-total-count', '3'); 111 | done(); 112 | }); 113 | }); 114 | }); 115 | 116 | // find one widget 117 | describe('GET /widgets/:product_id', function () { 118 | beforeEach(function () { 119 | removeWidgets({}); 120 | 121 | saveWidget(new Widget({ 122 | product_id: '4YFZH127BX', 123 | name: 'TestWidget_4YFZH127BX', 124 | color: 'Orange', 125 | size: 'Small', 126 | price: '$19.93', 127 | inventory: 13 128 | })); 129 | 130 | saveWidget(new Widget({ 131 | product_id: '0EJLZK6BK8', 132 | name: 'TestWidget_0EJLZK6BK8', 133 | color: 'Red', 134 | size: 'Huge', 135 | price: '$137.49', 136 | inventory: 46 137 | })); 138 | }); 139 | 140 | afterEach(function () { 141 | removeWidgets({}); 142 | }); 143 | 144 | let url1 = base_url + '/widgets/4YFZH127BX'; 145 | 146 | let options1 = { 147 | method: 'GET', 148 | url: url1, 149 | headers: { 150 | accept: 'application/json' 151 | } 152 | }; 153 | 154 | it('should respond with a status code of 200', function (done) { 155 | request(options1, function (error, response, body) { 156 | response.statusCode.should.be.exactly(200); 157 | done(); 158 | }); 159 | }); 160 | 161 | it('should respond with exactly (1) widget object', function (done) { 162 | request(options1, function (error, response, body) { 163 | let widget = JSON.parse(body); 164 | widget.should.be.an.instanceof(Object); 165 | done(); 166 | }); 167 | }); 168 | 169 | it('should respond with the value of \'TestWidget_4YFZH127BX\' for \'name\' key', function (done) { 170 | request(options1, function (error, response, body) { 171 | let widget = JSON.parse(body); 172 | widget.should.have.a.property('name', 'TestWidget_4YFZH127BX'); 173 | done(); 174 | }); 175 | }); 176 | 177 | let url2 = base_url + '/widgets/BADPRODUCT'; 178 | 179 | let options2 = { 180 | method: 'GET', 181 | url: url2, 182 | headers: { 183 | accept: 'application/json' 184 | } 185 | }; 186 | 187 | it('should respond with \'null\' when the \'product_id\' is not found', function (done) { 188 | request(options2, function (error, response, body) { 189 | body.should.be.null; 190 | done(); 191 | }); 192 | }); 193 | }); 194 | 195 | // create new widget 196 | describe('POST /widgets', function () { 197 | beforeEach(function () { 198 | removeWidgets({}); 199 | }); 200 | afterEach(function () { 201 | removeWidgets({}); 202 | }); 203 | 204 | let widget = { 205 | product_id: 'DC3NHTGNAY', 206 | name: 'TestWidget_DC3NHTGNAY', 207 | color: 'Green', 208 | size: 'Big', 209 | price: '$79.92', 210 | inventory: 27 211 | }; 212 | 213 | let url = base_url + '/widgets'; 214 | 215 | let options = { 216 | method: 'POST', 217 | url: url, 218 | headers: { 219 | accept: 'application/json' 220 | }, 221 | body: widget, 222 | json: true 223 | }; 224 | 225 | it('should respond with a status code of 204', function (done) { 226 | request(options, function (error, response, body) { 227 | response.statusCode.should.be.exactly(204); 228 | done(); 229 | }); 230 | }); 231 | }); 232 | 233 | // create new widget 234 | describe('POST /widgets', function () { 235 | beforeEach(function () { 236 | removeWidgets({}); 237 | }); 238 | afterEach(function () { 239 | removeWidgets({}); 240 | }); 241 | 242 | let widget = { // no 'name' key 243 | product_id: 'DC3NHTGNAY', 244 | color: 'Green', 245 | size: 'Big', 246 | price: '$79.92', 247 | inventory: 27 248 | }; 249 | 250 | let url = base_url + '/widgets'; 251 | 252 | let options = { 253 | method: 'POST', 254 | url: url, 255 | headers: { 256 | accept: 'application/json' 257 | }, 258 | body: widget, 259 | json: true 260 | }; 261 | 262 | it('should respond with a status code of 500 when missing \'name\' property', function (done) { 263 | request(options, function (error, response, body) { 264 | response.statusCode.should.be.exactly(500); 265 | done(); 266 | }); 267 | }); 268 | 269 | it('should respond with an error message: \'Widget validation failed\' when missing \'name\' property', function (done) { 270 | request(options, function (error, response, body) { 271 | body.should.have.a.property('_message', 'Widget validation failed'); 272 | done(); 273 | }); 274 | }); 275 | }); 276 | 277 | // update one widget 278 | describe('PUT /widgets', function () { 279 | beforeEach(function () { 280 | removeWidgets({}); 281 | 282 | saveWidget(new Widget({ 283 | product_id: 'ZC7DV7BSPE', 284 | name: 'TestWidget_ZC7DV7BSPE', 285 | color: 'Blue', 286 | size: 'Small', 287 | price: '$9.92', 288 | inventory: 27 289 | })); 290 | }); 291 | 292 | afterEach(function () { 293 | removeWidgets({}); 294 | }); 295 | 296 | let widget = { // modified inventory level 297 | product_id: 'ZC7DV7BSPE', 298 | name: 'TestWidget_ZC7DV7BSPE', 299 | color: 'Blue', 300 | size: 'Small', 301 | price: '$9.92', 302 | inventory: 21 303 | }; 304 | 305 | let url = base_url + '/widgets'; 306 | 307 | let options = { 308 | method: 'PUT', 309 | url: url, 310 | headers: { 311 | accept: 'application/json' 312 | }, 313 | body: widget, 314 | json: true 315 | }; 316 | 317 | it('should respond with a status code of 204', function (done) { 318 | request(options, function (error, response, body) { 319 | response.statusCode.should.be.exactly(204); 320 | done(); 321 | }); 322 | }); 323 | 324 | it('should respond with no response body', function (done) { 325 | request(options, function (error, response, body) { 326 | response.should.not.have.a.property('body'); 327 | done(); 328 | }); 329 | }); 330 | }); 331 | 332 | // update and confirm one widget 333 | describe('PUT /widgets', function () { 334 | beforeEach(function () { 335 | removeWidgets({}); 336 | 337 | saveWidget(new Widget({ 338 | product_id: 'ZC7DV7BSPE', 339 | name: 'TestWidget_ZC7DV7BSPE', 340 | color: 'Blue', 341 | size: 'Small', 342 | price: '$9.92', 343 | inventory: 27 344 | })); 345 | 346 | let widget = { // modified inventory level 347 | product_id: 'ZC7DV7BSPE', 348 | name: 'TestWidget_ZC7DV7BSPE', 349 | color: 'Blue', 350 | size: 'Small', 351 | price: '$9.92', 352 | inventory: 21 353 | }; 354 | 355 | let url1 = base_url + '/widgets'; 356 | 357 | let options1 = { 358 | method: 'PUT', 359 | url: url1, 360 | headers: { 361 | accept: 'application/json' 362 | }, 363 | body: widget, 364 | json: true 365 | }; 366 | 367 | request(options1, function (error, response, body) { 368 | }); 369 | }); 370 | 371 | afterEach(function () { 372 | removeWidgets({}); 373 | }); 374 | 375 | let url2 = base_url + '/widgets/ZC7DV7BSPE'; 376 | 377 | let options2 = { 378 | method: 'GET', 379 | url: url2, 380 | headers: { 381 | accept: 'application/json' 382 | } 383 | }; 384 | 385 | it('should respond with new value of \'21\' for \'inventory\' key', function (done) { 386 | request(options2, function (error, response, body) { 387 | let widget = JSON.parse(body); 388 | widget.should.have.a.property('inventory', 21); 389 | done(); 390 | }); 391 | }); 392 | }); 393 | 394 | // delete one widget 395 | describe('DELETE /widgets/:product_id', function () { 396 | beforeEach(function () { 397 | removeWidgets({}); 398 | 399 | saveWidget(new Widget({ 400 | product_id: '3NDO87DF3C', 401 | name: 'TestWidget_3NDO87DF3C', 402 | color: 'Green', 403 | size: 'Small', 404 | price: '$71.95', 405 | inventory: 653 406 | })); 407 | }); 408 | 409 | afterEach(function () { 410 | removeWidgets({}); 411 | }); 412 | 413 | let url = base_url + '/widgets/3NDO87DF3C'; 414 | 415 | let options = { 416 | method: 'DELETE', 417 | url: url, 418 | headers: { 419 | accept: 'application/json' 420 | } 421 | }; 422 | 423 | it('should respond with a status code of 204', function (done) { 424 | request(options, function (error, response, body) { 425 | response.statusCode.should.be.exactly(204); 426 | done(); 427 | }); 428 | }); 429 | 430 | it('should respond with an empty response body', function (done) { 431 | request(options, function (error, response, body) { 432 | body.should.be.exactly(''); 433 | done(); 434 | }); 435 | }); 436 | }); 437 | }); 438 | 439 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var gulp = require('gulp'); 4 | var eslint = require('gulp-eslint'); 5 | var excludeGitignore = require('gulp-exclude-gitignore'); 6 | var mocha = require('gulp-mocha'); 7 | var istanbul = require('gulp-istanbul'); 8 | var nsp = require('gulp-nsp'); 9 | var plumber = require('gulp-plumber'); 10 | var coveralls = require('gulp-coveralls'); 11 | 12 | gulp.task('static', function () { 13 | return gulp.src('**/*.js') 14 | .pipe(excludeGitignore()) 15 | .pipe(eslint()) 16 | .pipe(eslint.format()) 17 | .pipe(eslint.failAfterError()); 18 | }); 19 | 20 | gulp.task('nsp', function (cb) { 21 | nsp({package: path.resolve('package.json')}, cb); 22 | }); 23 | 24 | gulp.task('pre-test', function () { 25 | return gulp.src('generators/**/*.js') 26 | .pipe(excludeGitignore()) 27 | .pipe(istanbul({ 28 | includeUntested: true 29 | })) 30 | .pipe(istanbul.hookRequire()); 31 | }); 32 | 33 | gulp.task('test', ['pre-test'], function (cb) { 34 | var mochaErr; 35 | 36 | gulp.src('test/**/*.js') 37 | .pipe(plumber()) 38 | .pipe(mocha({reporter: 'spec'})) 39 | .on('error', function (err) { 40 | mochaErr = err; 41 | }) 42 | .pipe(istanbul.writeReports()) 43 | .on('end', function () { 44 | cb(mochaErr); 45 | }); 46 | }); 47 | 48 | gulp.task('watch', function () { 49 | gulp.watch(['generators/**/*.js', 'test/**'], ['test']); 50 | }); 51 | 52 | gulp.task('coveralls', ['test'], function () { 53 | if (!process.env.CI) { 54 | return; 55 | } 56 | 57 | return gulp.src(path.join(__dirname, 'coverage/lcov.info')) 58 | .pipe(coveralls()); 59 | }); 60 | 61 | gulp.task('prepublish', ['nsp']); 62 | gulp.task('default', ['static', 'test', 'coveralls']); 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-node-restify-mongodb", 3 | "version": "0.3.0", 4 | "description": "Scaffolds a basic RESTful microservice, based on Node, Restify, and MongoDB", 5 | "homepage": "https://github.com/garystafford/generator-node-restify-mongodb", 6 | "license": "MIT", 7 | "author": { 8 | "name": "Gary A. Stafford", 9 | "email": "garystafford@rochester.rr.com", 10 | "url": "http://www.programmaticponderings.com" 11 | }, 12 | "repository": "garystafford/generator-node-restify-mongodb", 13 | "scripts": { 14 | "prepublish": "gulp prepublish", 15 | "test": "gulp test" 16 | }, 17 | "files": [ 18 | "generators" 19 | ], 20 | "main": "generators/index.js", 21 | "keywords": [ 22 | "api", 23 | "jasmine", 24 | "microservice", 25 | "mongodb", 26 | "node", 27 | "restful", 28 | "RESTful", 29 | "restify", 30 | "yeoman-generator" 31 | ], 32 | "dependencies": { 33 | "chalk": "^2.3.0", 34 | "mkdirp": "^0.5.1", 35 | "yeoman-generator": "^2.0.1", 36 | "yosay": "^2.0.1" 37 | }, 38 | "devDependencies": { 39 | "eslint": "^4.10.0", 40 | "eslint-config-xo-space": "^0.17.0", 41 | "gulp": "^3.9.1", 42 | "gulp-coveralls": "^0.1.4", 43 | "gulp-eslint": "^4.0.0", 44 | "gulp-exclude-gitignore": "^1.2.0", 45 | "gulp-istanbul": "^1.1.2", 46 | "gulp-line-ending-corrector": "^1.0.1", 47 | "gulp-mocha": "^4.3.1", 48 | "gulp-nsp": "^2.4.2", 49 | "gulp-plumber": "^1.1.0", 50 | "yeoman-assert": "^3.1.0", 51 | "yeoman-test": "^1.7.0" 52 | }, 53 | "eslintConfig": { 54 | "extends": "xo-space", 55 | "env": { 56 | "mocha": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garystafford/generator-node-restify-mongodb/89bf8449e834f5e94d3c97b50c00c567b09f52d5/preview.png -------------------------------------------------------------------------------- /test/app.js: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it */ 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | var assert = require('yeoman-assert'); 6 | var helpers = require('yeoman-test'); 7 | 8 | const expectedFiles = { 9 | app: [ 10 | 'app.js', 11 | 'app/models/index.js', 12 | 'app/models/widget.js', 13 | 'app/routes/index.js', 14 | 'app/routes/widget.js', 15 | 'app/routes/utilities.js' 16 | ], 17 | config: [ 18 | 'config/config.js' 19 | ], 20 | data: [ 21 | 'data/widgets.json' 22 | ], 23 | spec: [ 24 | 'spec/utils_spec.js', 25 | 'spec/widget_spec.js' 26 | ], 27 | postman: [ 28 | 'postman/localhost-restify.postman_environment.json', 29 | 'postman/Widgets.postman_collection.json' 30 | ], 31 | grunt: [ 32 | 'Gruntfile.js' 33 | ], 34 | projectfiles: [ 35 | 'package.json', 36 | 'db-connection.js', 37 | 'log.js', 38 | '.gitignore', 39 | '.editorconfig' 40 | ] 41 | }; 42 | 43 | describe('generator-node-restify-mongodb:app', function () { 44 | before(function () { 45 | return helpers.run(path.join(__dirname, '../generators/app')) 46 | .toPromise(); 47 | }); 48 | 49 | it('can be imported without blowing up', function () { 50 | var app = require('../generators/app'); 51 | assert(app !== undefined); 52 | }); 53 | 54 | it('creates expected default files', function () { 55 | assert.file(expectedFiles.app); 56 | assert.file(expectedFiles.config); 57 | assert.file(expectedFiles.data); 58 | assert.file(expectedFiles.spec); 59 | assert.file(expectedFiles.postman); 60 | assert.file(expectedFiles.grunt); 61 | assert.file(expectedFiles.projectfiles); 62 | }); 63 | }) 64 | ; 65 | --------------------------------------------------------------------------------