├── .nvmrc ├── test ├── mocha.opts └── api │ └── controllers │ └── person.integration.js ├── .coveralls.yml ├── .gitignore ├── .dockerignore ├── .travis.yml ├── config └── default.js ├── lib ├── swaggerDoc.js ├── logger.js ├── schema.js └── middleware │ └── errorHandler.js ├── api ├── models │ └── person.js └── controllers │ └── person.js ├── index.js ├── Dockerfile ├── package.json ├── server.js ├── README.md ├── swagger.json └── .eslintrc /.nvmrc: -------------------------------------------------------------------------------- 1 | 0.12.7 2 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug* 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug* 3 | coverage/ 4 | .git/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.12' 4 | services: 5 | - mongodb 6 | after_script: istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage 7 | -------------------------------------------------------------------------------- /config/default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | server: { 5 | port: 3000, 6 | hostname: 'localhost' 7 | }, 8 | db: 'mongodb://localhost/rest-service-boilerplate', 9 | log: { 10 | level: 'debug' 11 | }, 12 | errors: { 13 | showStack: false 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /lib/swaggerDoc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var config = require('config'); 3 | var PORT = config.get('server.port'); 4 | var HOSTNAME = config.get('server.hostname'); 5 | 6 | function SwaggerDoc(doc){ 7 | doc.host = HOSTNAME + ':' + PORT; 8 | return doc; 9 | } 10 | 11 | module.exports = SwaggerDoc; 12 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var winston = require('winston'); 4 | var config = require('config'); 5 | var level = config.get('log.level'); 6 | var logger = new winston.Logger({ 7 | transports: [ 8 | new winston.transports.Console({'timestamp': true, level: level}) 9 | ] 10 | }); 11 | 12 | module.exports = logger; 13 | -------------------------------------------------------------------------------- /api/models/person.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var getSchema = require('../../lib/schema'); 5 | 6 | var modelName = 'Person'; 7 | var Schema = getSchema(modelName); 8 | 9 | // create methods on the schema before converting to model 10 | // Schema.methods.doSomething = function()... 11 | 12 | module.exports = Schema = mongoose.model(modelName, Schema); 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var server = require('./server'); 4 | 5 | server.start(function startCb(err){ 6 | if(err) { 7 | process.exit(1); 8 | } 9 | }); 10 | 11 | // PM2 sends IPC message for graceful shutdown 12 | process.on('message', function msgCb(msg) { 13 | if (msg === 'shutdown') { 14 | server.stop(); 15 | } 16 | }); 17 | 18 | // Ctrl+c or kill $pid 19 | process.on('SIGINT', server.stop); 20 | process.on('SIGTERM', server.stop); 21 | -------------------------------------------------------------------------------- /lib/schema.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Swagger2Mongoose = require('swagger2mongoose'); 3 | var swaggerDoc = require('../swagger.json'); 4 | 5 | var s2m = new Swagger2Mongoose({ 6 | swaggerDoc: swaggerDoc, 7 | modelDir: 'api/models' 8 | }); 9 | 10 | function GetSchema(name){ 11 | return s2m.getMongooseSchema(name); 12 | } 13 | 14 | // create methods on the schema before converting to model 15 | // Schema.methods.doSomething = function()... 16 | 17 | module.exports = GetSchema; 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | MAINTAINER Ian Patton 3 | 4 | RUN apt-get update && \ 5 | apt-get install -yq \ 6 | curl \ 7 | wget \ 8 | git-core \ 9 | g++ \ 10 | libssl-dev \ 11 | libxml2-dev \ 12 | apt-transport-https \ 13 | lsb-release \ 14 | build-essential \ 15 | python-all 16 | 17 | RUN git clone https://github.com/creationix/nvm.git /.nvm 18 | RUN echo ". /.nvm/nvm.sh" >> /etc/bash.bashrc 19 | RUN . /.nvm/nvm.sh && nvm install v0.12.7 && nvm use v0.12.7 && nvm alias default v0.12.7 20 | 21 | # Bundle app source 22 | COPY . /app 23 | # Install app dependencies 24 | RUN . /.nvm/nvm.sh && nvm use default && cd /app && npm install --production 25 | 26 | EXPOSE 3000 27 | 28 | CMD cd /app && . /.nvm/nvm.sh && nvm use default && node index 29 | -------------------------------------------------------------------------------- /lib/middleware/errorHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var config = require('config'); 3 | var showErrors = config.get('errors.showStack'); 4 | 5 | module.exports = function errorHandler(err, req, res, next){ 6 | if (err.status) res.statusCode = err.status; 7 | if (res.statusCode < 400) res.statusCode = 500; 8 | req.headers = req.headers || {}; 9 | var accept = req.headers.accept || ''; 10 | // json 11 | if (~accept.indexOf('json')) { 12 | var error = { message: err.message}; 13 | if(showErrors) error.stack = err.stack; 14 | for (var prop in err) error[prop] = err[prop]; 15 | var json = JSON.stringify({ error: error }); 16 | res.setHeader('Content-Type', 'application/json'); 17 | res.end(json); 18 | // plain text 19 | } else { 20 | res.setHeader('Content-Type', 'text/plain'); 21 | res.end(err.message); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /test/api/controllers/person.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('chai').should(); 3 | var mongoose = require('mongoose'); 4 | var rest = require('restler'); 5 | var server = require('../../../server'); 6 | var config = require('config'); 7 | var host = config.get('server.hostname'); 8 | var port = config.get('server.port'); 9 | var baseUrl = 'http://' + host + ':' + port; 10 | 11 | describe('Person Controller', function descCb(){ 12 | before(server.start); 13 | 14 | after(function cb(done) { 15 | var Model = mongoose.model('Person'); 16 | Model.remove(function rmCb(){ 17 | server.stop(done); 18 | }); 19 | }); 20 | 21 | var person; 22 | 23 | it('should allow creation of a person', function itCb(done){ 24 | rest.postJson(baseUrl + '/person', 25 | {name: 'John Doe'} 26 | ).on('complete', function completeCb(data, response){ 27 | (response.statusCode).should.equal(201); 28 | person = data; 29 | done(); 30 | }); 31 | }); 32 | 33 | it('should allow retrieval of a person', function itCb(done){ 34 | rest.get(baseUrl + '/person/' + person._id) 35 | .on('complete', function completeCb(data, response){ 36 | (response.statusCode).should.equal(200); 37 | done(); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /api/controllers/person.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var JSONStream = require('JSONStream'); 3 | var Model = require('../models/person'); 4 | 5 | function GET(req, res, next){ 6 | if(!req.swagger.params.id){ 7 | res.set('Content-Type', 'application/json'); 8 | Model.find(req.swagger.params).stream().pipe(JSONStream.stringify()).pipe(res); 9 | }else{ 10 | Model.findOne(req.params, function findOneCb(err, doc){ 11 | if(err){ 12 | next(err); 13 | }else if(doc){ 14 | res.status(200).json(doc); 15 | }else{ 16 | next(); 17 | } 18 | }); 19 | } 20 | } 21 | 22 | function POST(req, res, next){ 23 | var M = Model; 24 | var doc = new M(req.body); 25 | doc.save(function saveCb(err){ 26 | if(err){ 27 | next(err); 28 | }else{ 29 | res.status(201).json(doc); 30 | } 31 | }); 32 | } 33 | 34 | function PUT(req, res, next){ 35 | Model.update(req.swagger.params, req.body, function updateCb(err){ 36 | if(err){ 37 | next(err); 38 | }else{ 39 | res.status(200).json(req.body); 40 | } 41 | }); 42 | } 43 | 44 | function DEL(req, res, next){ 45 | Model.remove(req.params, function removeCb(err){ 46 | if(err){ 47 | next(err); 48 | }else{ 49 | res.status(200).json(); 50 | } 51 | }); 52 | } 53 | 54 | module.exports = { 55 | GET: GET, 56 | POST: POST, 57 | PUT: PUT, 58 | DEL: DEL 59 | }; 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest-service-boilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "pm2 start index.js -i 2 --name \"api\" --watch", 8 | "status": "pm2 show \"api\"", 9 | "reload": "pm2 reload \"api\"", 10 | "stop": "pm2 stop \"api\"", 11 | "test": "NODE_ENV=TEST mocha", 12 | "coverage": "NODE_ENV=TEST istanbul cover _mocha", 13 | "lint": "eslint --ext .js api/ lib/ index.js server.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/niahmiah/rest-service-boilerplate.git" 18 | }, 19 | "keywords": [ 20 | "swagger", 21 | "mongo", 22 | "express", 23 | "rest", 24 | "service" 25 | ], 26 | "author": "Ian Patton (http://github.com/niahmiah)", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/niahmiah/rest-service-boilerplate/issues" 30 | }, 31 | "homepage": "https://github.com/niahmiah/rest-service-boilerplate", 32 | "devDependencies": { 33 | "chai": "^3.2.0", 34 | "coveralls": "^2.11.3", 35 | "eslint": "^1.0.0", 36 | "istanbul": "^0.3.17", 37 | "mocha": "^2.2.5", 38 | "restler": "^3.3.0" 39 | }, 40 | "dependencies": { 41 | "JSONStream": "^1.0.4", 42 | "config": "^1.15.0", 43 | "express": "^4.13.2", 44 | "mongoose": "^4.1.0", 45 | "pm2": "^0.14.6", 46 | "swagger-tools": "^0.9.0", 47 | "swagger2mongoose": "^1.1.0", 48 | "winston": "^1.0.1" 49 | }, 50 | "engine": "node >= 0.12.1" 51 | } 52 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('config'); 4 | var HOST = config.get('server.hostname'); 5 | var PORT = config.get('server.port'); 6 | var DB = config.get('db'); 7 | 8 | var swaggerDoc = require('./swagger.json'); 9 | var apiDoc = require('./lib/swaggerDoc')(swaggerDoc); 10 | 11 | var server; 12 | var mongoose = require('mongoose'); 13 | 14 | var express = require('express'); 15 | var swaggerTools = require('swagger-tools'); 16 | 17 | var errorHandler = require('./lib/middleware/errorHandler'); 18 | 19 | var log = require('./lib/logger'); 20 | 21 | function start(cb) { 22 | var configs = config.util.getConfigSources(); 23 | configs.forEach(function iterator(c){ 24 | log.info('Loading config ' + c.name); 25 | }); 26 | mongoose.connect(DB); 27 | var db = mongoose.connection; 28 | db.on('error', function dbConnErrCb(err){ 29 | log.error('Database connection error: ' + err); 30 | cb(err); 31 | }); 32 | db.once('open', function openCb() { 33 | log.debug('Connected to database: ' + DB); 34 | swaggerTools.initializeMiddleware(apiDoc, function initSwaggerCb(middleware) { 35 | log.debug('Initialized middleware. Starting app.'); 36 | var app = express(); 37 | app.use(middleware.swaggerMetadata()); // Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain 38 | log.debug('Loaded middleware: swaggerMetadata'); 39 | app.use(middleware.swaggerValidator()); 40 | log.debug('Loaded middleware: swaggerValidator'); 41 | app.use(middleware.swaggerRouter({controllers: './api/controllers'})); 42 | log.debug('Loaded middleware: swaggerRouter'); 43 | app.use(middleware.swaggerUi()); // Swagger documents and Swagger UI 44 | log.debug('Loaded middleware: swaggerUI'); 45 | app.use(errorHandler); 46 | log.debug('Loaded middleware: errorHandler'); 47 | log.info('Service started on ' + PORT); 48 | log.info('API Documentation available at http://' + HOST + ':' + PORT + '/docs'); 49 | server = app.listen(PORT, cb); 50 | }); 51 | }); 52 | } 53 | 54 | function stop(cb){ 55 | log.info('Initiating graceful shutdown'); 56 | try{ 57 | server.close(function serverStopCb() { 58 | mongoose.disconnect(function mongooseStopCb(){ 59 | log.info('Shutdown complete'); 60 | if(cb) { cb(); } 61 | }); 62 | }); 63 | }catch(e){ 64 | log.info('Shutdown complete'); 65 | if(cb) { return cb(e); } 66 | process.exit(1); 67 | } 68 | } 69 | 70 | module.exports = { 71 | start: start, 72 | stop: stop 73 | }; 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/niahmiah/rest-service-boilerplate.svg?branch=master&style=flat-square)](https://travis-ci.org/niahmiah/rest-service-boilerplate) 2 | [![Coverage Status](https://img.shields.io/coveralls/niahmiah/rest-service-boilerplate.svg?style=flat-square)](https://coveralls.io/r/niahmiah/rest-service-boilerplate) 3 | 4 | 5 | # REST Service Boilerplate 6 | 7 | A boilerplate project for scalable REST Services using Node.js, Express, Mongoose, PM2, and Swagger 8 | 9 | ## Inspiration 10 | 11 | There is usually a lot of redundancy in creating a new RESTful API. You create express routes, and you develop schemas. You come up with a way to validate requests and data going to the database. You then have to document all of this, and that documentation can easily become out-of-sync with the actual implementation. This project seeks to correct all of that duplication of effort by using a Swagger document as the "source of truth". The end result is that an API can be developed with a minimal amount of code or effort. 12 | 13 | ## Features 14 | 15 | - Quickly implement your RESTful service. Just modify the swagger.json document, adjust the models and controllers as needed, and start the service. 16 | - Generates express routes using swaggerRouter middleware from [swagger-tools](https://github.com/apigee-127/swagger-tools) 17 | - Validation of requests and responses using swaggerValidator middleware from [swagger-tools](https://github.com/apigee-127/swagger-tools) 18 | - Generates Mongoose schemas from Swagger document using [swagger2mongoose](https://github.com/niahmiah/swagger2mongoose) 19 | - Uses [PM2] (https://github.com/Unitech/pm2) for process management. Scales the service to multiple CPUs. Allows zero downtime reloads. 20 | - Uses [Winston] (https://github.com/winstonjs/winston) logger 21 | - Uses [config] (https://github.com/lorenwest/node-config) for easy multi-environment configuration 22 | 23 | 24 | ## API Documentation and Test Utility 25 | 26 | Start the service, and connect to [http://localhost:3000/docs](http://localhost:3000/docs) to open Swagger UI. 27 | Using Swagger UI, you can: 28 | - View the generated REST API documentation, including request and response schemas 29 | - Test your API and see responses 30 | 31 | ## Mapping requests to controllers 32 | 33 | In the "paths" section of your swagger.json, use the following format to declare the controller and function to use for each request. 34 | 35 | ``` 36 | "x-swagger-router-controller": "person", // this is the controller filename 37 | "operationId": "GET", // this is the function that the controller exports 38 | ``` 39 | 40 | See the included swagger.json for a complete example. 41 | 42 | ##### Controller Files 43 | 44 | server.js initializes the swaggerRouter middleware with controllers directory set as: 45 | ``` 46 | {controllers: './api/controllers'} 47 | ``` 48 | 49 | See [swagger-tools Middleware] (https://github.com/apigee-127/swagger-tools/blob/master/docs/Middleware.md) docs for more info. 50 | 51 | ## Useful Commands 52 | 53 | ``` 54 | # install modules 55 | npm install 56 | 57 | # run tests (mocha, eslint, istanbul) 58 | npm test 59 | npm run lint 60 | npm run coverage 61 | 62 | # manage process (these issue common pm2 commands) 63 | npm start 64 | npm stop 65 | npm run reload 66 | npm run status 67 | ``` 68 | -------------------------------------------------------------------------------- /swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "Person API", 5 | "description": "API for Managing People", 6 | "version": "1.0.0" 7 | }, 8 | "produces": ["application/json"], 9 | "host": "localhost:3000", 10 | "basePath": "/", 11 | "tags": [ 12 | { 13 | "name": "person", 14 | "description": "Person" 15 | } 16 | ], 17 | "paths": { 18 | "/person": { 19 | "get": { 20 | "tags": [ 21 | "person" 22 | ], 23 | "x-swagger-router-controller": "person", 24 | "operationId": "GET", 25 | "description": "Returns a list of persons", 26 | "responses": { 27 | "200": { 28 | "description": "Person list", 29 | "schema": { 30 | "$ref": "#/definitions/Person" 31 | } 32 | }, 33 | "default": { 34 | "description": "Unexpected error", 35 | "schema": { 36 | "$ref": "#/definitions/Error" 37 | } 38 | } 39 | } 40 | }, 41 | "post": { 42 | "tags": [ 43 | "person" 44 | ], 45 | "x-swagger-router-controller": "person", 46 | "operationId": "POST", 47 | "description": "Create person", 48 | "parameters": [ 49 | { 50 | "name": "body", 51 | "in": "body", 52 | "description": "The person details", 53 | "required": true, 54 | "schema": { 55 | "$ref": "#/definitions/Person" 56 | } 57 | } 58 | ], 59 | "responses": { 60 | "201": { 61 | "description": "Newly created person", 62 | "schema": { 63 | "$ref": "#/definitions/Person" 64 | } 65 | }, 66 | "default": { 67 | "description": "Unexpected error", 68 | "schema": { 69 | "$ref": "#/definitions/Error" 70 | } 71 | } 72 | } 73 | } 74 | }, 75 | "/person/{id}": { 76 | "get": { 77 | "tags": [ 78 | "person" 79 | ], 80 | "x-swagger-router-controller": "person", 81 | "operationId": "GET", 82 | "description": "Fetch a person", 83 | "parameters": [ 84 | { 85 | "name": "id", 86 | "in": "path", 87 | "description": "The person ID to fetch", 88 | "required": true, 89 | "type": "string" 90 | } 91 | ], 92 | "responses": { 93 | "200": { 94 | "description": "Person details", 95 | "schema": { 96 | "$ref": "#/definitions/Person" 97 | } 98 | }, 99 | "default": { 100 | "description": "Unexpected error", 101 | "schema": { 102 | "$ref": "#/definitions/Error" 103 | } 104 | } 105 | } 106 | }, 107 | "put": { 108 | "tags": [ 109 | "person" 110 | ], 111 | "x-swagger-router-controller": "person", 112 | "operationId": "PUT", 113 | "description": "Update a person", 114 | "parameters": [ 115 | { 116 | "name": "id", 117 | "in": "path", 118 | "description": "The person ID to update", 119 | "required": true, 120 | "type": "integer" 121 | }, 122 | { 123 | "name": "body", 124 | "in": "body", 125 | "description": "The person details for the update", 126 | "required": true, 127 | "schema": { 128 | "$ref": "#/definitions/Person" 129 | } 130 | } 131 | ], 132 | "responses": { 133 | "200": { 134 | "description": "Updated person details", 135 | "schema": { 136 | "$ref": "#/definitions/Person" 137 | } 138 | }, 139 | "default": { 140 | "description": "Unexpected error", 141 | "schema": { 142 | "$ref": "#/definitions/Error" 143 | } 144 | } 145 | } 146 | } 147 | } 148 | }, 149 | "definitions": { 150 | "Error": { 151 | "required": ["code", "message"], 152 | "properties": { 153 | "code": { 154 | "type": "integer", 155 | "format": "int32" 156 | }, 157 | "message": { 158 | "type": "string" 159 | } 160 | } 161 | }, 162 | "Success": { 163 | "required": ["code", "message"], 164 | "properties": { 165 | "code": { 166 | "type": "integer", 167 | "format": "int32" 168 | }, 169 | "message": { 170 | "type": "string" 171 | } 172 | } 173 | }, 174 | "Person": { 175 | "required": ["name"], 176 | "properties": { 177 | "id": { 178 | "type": "string" 179 | }, 180 | "name": { 181 | "type": "string" 182 | }, 183 | "permissions": { 184 | "type": "array", 185 | "items": { 186 | "type": "string", 187 | "enum": [ 188 | "finance", 189 | "accounts", 190 | "sales", 191 | "leads" 192 | ] 193 | }, 194 | "uniqueItems": true 195 | } 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "rules": { 8 | /** 9 | * Strict mode 10 | */ 11 | // http://eslint.org/docs/rules/strict 12 | "strict": [2, "global"], 13 | 14 | /** 15 | * Variables 16 | */ 17 | "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow 18 | "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names 19 | "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars 20 | "vars": "local", 21 | "args": "after-used" 22 | }], 23 | "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define 24 | 25 | /** 26 | * Possible errors 27 | */ 28 | "comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle 29 | "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign 30 | "no-console": 1, // http://eslint.org/docs/rules/no-console 31 | "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger 32 | "no-alert": 1, // http://eslint.org/docs/rules/no-alert 33 | "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition 34 | "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys 35 | "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case 36 | "no-empty": 2, // http://eslint.org/docs/rules/no-empty 37 | "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign 38 | "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast 39 | "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi 40 | "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign 41 | "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations 42 | "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp 43 | "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace 44 | "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls 45 | "quote-props": 0, // http://eslint.org/docs/rules/quote-props 46 | "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays 47 | "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable 48 | "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan 49 | "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var 50 | 51 | /** 52 | * Best practices 53 | */ 54 | "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return 55 | "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly 56 | "default-case": 2, // http://eslint.org/docs/rules/default-case 57 | "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation 58 | "allowKeywords": true 59 | }], 60 | "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq 61 | "guard-for-in": 2, // http://eslint.org/docs/rules/guard-for-in 62 | "no-caller": 2, // http://eslint.org/docs/rules/no-caller 63 | "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return 64 | "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null 65 | "no-eval": 2, // http://eslint.org/docs/rules/no-eval 66 | "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native 67 | "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind 68 | "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough 69 | "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal 70 | "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval 71 | "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks 72 | "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func 73 | "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str 74 | "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign 75 | "no-new": 2, // http://eslint.org/docs/rules/no-new 76 | "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func 77 | "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers 78 | "no-octal": 2, // http://eslint.org/docs/rules/no-octal 79 | "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape 80 | "no-param-reassign": 1, // http://eslint.org/docs/rules/no-param-reassign 81 | "no-proto": 2, // http://eslint.org/docs/rules/no-proto 82 | "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare 83 | "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign 84 | "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url 85 | "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare 86 | "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences 87 | "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal 88 | "no-with": 2, // http://eslint.org/docs/rules/no-with 89 | "radix": 2, // http://eslint.org/docs/rules/radix 90 | "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife 91 | "yoda": 2, // http://eslint.org/docs/rules/yoda 92 | 93 | /** 94 | * Style 95 | */ 96 | "indent": [2, 2], // http://eslint.org/docs/rules/ 97 | "brace-style": [2, // http://eslint.org/docs/rules/brace-style 98 | "1tbs", { 99 | "allowSingleLine": true 100 | }], 101 | "quotes": [ 102 | 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes 103 | ], 104 | "camelcase": [1, { // http://eslint.org/docs/rules/camelcase 105 | "properties": "never" 106 | }], 107 | "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing 108 | "before": false, 109 | "after": true 110 | }], 111 | "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style 112 | "eol-last": 2, // http://eslint.org/docs/rules/eol-last 113 | "func-names": 1, // http://eslint.org/docs/rules/func-names 114 | "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing 115 | "beforeColon": false, 116 | "afterColon": true 117 | }], 118 | "new-cap": [1, { // http://eslint.org/docs/rules/new-cap 119 | "newIsCap": true 120 | }], 121 | "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines 122 | "max": 2 123 | }], 124 | "no-nested-ternary": 2, // http://eslint.org/docs/rules/no-nested-ternary 125 | "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object 126 | "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func 127 | "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces 128 | "no-extra-parens": 2, // http://eslint.org/docs/rules/no-extra-parens 129 | "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle 130 | "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var 131 | "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks 132 | "semi": [2, "always"], // http://eslint.org/docs/rules/semi 133 | "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing 134 | "before": false, 135 | "after": true 136 | }], 137 | "space-after-keywords": 0, // http://eslint.org/docs/rules/space-after-keywords 138 | "space-before-blocks": 0, // http://eslint.org/docs/rules/space-before-blocks 139 | "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren 140 | "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops 141 | "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case 142 | "spaced-comment": 2, // http://eslint.org/docs/rules/spaced-comment 143 | 144 | /** 145 | * Other 146 | */ 147 | "no-path-concat": 0, // http://eslint.org/docs/rules/no-path-concat 148 | } 149 | } 150 | --------------------------------------------------------------------------------