├── .gitignore ├── server ├── Procfile ├── .env.example ├── .gitignore ├── config │ ├── .env │ ├── db.conf.js │ ├── routes.conf.js │ ├── app.conf.js │ └── socket.conf.js ├── .babelrc ├── .editorconfig ├── .jshintrc-spec ├── .jshintrc ├── index.js ├── mocha.conf.js ├── Dockerfile ├── api │ ├── service │ │ ├── index.js │ │ ├── service.events.js │ │ ├── service.socket.js │ │ ├── service.model.js │ │ ├── index.spec.js │ │ ├── service.controller.js │ │ └── service.integration.js │ └── utils.js ├── app.js ├── package.json └── gulpfile.babel.js ├── logo.jpg ├── nginx ├── Dockerfile └── nginx.conf ├── ISSUE_TEMPLATE.md ├── .travis.yml ├── PULL_REQUEST_TEMPLATE.md ├── LICENSE ├── docker-compose.yml ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /server/Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | PORT= 2 | IP= 3 | NODE_ENV= 4 | MONGODB_URI= 5 | -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abdizriel/nodejs-microservice-starter/HEAD/logo.jpg -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | coverage 5 | server/config/.env 6 | -------------------------------------------------------------------------------- /server/config/.env: -------------------------------------------------------------------------------- 1 | PORT=9000 2 | IP='0.0.0.0' 3 | NODE_ENV=production 4 | MONGODB_URI= 5 | -------------------------------------------------------------------------------- /server/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "plugins": ["transform-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | # Set nginx base image 2 | FROM nginx 3 | 4 | # File Author / Maintainer 5 | MAINTAINER Marcin Mrotek 6 | 7 | # Copy custom configuration file from the current directory 8 | COPY nginx.conf /etc/nginx/nginx.conf 9 | -------------------------------------------------------------------------------- /server/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected behaviour 2 | 3 | ### Actual behaviour 4 | 5 | ### Steps to reproduce 6 | 7 | ### Additional informations 8 | 9 | Item | Version 10 | ----- | ----- 11 | Node | x.x.x 12 | npm | x.x.x 13 | Operating System | OS X 10 / Windows 10 / Ubuntu 15.10 / etc 14 | etc | etc 15 | -------------------------------------------------------------------------------- /server/.jshintrc-spec: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ".jshintrc", 3 | "globals": { 4 | "describe": true, 5 | "it": true, 6 | "before": true, 7 | "beforeEach": true, 8 | "after": true, 9 | "afterEach": true, 10 | "expect": true, 11 | "assert": true, 12 | "sinon": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "expr": true, 3 | "node": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "eqeqeq": true, 7 | "immed": true, 8 | "latedef": "nofunc", 9 | "newcap": true, 10 | "noarg": true, 11 | "undef": true, 12 | "smarttabs": true, 13 | "asi": true, 14 | "debug": true 15 | } 16 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Set default node environment to development 4 | var env = process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 5 | 6 | if (env === 'development' || env === 'test') { 7 | // Register the Babel require hook 8 | require('babel-register'); 9 | } 10 | // Export the application 11 | exports = module.exports = require('./app'); 12 | -------------------------------------------------------------------------------- /server/config/db.conf.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import bluebird from 'bluebird'; 3 | 4 | mongoose.Promise = bluebird; 5 | 6 | export default class DBConfig { 7 | static init() { 8 | mongoose.connect(process.env.MONGODB_URI); 9 | mongoose.connection.on('error', err => { 10 | console.error(`MongoDB connection error: ${err}`); 11 | process.exit(-1); 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server/config/routes.conf.js: -------------------------------------------------------------------------------- 1 | import ServiceRoutes from '../api/service' 2 | 3 | export function initRoutes (app) { 4 | const startTime = new Date(); 5 | 6 | // Insert routes below 7 | app.use('/api/services', ServiceRoutes); 8 | 9 | app.route('/*') 10 | .get((req, res) => { 11 | const uptime = `${new Date() - startTime}ms`; 12 | res.status(200).json({ startTime, uptime }); 13 | } 14 | ); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "6" 5 | 6 | env: 7 | global: 8 | - NODE_ENV=test 9 | - MONGODB_URI=mongodb://localhost/service 10 | 11 | before_script: 12 | - "cd server" 13 | - "npm install" 14 | - "export DISPLAY=:99.0" 15 | - "sh -e /etc/init.d/xvfb start" 16 | 17 | script: 18 | - npm run coveralls 19 | 20 | after_success: 21 | - bash <(curl -s https://codecov.io/bash) 22 | 23 | services: mongodb 24 | -------------------------------------------------------------------------------- /server/mocha.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Register the Babel require hook 4 | require('babel-core/register'); 5 | 6 | var chai = require('chai'); 7 | 8 | // Load Chai assertions 9 | global.expect = chai.expect; 10 | global.assert = chai.assert; 11 | chai.should(); 12 | 13 | // Load Sinon 14 | global.sinon = require('sinon'); 15 | 16 | // Initialize Chai plugins 17 | chai.use(require('sinon-chai')); 18 | chai.use(require('chai-as-promised')); 19 | chai.use(require('chai-things')); 20 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Set the base image to Node 2 | FROM node 3 | 4 | # File Author / Maintainer 5 | MAINTAINER Marcin Mrotek 6 | 7 | # Provides cached layer for node_modules 8 | ADD package.json /tmp/package.json 9 | RUN cd /tmp && npm install 10 | RUN mkdir -p /src && cp -a /tmp/node_modules /src/ 11 | RUN npm install pm2 -g 12 | 13 | # Define working directory 14 | WORKDIR /src 15 | ADD . /src 16 | 17 | # Expose port 18 | EXPOSE 9000 19 | 20 | # Run app using pm2 21 | CMD ["npm", "run", "pm2"] 22 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | @Abdizriel 2 | 3 | PR to 4 | 5 | For issue: https://github.com/Abdizriel/nodejs-microservice-starter/issues/ 6 | 7 | - [ ] I have read the [Contributing Documents](https://github.com/Abdizriel/nodejs-microservice-starter/blob/master/CONTRIBUTING.md) 8 | - [ ] My commit(s) follow the [commit message guidelines](https://github.com/Abdizriel/nodejs-microservice-starter/blob/master/CONTRIBUTING.md#commit-message-format) 9 | - [ ] Project tests pass (`npm test`) 10 | - [ ] Project tests coverage is at least **90%** 11 | -------------------------------------------------------------------------------- /server/config/app.conf.js: -------------------------------------------------------------------------------- 1 | import morgan from 'morgan'; 2 | import bodyParser from 'body-parser'; 3 | import contentLength from 'express-content-length-validator'; 4 | import helmet from 'helmet'; 5 | import express from 'express'; 6 | 7 | export default class ApplicationConfig { 8 | static init(app) { 9 | let _root = process.cwd(); 10 | let _nodeModules = '/node_modules/'; 11 | 12 | app.use(express.static(_root + _nodeModules)); 13 | app.use(bodyParser.json()); 14 | app.use(morgan('dev')); 15 | app.use(contentLength.validateMax({max: 999})); 16 | app.use(helmet()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Marcin Mrotek 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 4; 2 | 3 | events { worker_connections 1024; } 4 | 5 | http { 6 | 7 | upstream microservice { 8 | least_conn; 9 | server microservice1:9000 weight=10 max_fails=3 fail_timeout=30s; 10 | server microservice2:9000 weight=10 max_fails=3 fail_timeout=30s; 11 | server microservice3:9000 weight=10 max_fails=3 fail_timeout=30s; 12 | } 13 | 14 | server { 15 | listen 80; 16 | 17 | location / { 18 | proxy_pass http://microservice; 19 | proxy_http_version 1.1; 20 | proxy_set_header Upgrade $http_upgrade; 21 | proxy_set_header Connection 'upgrade'; 22 | proxy_set_header Host $host; 23 | proxy_cache_bypass $http_upgrade; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/api/service/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @description Express Framework Router 5 | * @param Router 6 | */ 7 | import { Router } from 'express'; 8 | 9 | /** 10 | * @description Service route Controller 11 | * @param ServiceController 12 | */ 13 | import * as ServiceController from './service.controller'; 14 | 15 | let router = new Router(); 16 | 17 | router.get('/', ServiceController.index); 18 | router.get('/:id', ServiceController.show); 19 | router.post('/', ServiceController.create); 20 | router.put('/:id', ServiceController.update); 21 | router.patch('/:id', ServiceController.update); 22 | router.delete('/:id', ServiceController.destroy); 23 | 24 | /** 25 | * @description Configured router for Service Routes 26 | * @exports router 27 | * @default 28 | */ 29 | export default router; 30 | -------------------------------------------------------------------------------- /server/config/socket.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import ServiceSockets from '../api/service/service.socket'; 4 | 5 | // When the user disconnects.. perform this 6 | function onDisconnect(socket) { 7 | } 8 | 9 | // When the user connects.. perform this 10 | function onConnect(socket) { 11 | // When the client emits 'info', this listens and executes 12 | socket.on('info', data => { 13 | socket.log(JSON.stringify(data, null, 2)); 14 | }); 15 | 16 | ServiceSockets(socket); 17 | } 18 | 19 | function initSocket (socketio) { 20 | socketio.on('connection', socket => { 21 | socket.address = socket.request.connection.remoteAddress + 22 | ':' + socket.request.connection.remotePort; 23 | 24 | socket.connectedAt = new Date(); 25 | 26 | socket.log = (...data) => { 27 | console.log(`SocketIO ${socket.nsp.name} [${socket.address}]`, ...data); 28 | }; 29 | 30 | // Call onDisconnect. 31 | socket.on('disconnect', () => { 32 | onDisconnect(socket); 33 | socket.log('DISCONNECTED'); 34 | }); 35 | 36 | // Call onConnect. 37 | onConnect(socket); 38 | socket.log('CONNECTED'); 39 | }); 40 | } 41 | 42 | export { 43 | initSocket 44 | } 45 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Reverse Proxy 2 | nginx: 3 | build: ./nginx 4 | links: 5 | - microservice1:microservice1 6 | - microservice2:microservice2 7 | - microservice3:microservice3 8 | ports: 9 | - "80:80" 10 | 11 | # NodeJS RESTful API Microservice 12 | microservice1: 13 | build: ./server/dist 14 | links: 15 | - mongo:mongo 16 | ports: 17 | - "9001:9000" 18 | environment: 19 | NODE_ENV: production 20 | MONGODB_URI: mongodb://mongo/service 21 | IP: 0.0.0.0 22 | PORT: 9000 23 | microservice2: 24 | build: ./server/dist 25 | links: 26 | - mongo:mongo 27 | ports: 28 | - "9002:9000" 29 | environment: 30 | NODE_ENV: production 31 | MONGODB_URI: mongodb://mongo/service 32 | IP: 0.0.0.0 33 | PORT: 9000 34 | microservice3: 35 | build: ./server/dist 36 | links: 37 | - mongo:mongo 38 | ports: 39 | - "9003:9000" 40 | environment: 41 | NODE_ENV: production 42 | MONGODB_URI: mongodb://mongo/service 43 | IP: 0.0.0.0 44 | PORT: 9000 45 | 46 | # MongoDB 47 | mongo: 48 | image: mongo 49 | ports: 50 | - "27017:27017" 51 | -------------------------------------------------------------------------------- /server/api/service/service.events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @description Events Emitter 5 | * @param EventEmitter 6 | */ 7 | import { EventEmitter } from 'events'; 8 | 9 | /** 10 | * @description Service MongoDB schema 11 | * @param Service 12 | */ 13 | import Service from './service.model'; 14 | 15 | /** 16 | * @description Service Events Emitter 17 | * @param ServiceEvents 18 | */ 19 | const ServiceEvents = new EventEmitter(); 20 | 21 | ServiceEvents.setMaxListeners(0); 22 | 23 | /** 24 | * @description Events to listen on 25 | * @param events 26 | */ 27 | const events = { 28 | 'afterCreate': 'save', 29 | 'afterUpdate': 'save', 30 | 'afterDestroy': 'remove' 31 | }; 32 | 33 | /** 34 | * @description Emit correct event on hooks 35 | */ 36 | for (const e in events) { 37 | const event = events[e]; 38 | Service.schema.post(e, emitEvent(event)); 39 | } 40 | 41 | /** 42 | * @description Emit correct event 43 | * @function emitEvent 44 | * @function emitEvent 45 | * @param event - Event to emit 46 | */ 47 | function emitEvent(event) { 48 | return (doc, options, done) => { 49 | ServiceEvents.emit(event + ':' + doc._id, doc); 50 | ServiceEvents.emit(event, doc); 51 | done(null); 52 | } 53 | } 54 | 55 | /** 56 | * @export ServiceEvents 57 | * @default 58 | */ 59 | export default ServiceEvents; 60 | -------------------------------------------------------------------------------- /server/api/service/service.socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @description Service Events Emitter 5 | * @param ServiceEvents 6 | */ 7 | import ServiceEvents from './service.events'; 8 | 9 | /** 10 | * @description Service Model Events to emit 11 | * @param events 12 | */ 13 | const events = ['save', 'remove']; 14 | 15 | /** 16 | * @description Broadcast events to client 17 | * @function register 18 | * @param socket - Socket library 19 | */ 20 | function register(socket) { 21 | for (let i = 0, eventsLength = events.length; i < eventsLength; i++) { 22 | const event = events[i]; 23 | const listener = createListener('service:' + event, socket); 24 | 25 | ServiceEvents.on(event, listener); 26 | socket.on('disconnect', removeListener(event, listener)); 27 | } 28 | } 29 | 30 | /** 31 | * @description Emit Model event to client 32 | * @function createListener 33 | * @param event - Model event 34 | * @param socket - Socket library 35 | */ 36 | function createListener(event, socket) { 37 | return doc => { 38 | socket.emit(event, doc); 39 | }; 40 | } 41 | 42 | /** 43 | * @description Remove event emitter to client 44 | * @function removeListener 45 | * @param event - Model event 46 | * @param listener - Socket Listener 47 | */ 48 | function removeListener(event, listener) { 49 | return () => { 50 | ServiceEvents.removeListener(event, listener); 51 | }; 52 | } 53 | 54 | /** 55 | * @export register 56 | * @default 57 | */ 58 | export default register; 59 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description HTTP server module 3 | * @param http 4 | */ 5 | import http from 'http'; 6 | 7 | /** 8 | * @description Express Framework module 9 | * @param express 10 | */ 11 | import express from 'express'; 12 | 13 | /** 14 | * @description Configure env variables 15 | * @param config 16 | */ 17 | import dotenv from 'dotenv-safe' 18 | dotenv.load({ 19 | path: `${__dirname}/config/.env`, 20 | sample: `${__dirname}/.env.example`, 21 | allowEmptyValues: false 22 | }); 23 | 24 | /** 25 | * @description Database config class 26 | * @param DBConfig 27 | */ 28 | import DBConfig from './config/db.conf'; 29 | 30 | /** 31 | * @description Routes config class 32 | * @param Routes 33 | */ 34 | import { initRoutes } from './config/routes.conf'; 35 | 36 | /** 37 | * @description IApplication config class 38 | * @param Routes 39 | */ 40 | import ApplicationConfig from './config/app.conf'; 41 | 42 | /** 43 | * @description Create application with Express Framework 44 | * @param app 45 | */ 46 | const app = express(); 47 | 48 | /** 49 | * @description Create application server 50 | * @param server 51 | */ 52 | const server = http.createServer(app); 53 | 54 | /** 55 | * @description Configure Database 56 | */ 57 | DBConfig.init(); 58 | 59 | /** 60 | * @description Configure Application 61 | */ 62 | ApplicationConfig.init(app); 63 | 64 | /** 65 | * @description Configure Routes 66 | */ 67 | initRoutes(app); 68 | 69 | /** 70 | * @function startServer 71 | * @description Start API Server 72 | */ 73 | const startServer = () => { 74 | server.listen(process.env.PORT, process.env.IP, () => { 75 | console.log('Express server listening on %s:%s in %s mode', process.env.IP, process.env.PORT, process.env.NODE_ENV); 76 | }); 77 | }; 78 | 79 | /** 80 | * @description Starting API Server after everythin is set up 81 | */ 82 | setImmediate(startServer); 83 | 84 | /** 85 | * @description Application object 86 | * @module app 87 | */ 88 | module.exports = app; 89 | -------------------------------------------------------------------------------- /server/api/service/service.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @description MongoDB connector 5 | * @param mongoose 6 | */ 7 | import mongoose from 'mongoose'; 8 | 9 | /** 10 | * @description Promise library 11 | * @param Promise 12 | */ 13 | import Promise from 'bluebird'; 14 | 15 | /** 16 | * @description MongoDB Schema 17 | * @param Schema 18 | */ 19 | import { Schema } from 'mongoose'; 20 | 21 | // Apply bluebird Promise as Mongoose Promise library 22 | mongoose.Promise = Promise; 23 | 24 | /** 25 | * @description Service MongoDB Schema 26 | * @param ServiceSchema 27 | * @const 28 | */ 29 | const ServiceSchema = new Schema({ 30 | name: { 31 | type: String, 32 | required: true, 33 | trim: true 34 | }, 35 | createdAt: { 36 | type: Date, 37 | default: Date.now 38 | }, 39 | updatedAt: { 40 | type: Date 41 | } 42 | }); 43 | 44 | /** 45 | * @description Virtual Method that returns service data 46 | */ 47 | ServiceSchema 48 | .virtual('service') 49 | .get(function () { 50 | return { 51 | 'id': this._id, 52 | 'name': this.name 53 | }; 54 | }); 55 | 56 | /** 57 | * @description Validate if name field is not empty 58 | */ 59 | ServiceSchema 60 | .path('name') 61 | .validate(name => name.length, 'Name cannot be empty'); 62 | 63 | /** 64 | * @description Validate if name is not taken 65 | */ 66 | ServiceSchema 67 | .path('name') 68 | .validate(function (name, respond) { 69 | const self = this; 70 | return self.constructor.findOne({ name }).exec() 71 | .then(name => { 72 | if (name) return respond(false); 73 | return respond(true); 74 | }) 75 | }, 'The specified name is already in use.'); 76 | 77 | /** 78 | * @description Every update set new updatedAt date 79 | */ 80 | ServiceSchema 81 | .post('update', function () { 82 | this.update({},{ 83 | $set: { 84 | updatedAt: new Date() 85 | } 86 | }); 87 | }); 88 | 89 | /** 90 | * @exports serviceSchema 91 | * @default 92 | */ 93 | export default mongoose.model('Service', ServiceSchema); 94 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project has 2 main branches: `master` and `development`. The `master` branch is where the current stable code lives and should be used for production setups. The `development` branch is the main development branch, this is where PRs should be submitted to (backport fixes may be applied to `master`). 4 | 5 | By separating the current stable code from the cutting-edge development I hope to provide a stable and efficient workflow for users and developers alike. 6 | 7 | Please submit PRs to the `development` branch, it is the main development branch for this project. 8 | 9 | When submitting a bugfix, try to write a test that exposes the bug and fails before applying your fix. Submit the test alongside the fix. 10 | 11 | When submitting a new feature, add tests that cover the feature. 12 | 13 | Open [GitHub Issue](https://github.com/Abdizriel/nodejs-microservice-starter/issues) to spy progress. 14 | 15 | Create new branch with following pattern issue-xx-zz where xx is GitHub issue number and zz is short description. 16 | 17 | ## Git Commit Guidelines 18 | 19 | ### Commit Message Format 20 | Each commit message consists of a **issue**, a **type** and a **subject**. 21 | 22 | ``` 23 | [][] 24 | ``` 25 | 26 | Any line of the commit message cannot be longer 100 characters! This allows the message to be easier 27 | to read on github as well as in various git tools. 28 | 29 | ### Issue 30 | The number generated for story, bug etc... on GitHub. 31 | 32 | ### Type 33 | Must be one of the following: 34 | 35 | * **ADD**: Add something to code 36 | * **FIX**: A bug fix 37 | * **DOCS**: Documentation changes 38 | * **UPDATE**: A update of code that is not a bug fix 39 | * **DELETE**: Remove of file or directory 40 | * **TEST**: Adding missing tests 41 | * **CHORE**: Changes to the build process or auxiliary tools and libraries such as documentation 42 | generation 43 | 44 | ### Subject 45 | The subject contains succinct description of the change: 46 | 47 | * use the imperative, present tense: "change" not "changed" nor "changes" 48 | * capitalize first letter 49 | * no dot (.) at the end 50 | -------------------------------------------------------------------------------- /server/api/service/index.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let proxyquire = require('proxyquire').noPreserveCache(); 4 | 5 | const serviceCtrlStub = { 6 | index: 'serviceCtrl.index', 7 | show: 'serviceCtrl.show', 8 | create: 'serviceCtrl.create', 9 | update: 'serviceCtrl.update', 10 | destroy: 'serviceCtrl.destroy' 11 | }; 12 | 13 | const routerStub = { 14 | get: sinon.spy(), 15 | put: sinon.spy(), 16 | patch: sinon.spy(), 17 | post: sinon.spy(), 18 | delete: sinon.spy() 19 | }; 20 | 21 | // require the index with our stubbed out modules 22 | const serviceIndex = proxyquire('./index.js', { 23 | 'express': { 24 | Router: function() { 25 | return routerStub; 26 | } 27 | }, 28 | './service.controller': serviceCtrlStub 29 | }); 30 | 31 | describe('Service API Router:', function() { 32 | 33 | describe('GET /api/services', function() { 34 | 35 | it('should route to service.controller.index', function() { 36 | expect(routerStub.get 37 | .withArgs('/', 'serviceCtrl.index') 38 | ).to.have.been.calledOnce; 39 | }); 40 | 41 | }); 42 | 43 | describe('GET /api/services/:id', function() { 44 | 45 | it('should route to service.controller.show', function() { 46 | expect(routerStub.get 47 | .withArgs('/:id', 'serviceCtrl.show') 48 | ).to.have.been.calledOnce; 49 | }); 50 | 51 | }); 52 | 53 | describe('POST /api/services', function() { 54 | 55 | it('should route to service.controller.create', function() { 56 | expect(routerStub.post 57 | .withArgs('/', 'serviceCtrl.create') 58 | ).to.have.been.calledOnce; 59 | }); 60 | 61 | }); 62 | 63 | describe('PUT /api/services/:id', function() { 64 | 65 | it('should route to service.controller.update', function() { 66 | expect(routerStub.put 67 | .withArgs('/:id', 'serviceCtrl.update') 68 | ).to.have.been.calledOnce; 69 | }); 70 | 71 | }); 72 | 73 | describe('PATCH /api/services/:id', function() { 74 | 75 | it('should route to service.controller.update', function() { 76 | expect(routerStub.patch 77 | .withArgs('/:id', 'serviceCtrl.update') 78 | ).to.have.been.calledOnce; 79 | }); 80 | 81 | }); 82 | 83 | describe('DELETE /api/services/:id', function() { 84 | 85 | it('should route to service.controller.destroy', function() { 86 | expect(routerStub.delete 87 | .withArgs('/:id', 'serviceCtrl.destroy') 88 | ).to.have.been.calledOnce; 89 | }); 90 | 91 | }); 92 | 93 | }); 94 | -------------------------------------------------------------------------------- /server/api/utils.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | /** 4 | * @function respondWithResult 5 | * @description Function that returns response with data 6 | * @param {Object} res - Express Framework Response Object 7 | * @param {Number=} statusCode - Response status code 8 | */ 9 | function respondWithResult(res, statusCode) { 10 | statusCode = statusCode || 200; 11 | return entity => { 12 | if (entity) { 13 | return res.status(statusCode).json(entity); 14 | } 15 | }; 16 | } 17 | 18 | /** 19 | * @function saveUpdates 20 | * @description Function that updates entity with new data 21 | * @param {Object} updates - Updated data 22 | */ 23 | 24 | function saveUpdates(updates) { 25 | return entity => { 26 | const updated = _.merge(entity, updates); 27 | return updated.save() 28 | .then(updated => updated); 29 | }; 30 | } 31 | 32 | /** 33 | * @function removeEntity 34 | * @description Function that remove entity from Schema 35 | * @param {Object} res - Express Framework Response Object 36 | */ 37 | function removeEntity(res) { 38 | return entity => { 39 | if (entity) { 40 | return entity.remove() 41 | .then(() => { 42 | res.status(204).end(); 43 | }); 44 | } 45 | }; 46 | } 47 | 48 | /** 49 | * @function handleEntityNotFound 50 | * @description Function that handle entity not found respond 51 | * @param {Object} res - Express Framework Response Object 52 | */ 53 | function handleEntityNotFound(res) { 54 | return entity => { 55 | if (!entity) { 56 | res.status(404).end(); 57 | return null; 58 | } 59 | return entity; 60 | }; 61 | } 62 | 63 | /** 64 | * @function handleError 65 | * @description Function that returns response with error details 66 | * @param {Object} res - Express Framework Response Object 67 | * @param {Number=} statusCode - Response status code 68 | */ 69 | function handleError(res, statusCode) { 70 | statusCode = statusCode || 500; 71 | return err => res.status(statusCode).send(err); 72 | } 73 | 74 | /** 75 | * @function validationError 76 | * @description Function that returns response with model validation error details 77 | * @param {Object} res - Express Framework Response Object 78 | * @param {Number=} statusCode - Response status code 79 | */ 80 | function validationError(res, statusCode) { 81 | statusCode = statusCode || 422; 82 | return err => res.status(statusCode).json(err); 83 | } 84 | 85 | export { 86 | validationError, 87 | handleError, 88 | handleEntityNotFound, 89 | removeEntity, 90 | saveUpdates, 91 | respondWithResult 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![NodeJS RESTful API Microservice Logo](https://github.com/Abdizriel/nodejs-microservice-starter/blob/master/logo.jpg) 2 | 3 | # NodeJS RESTful API Microservice Starter v1.2.0 4 | This repository contains a full configuration that runs NodeJS RESTful API Microservice Starter. 5 | 6 | [![Build Status](https://secure.travis-ci.org/Abdizriel/nodejs-microservice-starter.png?branch=master)](https://travis-ci.org/Abdizriel/nodejs-microservice-starter) 7 | [![Coverage Status](https://coveralls.io/repos/github/Abdizriel/nodejs-microservice-starter/badge.svg?branch=master)](https://coveralls.io/github/Abdizriel/nodejs-microservice-starter?branch=master) 8 | [![Dependency Status](https://img.shields.io/david/Abdizriel/nodejs-microservice-starter.svg)](https://david-dm.org/Abdizriel/nodejs-microservice-starter) 9 | [![Dev-Dependency Status](https://img.shields.io/david/dev/Abdizriel/nodejs-microservice-starter.svg)](https://david-dm.org/Abdizriel/nodejs-microservice-starter#info=devDependencies) 10 | 11 | ## Requirements 12 | 13 | * [MongoDB](https://www.mongodb.com/download-center "MongoDB") 14 | * [NodeJS](https://nodejs.org/en/download "NodeJS") 15 | 16 | ## Build for local development 17 | 18 | You have to use the following command to start a development server: 19 | 20 | ```sh 21 | npm run dev 22 | ``` 23 | 24 | See `package.json` for more details. 25 | 26 | ## Build for staging and production environments 27 | 28 | Use following command to build project: 29 | 30 | ```sh 31 | npm run build 32 | ``` 33 | 34 | Use following command to start project on staging and production environments: 35 | 36 | ```sh 37 | npm start 38 | ``` 39 | 40 | See `package.json` for more details. 41 | 42 | ## Tests 43 | 44 | Following tests libraries are used for unit/integration tests: 45 | * [MochaJS](https://mochajs.org "MochaJS") 46 | * [SinonJS](http://sinonjs.org "SinonJS") 47 | * [ChaiJS](http://chaijs.com/ "ChaiJS") 48 | 49 | Tests are kept next to source with following pattern *.spec.js 50 | 51 | Use following command to run tests: 52 | 53 | ```sh 54 | npm test 55 | ``` 56 | 57 | Use following command to run tests coverage: 58 | 59 | ```sh 60 | npm run coverage 61 | ``` 62 | 63 | ## Docker container 64 | 65 | There is available Docker container and Docker Composer if you would like to run many NodeJS Microservices. 66 | 67 | Build API Microservice by using following command: 68 | 69 | ```sh 70 | npm run build 71 | ``` 72 | 73 | Then use following command to build Docker containers: 74 | 75 | ```sh 76 | docker-compose up -d --build 77 | ``` 78 | 79 | See `Dockerfile` and `docker-compose.yml` for more details. 80 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-microservice-starter", 3 | "version": "1.2.0", 4 | "description": "NodeJS RESTful API Microservice", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "pm2 start index.js -i 4 --name=\"microservice\" --no-daemon", 8 | "dev": "gulp serve", 9 | "dist": "gulp serve:dist", 10 | "build": "gulp build", 11 | "test": "gulp test", 12 | "coverage": "gulp mocha:coverage", 13 | "coveralls": "gulp mocha:coveralls" 14 | }, 15 | "author": "Marcin Mrotek ", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "autoprefixer": "^6.0.0", 19 | "babel-cli": "^6.3.15", 20 | "babel-core": "^6.6.5", 21 | "babel-plugin-transform-class-properties": "^6.6.0", 22 | "babel-plugin-transform-runtime": "^6.6.0", 23 | "babel-preset-es2015": "^6.3.13", 24 | "babel-preset-stage-0": "^6.3.13", 25 | "babel-register": "^6.6.5", 26 | "chai": "^3.5.0", 27 | "chai-as-promised": "^5.1.0", 28 | "chai-things": "^0.2.0", 29 | "connect-livereload": "^0.5.3", 30 | "coveralls": "^2.11.11", 31 | "del": "^2.0.2", 32 | "gulp": "^3.9.1", 33 | "gulp-add-src": "^0.2.0", 34 | "gulp-autoprefixer": "^3.1.0", 35 | "gulp-babel": "^6.1.2", 36 | "gulp-cache": "^0.4.2", 37 | "gulp-concat": "^2.6.0", 38 | "gulp-coveralls": "^0.1.4", 39 | "gulp-env": "^0.4.0", 40 | "gulp-filter": "^4.0.0", 41 | "gulp-istanbul": "~0.10.3", 42 | "gulp-istanbul-enforcer": "^1.0.3", 43 | "gulp-jscs": "^3.0.2", 44 | "gulp-jshint": "^2.0.0", 45 | "gulp-livereload": "^3.8.0", 46 | "gulp-load-plugins": "^1.0.0-rc.1", 47 | "gulp-mocha": "^2.1.3", 48 | "gulp-node-inspector": "^0.1.0", 49 | "gulp-plumber": "^1.0.1", 50 | "gulp-rename": "^1.2.2", 51 | "gulp-rev": "^7.0.0", 52 | "gulp-rev-replace": "^0.4.2", 53 | "gulp-sort": "^2.0.0", 54 | "gulp-sourcemaps": "^1.5.2", 55 | "gulp-svgmin": "^1.1.2", 56 | "gulp-uglify": "^1.2.0", 57 | "gulp-useref": "^3.0.3", 58 | "gulp-util": "^3.0.5", 59 | "gulp-watch": "^4.3.5", 60 | "isparta": "^4.0.0", 61 | "istanbul": "^1.1.0-alpha.1", 62 | "jshint": "2.9.2", 63 | "jshint-stylish": "^2.2.0", 64 | "lazypipe": "^1.0.1", 65 | "mocha": "^2.5.3", 66 | "nodemon": "^1.8.1", 67 | "proxyquire": "^1.7.9", 68 | "run-sequence": "^1.1.0", 69 | "sinon": "^1.17.4", 70 | "sinon-chai": "^2.8.0", 71 | "supertest": "^1.2.0" 72 | }, 73 | "dependencies": { 74 | "bluebird": "^3.4.1", 75 | "body-parser": "^1.15.2", 76 | "dotenv-safe": "^2.3.1", 77 | "express": "^4.14.0", 78 | "express-content-length-validator": "^1.0.0", 79 | "helmet": "^2.1.1", 80 | "lodash": "^4.13.1", 81 | "mongoose": "^4.5.2", 82 | "morgan": "^1.7.0", 83 | "pm2": "^1.1.3" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /server/api/service/service.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * GET /api/service -> index 3 | * POST /api/service -> create 4 | * GET /api/service/:id -> show 5 | * PUT /api/service/:id -> update 6 | * PATCH /api/service/:id -> update 7 | * DELETE /api/service/:id -> destroy 8 | */ 9 | 10 | /** 11 | * @description Service Model 12 | * @param Service 13 | */ 14 | import Service from './service.model'; 15 | 16 | /** 17 | * @description API Response Utility functions 18 | * @param {Function} validationError - Check for Model validation errors 19 | * @param handleError - Handle errors 20 | * @param handleEntityNotFound - Handle Entity not found error 21 | * @param removeEntity - Remove entity from DB 22 | * @param saveUpdates - Save entity updates to DB 23 | * @param respondWithResult - Respond with DB results 24 | */ 25 | import { 26 | validationError, 27 | handleError, 28 | handleEntityNotFound, 29 | removeEntity, 30 | saveUpdates, 31 | respondWithResult 32 | } from '../utils'; 33 | 34 | /** 35 | * @function index 36 | * @description Function that returns all allocations 37 | * @param {Object} req - Express Framework Request Object 38 | * @param {Object} res - Express Framework Response Object 39 | */ 40 | function index(req, res) { 41 | return Service.find() 42 | .then(respondWithResult(res)) 43 | .catch(handleError(res)); 44 | } 45 | 46 | /** 47 | * @function show 48 | * @description Function that returns single user by id provided in url 49 | * @param {Object} req - Express Framework Request Object 50 | * @param {Object} res - Express Framework Response Object 51 | */ 52 | function show(req, res) { 53 | return Service.findById(req.params.id) 54 | .then(handleEntityNotFound(res)) 55 | .then(respondWithResult(res)) 56 | .catch(handleError(res)); 57 | } 58 | 59 | /** 60 | * @function create 61 | * @description Function that create user by provided request body 62 | * @param {Object} req - Express Framework Request Object 63 | * @param {Object} res - Express Framework Response Object 64 | */ 65 | function create(req, res) { 66 | return Service.create(req.body) 67 | .then(respondWithResult(res, 201)) 68 | .catch(validationError(res)); 69 | } 70 | 71 | /** 72 | * @function update 73 | * @description Function that update user by provided id in url and updated data in request body 74 | * @param {Object} req - Express Framework Request Object 75 | * @param {Object} res - Express Framework Response Object 76 | */ 77 | function update(req, res) { 78 | if (req.body._id) { 79 | delete req.body._id; 80 | } 81 | 82 | return Service.findById(req.params.id) 83 | .then(handleEntityNotFound(res)) 84 | .then(saveUpdates(req.body)) 85 | .then(respondWithResult(res)) 86 | .catch(validationError(res)); 87 | } 88 | 89 | /** 90 | * @function destroy 91 | * @description Function that delete user by id provided in url 92 | * @param {Object} req - Express Framework Request Object 93 | * @param {Object} res - Express Framework Response Object 94 | */ 95 | function destroy(req, res) { 96 | return Service.findById(req.params.id) 97 | .then(handleEntityNotFound(res)) 98 | .then(removeEntity(res)) 99 | .catch(handleError(res)); 100 | } 101 | 102 | export { index, show, create, update, destroy } 103 | -------------------------------------------------------------------------------- /server/api/service/service.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import request from 'supertest'; 4 | import { expect } from 'chai'; 5 | import Service from './service.model'; 6 | 7 | let app = require('../..'); 8 | let newService; 9 | 10 | describe('Service API:', () => { 11 | 12 | before(done => { 13 | Service.remove({}) 14 | .then(() => done()) 15 | }); 16 | 17 | describe('GET /api/services', () => { 18 | let services; 19 | 20 | beforeEach(done => { 21 | request(app) 22 | .get('/api/services') 23 | .expect(200) 24 | .expect('Content-Type', /json/) 25 | .end((err, res) => { 26 | if (err) return done(err); 27 | services = res.body; 28 | done(); 29 | }); 30 | }); 31 | 32 | it('should respond with JSON array', () => { 33 | expect(services).to.be.instanceOf(Array); 34 | }); 35 | 36 | }); 37 | 38 | describe('POST /api/services', () => { 39 | beforeEach(done => { 40 | request(app) 41 | .post('/api/services') 42 | .send({ 43 | name: 'Service Test' 44 | }) 45 | .expect(201) 46 | .expect('Content-Type', /json/) 47 | .end((err, res) => { 48 | if (err) return done(err); 49 | newService = res.body; 50 | done(); 51 | }); 52 | }); 53 | 54 | it('should respond with the newly created service', () => { 55 | expect(newService).to.be.instanceOf(Object); 56 | expect(newService).ownProperty('_id'); 57 | expect(newService._id).to.not.be.undefined; 58 | expect(newService._id).to.not.be.null; 59 | expect(newService).ownProperty('name'); 60 | expect(newService.name).to.equal('Service Test'); 61 | expect(newService).ownProperty('createdAt'); 62 | expect(newService.createdAt).to.not.be.undefined; 63 | expect(newService.createdAt).to.not.be.null; 64 | }); 65 | 66 | }); 67 | 68 | describe('GET /api/services/:id', () => { 69 | let service; 70 | 71 | beforeEach(done => { 72 | request(app) 73 | .get('/api/services/' + newService._id) 74 | .expect(200) 75 | .expect('Content-Type', /json/) 76 | .end((err, res) => { 77 | if (err) return done(err); 78 | service = res.body; 79 | done(); 80 | }); 81 | }); 82 | 83 | afterEach(() => { 84 | service = {}; 85 | }); 86 | 87 | it('should respond with the requested service', () => { 88 | expect(service).to.be.instanceOf(Object); 89 | expect(service).ownProperty('_id'); 90 | expect(service._id).to.not.be.undefined; 91 | expect(service._id).to.not.be.null; 92 | expect(service).ownProperty('name'); 93 | expect(service.name).to.equal('Service Test'); 94 | expect(service).ownProperty('createdAt'); 95 | expect(service.createdAt).to.not.be.undefined; 96 | expect(service.createdAt).to.not.be.null; 97 | }); 98 | 99 | }); 100 | 101 | describe('PUT /api/services/:id', () => { 102 | let updatedService; 103 | 104 | beforeEach(done => { 105 | request(app) 106 | .put('/api/services/' + newService._id) 107 | .send({ 108 | 109 | _id: newService._id, 110 | name: 'Updated Service' 111 | }) 112 | .expect(200) 113 | .expect('Content-Type', /json/) 114 | .end((err, res) => { 115 | if (err) return done(err); 116 | updatedService = res.body; 117 | done(); 118 | }); 119 | }); 120 | 121 | afterEach(() => { 122 | updatedService = {}; 123 | }); 124 | 125 | it('should respond with the updated service', () => { 126 | expect(updatedService).to.be.instanceOf(Object); 127 | expect(updatedService).ownProperty('_id'); 128 | expect(updatedService._id).to.not.be.undefined; 129 | expect(updatedService._id).to.not.be.null; 130 | expect(updatedService).ownProperty('name'); 131 | expect(updatedService.name).to.equal('Updated Service'); 132 | expect(updatedService).ownProperty('createdAt'); 133 | expect(updatedService.createdAt).to.not.be.undefined; 134 | expect(updatedService.createdAt).to.not.be.null; 135 | // expect(updatedService).ownProperty('updatedAt'); 136 | // expect(updatedService.updatedAt).to.not.be.undefined; 137 | // expect(updatedService.updatedAt).to.not.be.null; 138 | }); 139 | 140 | }); 141 | 142 | describe('PATCH /api/services/:id', () => { 143 | let patchedService; 144 | 145 | beforeEach(done => { 146 | request(app) 147 | .put('/api/services/' + newService._id) 148 | .send({ 149 | _id: newService._id, 150 | name: 'Patched Service' 151 | }) 152 | .expect(200) 153 | .expect('Content-Type', /json/) 154 | .end((err, res) => { 155 | if (err) return done(err); 156 | patchedService = res.body; 157 | done(); 158 | }); 159 | }); 160 | 161 | afterEach(() => { 162 | patchedService = {}; 163 | }); 164 | 165 | it('should respond with the updated service', () => { 166 | expect(patchedService).to.be.instanceOf(Object); 167 | expect(patchedService).ownProperty('_id'); 168 | expect(patchedService._id).to.not.be.undefined; 169 | expect(patchedService._id).to.not.be.null; 170 | expect(patchedService).ownProperty('name'); 171 | expect(patchedService.name).to.equal('Patched Service'); 172 | expect(patchedService).ownProperty('createdAt'); 173 | expect(patchedService.createdAt).to.not.be.undefined; 174 | expect(patchedService.createdAt).to.not.be.null; 175 | // expect(patchedService).ownProperty('updatedAt'); 176 | // expect(patchedService.updatedAt).to.not.be.undefined; 177 | // expect(patchedService.updatedAt).to.not.be.null; 178 | }); 179 | 180 | }); 181 | 182 | describe('DELETE /api/services/:id', () => { 183 | 184 | it('should respond with 204 on successful removal', done => { 185 | request(app) 186 | .delete('/api/services/' + newService._id) 187 | .expect(204) 188 | .end((err, res) => { 189 | if (err) return done(err); 190 | done(); 191 | }); 192 | }); 193 | 194 | it('should respond with 404 when service does not exist', done => { 195 | request(app) 196 | .delete('/api/services/' + newService._id) 197 | .expect(404) 198 | .end((err, res) => { 199 | if (err) return done(err); 200 | done(); 201 | }); 202 | }); 203 | 204 | }); 205 | 206 | }); 207 | -------------------------------------------------------------------------------- /server/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import del from 'del'; 3 | import gulp from 'gulp'; 4 | import gulpLoadPlugins from 'gulp-load-plugins'; 5 | import lazypipe from 'lazypipe'; 6 | import nodemon from 'nodemon'; 7 | import runSequence from 'run-sequence'; 8 | import { Instrumenter } from 'isparta'; 9 | 10 | let plugins = gulpLoadPlugins(); 11 | 12 | const serverPaths = { 13 | scripts: [ 14 | '**/!(*.spec|*.integration).js', 15 | '!mocha.conf.js', 16 | '!gulpfile.babel.js', 17 | '!node_modules/**/*', 18 | '!dist/**/*.js', 19 | '!coverage/**/*', 20 | '!config/local.env.sample.js' 21 | ], 22 | json: '**/*.json', 23 | test: { 24 | integration: 'api/**/*.integration.js', 25 | unit: 'api/**/*.spec.js', 26 | coverage: [ 27 | 'api/**/*.js', 28 | '!api/**/*.events.js', 29 | '!api/**/*.socket.js' 30 | ] 31 | } 32 | }; 33 | 34 | const distPath = 'dist'; 35 | 36 | /******************** 37 | * Helper functions 38 | ********************/ 39 | 40 | function onServerLog(log) { 41 | console.log(plugins.util.colors.white('[') + 42 | plugins.util.colors.yellow('nodemon') + 43 | plugins.util.colors.white('] ') + 44 | log.message); 45 | } 46 | 47 | /******************** 48 | * Reusable pipelines 49 | ********************/ 50 | 51 | let lintServerScripts = lazypipe() 52 | .pipe(plugins.jshint, './.jshintrc') 53 | .pipe(plugins.jshint.reporter, 'jshint-stylish'); 54 | 55 | let lintServerTestScripts = lazypipe() 56 | .pipe(plugins.jshint, './.jshintrc-spec') 57 | .pipe(plugins.jshint.reporter, 'jshint-stylish'); 58 | 59 | let transpileServer = lazypipe() 60 | .pipe(plugins.sourcemaps.init) 61 | .pipe(plugins.babel, { 62 | plugins: [ 63 | 'transform-class-properties', 64 | 'transform-runtime' 65 | ] 66 | }) 67 | .pipe(plugins.sourcemaps.write, '.'); 68 | 69 | let mocha = lazypipe() 70 | .pipe(plugins.mocha, { 71 | reporter: 'spec', 72 | timeout: 5000, 73 | require: [ 74 | './mocha.conf' 75 | ] 76 | }); 77 | 78 | let istanbul = lazypipe() 79 | .pipe(plugins.istanbul.writeReports) 80 | .pipe(plugins.istanbulEnforcer, { 81 | thresholds: { 82 | global: { 83 | lines: 80, 84 | statements: 80, 85 | branches: 80, 86 | functions: 80 87 | } 88 | }, 89 | coverageDirectory: './coverage', 90 | rootDirectory : '' 91 | }); 92 | 93 | /******************** 94 | * Env 95 | ********************/ 96 | 97 | gulp.task('env:test', () => { 98 | plugins.env({ 99 | vars: { NODE_ENV: 'test' } 100 | }); 101 | }); 102 | 103 | gulp.task('env:prod', () => { 104 | plugins.env({ 105 | vars: { NODE_ENV: 'production' } 106 | }); 107 | }); 108 | 109 | /******************** 110 | * Tasks 111 | ********************/ 112 | 113 | gulp.task('transpile:server', () => { 114 | return gulp.src(_.union(serverPaths.scripts, serverPaths.json)) 115 | .pipe(transpileServer()) 116 | .pipe(gulp.dest(distPath)); 117 | }); 118 | 119 | gulp.task('lint:scripts:server', () => { 120 | return gulp.src(_.union(serverPaths.scripts, _.map(serverPaths.test, blob => '!' + blob))) 121 | .pipe(lintServerScripts()); 122 | }); 123 | 124 | gulp.task('lint:scripts:serverTest', () => { 125 | return gulp.src(serverPaths.test) 126 | .pipe(lintServerTestScripts()); 127 | }); 128 | 129 | gulp.task('jscs', () => { 130 | return gulp.src(serverPaths.scripts) 131 | .pipe(plugins.jscs()) 132 | .pipe(plugins.jscs.reporter()); 133 | }); 134 | 135 | gulp.task('start:server', () => { 136 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 137 | nodemon(`-w index.js index.js`) 138 | .on('log', onServerLog); 139 | }); 140 | 141 | gulp.task('start:server:prod', () => { 142 | process.env.NODE_ENV = process.env.NODE_ENV || 'production'; 143 | nodemon(`-w ./${distPath} ./${distPath}`) 144 | .on('log', onServerLog); 145 | }); 146 | 147 | gulp.task('start:inspector', () => { 148 | gulp.src([]) 149 | .pipe(plugins.nodeInspector()); 150 | }); 151 | 152 | gulp.task('start:server:debug', () => { 153 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 154 | nodemon(`-w ./ --debug-brk ./`) 155 | .on('log', onServerLog); 156 | }); 157 | 158 | gulp.task('watch', () => { 159 | var testFiles = _.union(serverPaths.test.unit, serverPaths.test.integration); 160 | 161 | plugins.livereload.listen(); 162 | 163 | plugins.watch(_.union(serverPaths.scripts, testFiles)) 164 | .pipe(plugins.plumber()) 165 | .pipe(lintServerScripts()) 166 | .pipe(plugins.livereload()); 167 | 168 | }); 169 | 170 | gulp.task('serve', cb => { 171 | runSequence( 172 | 'lint:scripts:server', 173 | 'start:server', 174 | 'watch', 175 | cb 176 | ); 177 | }); 178 | 179 | gulp.task('serve:dist', cb => { 180 | runSequence( 181 | 'clean:dist', 182 | 'build', 183 | 'start:server:prod', 184 | cb 185 | ); 186 | }); 187 | 188 | gulp.task('serve:debug', cb => { 189 | runSequence( 190 | 'lint:scripts:server', 191 | 'start:inspector', 192 | 'start:server:debug', 193 | 'watch', 194 | cb 195 | ); 196 | }); 197 | 198 | gulp.task('test', cb => { 199 | return runSequence('test:server', cb); 200 | }); 201 | 202 | gulp.task('test:server', cb => { 203 | runSequence( 204 | 'env:test', 205 | 'mocha:unit', 206 | 'mocha:integration', 207 | 'mocha:coverage', 208 | 'mocha:coveralls', 209 | cb 210 | ); 211 | }); 212 | 213 | gulp.task('mocha:unit', () => { 214 | return gulp.src(serverPaths.test.unit) 215 | .pipe(mocha()); 216 | }); 217 | 218 | gulp.task('mocha:integration', () => { 219 | return gulp.src(serverPaths.test.integration) 220 | .pipe(mocha()); 221 | }); 222 | 223 | gulp.task('build', cb => { 224 | runSequence( 225 | 'clean:dist', 226 | 'transpile:server', 227 | 'copy:server', 228 | cb 229 | ); 230 | }); 231 | 232 | gulp.task('clean:dist', () => del([`${distPath}/!(.git*)**`], {dot: true})); 233 | 234 | gulp.task('copy:server', () => { 235 | return gulp.src([ 236 | 'package.json', 237 | 'Dockerfile', 238 | '.env.example' 239 | ], {cwdbase: true}) 240 | .pipe(gulp.dest(distPath)); 241 | }); 242 | 243 | gulp.task('coverage:pre', () => { 244 | return gulp.src(serverPaths.test.coverage) 245 | .pipe(plugins.istanbul({ 246 | instrumenter: Instrumenter, 247 | includeUntested: true 248 | })) 249 | .pipe(plugins.istanbul.hookRequire()); 250 | }); 251 | 252 | gulp.task('coverage:unit', () => { 253 | return gulp.src(serverPaths.test.unit) 254 | .pipe(mocha()) 255 | .pipe(istanbul()); 256 | }); 257 | 258 | gulp.task('coverage:integration', () => { 259 | return gulp.src(serverPaths.test.integration) 260 | .pipe(mocha()) 261 | .pipe(istanbul()); 262 | }); 263 | 264 | gulp.task('mocha:coveralls', () => { 265 | return gulp.src('coverage/**/lcov.info') 266 | .pipe(plugins.coveralls()); 267 | }); 268 | 269 | gulp.task('mocha:coverage', cb => { 270 | runSequence( 271 | 'coverage:pre', 272 | 'env:test', 273 | 'coverage:unit', 274 | 'coverage:integration', 275 | cb 276 | ); 277 | }); 278 | --------------------------------------------------------------------------------