├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app └── models │ └── product_quantity.js ├── config.js ├── docker-compose.yml ├── package.json ├── server.js └── service.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | RUN mkdir -p /usr/src/app 4 | WORKDIR /usr/src/app 5 | COPY . /usr/src/app 6 | 7 | EXPOSE 8080 8 | RUN npm install 9 | CMD ["npm", "start"] 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 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 | This is a simple microservices example that uses Node.js + Express to provide a simple Product Quantities Service for tracking onhand inventories of products. Docker support is included for running locally or deploying to services such as [Cloud66](https://www.cloud66.com) 2 | 3 | __Note:__ This isn't production-ready code. The service assumes no Mongo username/password, doesn't force SSL, or enforce security best practices. This is for demonstration and learning purposes only. 4 | 5 | # Running locally 6 | 7 | Install the required dependencies: 8 | 9 | > npm install 10 | 11 | Set an environment variable to point to the location of your MongoDB instance (e.g. 127.0.0.01 for localhost, or the specific IP/hostname if not localhost) 12 | 13 | > export MONGODB_ADDRESS=127.0.0.1 14 | 15 | Run Express: 16 | 17 | > npm start 18 | 19 | # Running locally using Docker 20 | 21 | Use docker-compose to build the service and mongodb containers (takes a few minutes the first time): 22 | 23 | > docker-compose build 24 | 25 | Then start the the containers: 26 | 27 | > docker-compose up 28 | 29 | # Deploying to Cloud66 30 | 31 | Cloud66 is a Manager Containers as a Service that supports deploying Docker containers onto one or more servers using a bring-your-own-cloud approach. 32 | 33 | TODO 34 | 35 | # Interacting with the Products Service 36 | 37 | Use [Postman](https://www.getpostman.com) or [cURL](https://curl.haxx.se/) to directly interact with the API. 38 | 39 | Create/update a product's onhand quantity, given the product identifier of 12345: 40 | 41 | > curl -X PUT http://127.0.0.1:8080/product_quantities/12345 -d "quantity_onhand=15" 42 | 43 | ``` 44 | { 45 | "product_id": "12345", 46 | "quantity_onhand": 15, 47 | "created_at": "2016-04-11T16:43:29.000Z", 48 | "_links": { 49 | "self": { 50 | "href": "/product_quantities/12345" 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | (make the same PUT call to update the onhand quantity at a later time) 57 | 58 | Get the current onhand quantity for a product with the identifier 12345: 59 | 60 | > curl -X GET http://127.0.0.1:8080/product_quantities/12345 61 | 62 | ``` 63 | { 64 | "product_id": "12345", 65 | "quantity_onhand": 15, 66 | "created_at": "2016-04-11T16:43:29.000Z", 67 | "_links": { 68 | "self": { 69 | "href": "/product_quantities/12345" 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /app/models/product_quantity.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var ProductQuantitySchema = new Schema({ 5 | product_id: String, 6 | quantity_onhand: Number 7 | }, 8 | { 9 | timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } 10 | }); 11 | 12 | module.exports = mongoose.model('ProductQuantity', ProductQuantitySchema); 13 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | db: { 3 | production: "mongodb://"+process.env.MONGODB_ADDRESS+":27017/product_quantities", 4 | development: "mongodb://"+process.env.MONGODB_ADDRESS+":27017/product_quantities", 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | inventory: 2 | build: . 3 | command: npm start 4 | ports: 5 | - "8080:8080" 6 | links: 7 | - mongodb 8 | - mongodb:mongodb.cloud66.local 9 | environment: 10 | - NODE_ENV=production 11 | - MONGODB_ADDRESS=mongodb 12 | mongodb: 13 | image: mongo 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "microservices-node-inventory", 3 | "version": "1.0.0", 4 | "description": "Example REST-based API using Node Express, deployed using Docker", 5 | "main": "server.js", 6 | "author": "James Higginbotham", 7 | "license": "MIT", 8 | "dependencies": { 9 | "body-parser": "~1.0.1", 10 | "express": "~4.0.0", 11 | "halson": "~2.3.1", 12 | "mongoose": "~4.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // BASE SETUP 2 | // ============================================================================= 3 | 4 | // call the packages we need 5 | var express = require('express'); // call express 6 | var app = express(); // define our app using express 7 | var bodyParser = require('body-parser'); 8 | 9 | // HAL support 10 | var halson = require('halson'); 11 | 12 | // configure app to use bodyParser() 13 | // this will let us get the data from a POST 14 | app.use(bodyParser.urlencoded({ extended: true })); 15 | app.use(bodyParser.json()); 16 | 17 | // set our port, defaulting if nothing is specified in the env 18 | var port = process.env.PORT || 8080; 19 | 20 | // load app configurations from config.js 21 | var config = require('./config'); 22 | 23 | // configure our connection to MongoDB 24 | var mongoose = require('mongoose'); 25 | 26 | // establish our MongoDB connection for the models 27 | mongoose.connect(config.db[app.settings.env]); 28 | 29 | // import models 30 | var ProductQuantity = require('./app/models/product_quantity'); 31 | 32 | // get an instance of the express Router, allowing us to add 33 | // middleware and register our API routes as needed 34 | var router = express.Router(); 35 | 36 | // create/update a productQuantity 37 | router.put('/product_quantities/:product_id', function(req, res) { 38 | 39 | if (req.body.quantity_onhand == null) { 40 | res.status(400); 41 | res.setHeader('Content-Type', 'application/vnd.error+json'); 42 | res.json({ message: "quantity_onhand parameter is required"}); 43 | } else 44 | { 45 | 46 | ProductQuantity.findOne({ product_id: req.params.product_id}, function(err, productQuantity) { 47 | if (err) return console.error(err); 48 | 49 | var created = false; // track create vs. update 50 | if (productQuantity == null) { 51 | productQuantity = new ProductQuantity(); 52 | productQuantity.product_id = req.params.product_id; 53 | created = true; 54 | } 55 | 56 | // set/update the onhand quantity and save 57 | productQuantity.quantity_onhand = req.body.quantity_onhand; 58 | productQuantity.save(function(err) { 59 | if (err) { 60 | res.status(500); 61 | res.setHeader('Content-Type', 'application/vnd.error+json'); 62 | res.json({ message: "Failed to save productQuantity"}); 63 | } else { 64 | // return the appropriate response code, based 65 | // on whether we created or updated a ProductQuantity 66 | if (created) { 67 | res.status(201); 68 | } else { 69 | res.status(200); 70 | } 71 | 72 | res.setHeader('Content-Type', 'application/hal+json'); 73 | 74 | var resource = halson({ 75 | product_id: productQuantity.product_id, 76 | quantity_onhand: productQuantity.quantity_onhand, 77 | created_at: productQuantity.created_at 78 | }).addLink('self', '/product_quantities/'+productQuantity.product_id) 79 | 80 | res.send(JSON.stringify(resource)); 81 | } 82 | }); 83 | }); 84 | } 85 | }); 86 | 87 | router.get('/product_quantities/:product_id', function(req, res) { 88 | ProductQuantity.findOne({product_id: req.params.product_id}, function(err, productQuantity) { 89 | if (err) { 90 | res.status(500); 91 | res.setHeader('Content-Type', 'application/vnd.error+json'); 92 | res.json({ message: "Failed to fetch ProductQuantities"}); 93 | } else if (productQuantity == null) { 94 | res.status(404); 95 | res.setHeader('Content-Type', 'application/vnd.error+json'); 96 | res.json({ message: "ProductQuantity not found for product_id "+req.params.product_id}); 97 | } else { 98 | res.status(200); 99 | res.setHeader('Content-Type', 'application/hal+json'); 100 | 101 | var resource = halson({ 102 | product_id: productQuantity.product_id, 103 | quantity_onhand: productQuantity.quantity_onhand, 104 | created_at: productQuantity.created_at 105 | }).addLink('self', '/product_quantities/'+productQuantity.product_id) 106 | res.send(JSON.stringify(resource)); 107 | 108 | } 109 | }); 110 | }); 111 | 112 | 113 | // Register our route 114 | app.use('/', router); 115 | 116 | // Start the server 117 | app.listen(port); 118 | console.log('Running on port ' + port); 119 | -------------------------------------------------------------------------------- /service.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | inventory: 4 | git_url: git@github.com:launchany/microservices-node-inventory.git 5 | git_branch: master 6 | command: npm start 7 | build_root: . 8 | ports: 9 | - container: 8080 10 | http: 80 11 | https: 443 12 | env_vars: 13 | NODE_ENV: production 14 | 15 | databases: 16 | - mongodb 17 | --------------------------------------------------------------------------------