├── .npmignore ├── .gitignore ├── other └── img │ └── APIGateway.png ├── lib ├── debugMiddleware.js └── argv.js ├── routes ├── index.js ├── rpi.js └── example.js ├── README.md ├── package.json ├── boot.js └── server.js /.npmignore: -------------------------------------------------------------------------------- 1 | other/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | logs/ 4 | -------------------------------------------------------------------------------- /other/img/APIGateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roccomuso/api-gateway/HEAD/other/img/APIGateway.png -------------------------------------------------------------------------------- /lib/debugMiddleware.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('api-gateway:incoming') 2 | module.exports = function (req, res, next) { 3 | var mex = req.protocol+' - '+req.method+' - '+req.url 4 | debug(process.pid, ':', mex) 5 | next() 6 | } 7 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | // Routes autoloader 2 | 3 | var fs = require('fs') 4 | var path = require('path') 5 | var debug = require('debug')('api-gateway:routesLoader') 6 | 7 | module.exports = function (app) { 8 | fs.readdirSync(__dirname) 9 | .filter(function (file) { 10 | return (file.indexOf('.') !== 0) && (file !== 'index.js') 11 | }) 12 | .forEach(function (file) { 13 | require(path.join(__dirname, file))(app) 14 | debug('Route', file, 'loaded') 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Node API-Gateway 3 | 4 | Node.js API gateway that works as single entry point for all clients in a MicroService architecture pattern. 5 | 6 | [![Standard - JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 7 | 8 | ## Work in progress. Nothing to see here yet. 9 | 10 | ![API Gateway](https://github.com/roccomuso/api-gateway/blob/master/other/img/APIGateway.png?raw=true "Node.js API Gateway") 11 | 12 | ## Install 13 | 14 | $ npm install --save api-gateway 15 | 16 | ## Features 17 | 18 | - [ ] Cluster support to spawn multiple processes. 19 | - [ ] Proxy WS. 20 | - [ ] JSON descriptor for Services. 21 | - [ ] Store descriptors. 22 | 23 | ## Enhancement 24 | 25 | - [ ] Service discovery. 26 | - [ ] Services descriptors dynamic update. 27 | -------------------------------------------------------------------------------- /lib/argv.js: -------------------------------------------------------------------------------- 1 | var argv = require('yargs') 2 | .usage('Usage: npm start -- -p -P ') 3 | .help('help') 4 | .alias('help', 'h') 5 | .option('port', { 6 | alias: 'p', 7 | demand: true, 8 | describe: 'HTTP Port', 9 | type: 'number' 10 | }) 11 | .option('sslPort', { 12 | alias: 'P', 13 | demand: false, 14 | describe: 'HTTPS Port', 15 | type: 'number' 16 | }) 17 | .option('istances', { 18 | alias: 'i', 19 | demand: false, 20 | describe: 'Number of process istances', 21 | type: 'number' 22 | }) 23 | .example('npm start -- -p 80', 'Start the API gateway on port 80.') 24 | .example('npm start -- -p 80 -i 4', 'Start 4 istances (processes) of the gateway.') 25 | .epilogue('@Author: Rocco Musolino - @Copyright 2017') 26 | .argv 27 | 28 | module.exports = argv 29 | -------------------------------------------------------------------------------- /routes/rpi.js: -------------------------------------------------------------------------------- 1 | var proxy = require('http-proxy-middleware') 2 | var transformerProxy = require('transformer-proxy') 3 | 4 | function isPathAbsolute(path) { 5 | return /^(?:\/|[a-z]+:\/\/)/.test(path); 6 | } 7 | 8 | function transformerFunction(data, req, res){ 9 | console.log(data.toString()); return data 10 | var page = data.toString().replace(/href="\//g, 'href="/iot/') 11 | page = page.toString().replace(/src="\//g, 'src="/iot/') 12 | return new Buffer(page) 13 | } 14 | 15 | module.exports = function (app) { 16 | app.use(transformerProxy(transformerFunction)) 17 | app.use('/iot', proxy({target: 'http://192.168.88.111:8080', pathRewrite: {'^/iot' : '/'}, changeOrigin: true, ws: true, logProvider: function(){ return require('debug')('api-gateway:proxyLog')}})) 18 | app.use('/rpi', proxy({target: 'http://192.168.88.111', pathRewrite: {'^/rpi' : '/'}, changeOrigin: true })) 19 | // app.use('/rpi', function(req, res, next){ res.end('ciaooo');}) 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-gateway", 3 | "version": "1.0.0", 4 | "description": "Node.js API gateway that works as single entry point for all clients in a MicroService architecture pattern", 5 | "main": "boot.js", 6 | "dependencies": { 7 | "config": "^1.25.1", 8 | "express": "^4.14.1", 9 | "http-proxy-middleware": "^0.17.3", 10 | "morgan": "^1.8.1", 11 | "rotating-file-stream": "^1.1.9", 12 | "transformer-proxy": "^0.3.3", 13 | "yargs": "^6.6.0" 14 | }, 15 | "devDependencies": { 16 | "standard": "^8.6.0" 17 | }, 18 | "scripts": { 19 | "test": "standard", 20 | "start": "DEBUG=api-gateway:* node boot.js" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/roccomuso/api-gateway.git" 25 | }, 26 | "keywords": [ 27 | "api", 28 | "gateway", 29 | "rest", 30 | "proxy", 31 | "nodejs", 32 | "mmicroservices" 33 | ], 34 | "author": "Rocco Musolino", 35 | "license": "ISC", 36 | "bugs": { 37 | "url": "https://github.com/roccomuso/api-gateway/issues" 38 | }, 39 | "homepage": "https://github.com/roccomuso/api-gateway#readme" 40 | } 41 | -------------------------------------------------------------------------------- /boot.js: -------------------------------------------------------------------------------- 1 | // Clustering to exploit all the cores of a machine. Node is single-threaded so it will use by default only 1 core. 2 | var cluster = require('cluster') 3 | var debug = require('debug')('api-gateway:boot') 4 | var argv = require('./lib/argv') 5 | 6 | if (cluster.isMaster) { // Master process 7 | var environment = process.env.NODE_ENV || 'development' // production to use express built-in cache middleware 8 | debug('Running in %s environment', environment) 9 | 10 | var numCPUs = argv.istances || require('os').cpus().length 11 | 12 | for (var i = 0; i < numCPUs; i++) { 13 | cluster.fork() // 1 process per core 14 | } 15 | 16 | debug('Master process online with PID', process.pid) 17 | 18 | cluster.on('online', function (worker) { 19 | debug('Worker ' + worker.process.pid + ' is online') 20 | }) 21 | 22 | cluster.on('exit', function (worker, code, signal) { 23 | debug('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal) 24 | debug('Starting a new worker') 25 | cluster.fork() 26 | }) 27 | } else { // Worker process 28 | require('./server.js') 29 | } 30 | 31 | // NB. Windows doesn't use a Round-Robin approach. 32 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('api-gateway:server') 2 | var argv = require('./lib/argv') 3 | var express = require('express') 4 | var proxy = require('http-proxy-middleware') 5 | var morgan = require('morgan') 6 | var rfs = require('rotating-file-stream') 7 | // var https = require('https') 8 | var fs = require('fs') 9 | var path = require('path') 10 | var app = express() 11 | 12 | // set server port 13 | app.set('port', argv.port) 14 | 15 | var logDirectory = path.join(__dirname, 'logs') 16 | 17 | // ensure log directory exists 18 | fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory) 19 | 20 | // create a rotating write stream 21 | var accessLogStream = rfs('access.log', { 22 | size: '10M', 23 | interval: '1d', // rotate daily 24 | compress: 'gzip', 25 | path: logDirectory 26 | }) 27 | 28 | // setup the logger 29 | app.use(morgan('combined', {stream: accessLogStream})) 30 | app.use(require('./lib/debugMiddleware')) 31 | 32 | // Routes middlewares autoloader 33 | require('./routes/index')(app) 34 | 35 | // 404 Middleware 36 | app.use(function (req, res, next) { 37 | res.status(404) 38 | res.json({ error: 'Not found', pid: process.pid }) 39 | }) 40 | 41 | // start the server 42 | var srv = app.listen(app.get('port'), function () { 43 | debug(process.pid + ': Server listening on port', srv.address().port) 44 | }) 45 | 46 | /* 47 | // SSL Certificate 48 | var options = { 49 | key: fs.readFileSync(__dirname+'/SSL/cert.key'), 50 | cert: fs.readFileSync(__dirname+'/SSL/cert.crt') 51 | } 52 | // Create an HTTPS service identical to the HTTP service. 53 | https.createServer(options, app).listen(443) 54 | */ 55 | -------------------------------------------------------------------------------- /routes/example.js: -------------------------------------------------------------------------------- 1 | var proxy = require('http-proxy-middleware') 2 | 3 | /** 4 | * Proxy options 5 | */ 6 | var options = { 7 | // hostname to the target server 8 | target: 'http://localhost:3000', 9 | 10 | // set correct host headers for name-based virtual hosted sites 11 | changeOrigin: true, 12 | 13 | // enable websocket proxying 14 | ws: true, 15 | 16 | // additional request headers 17 | headers: { 18 | 'x-powered-by': 'api-gateway' 19 | }, 20 | 21 | // rewrite paths 22 | pathRewrite: { 23 | '^/api/old-path': '/api/new-path', // rewrite path 24 | '^/api/remove/path': '/path' // remove base path 25 | }, 26 | 27 | // re-target based on the request's host header and/or path 28 | router: { 29 | // host[/path] : 30 | // /path : 31 | 'integration.localhost:8000': 'http://localhost:8001', // host only 32 | 'staging.localhost:8000': 'http://localhost:8002', // host only 33 | 'localhost:8000/api': 'http://localhost:8003', // host + path 34 | '/rest': 'http://localhost:8004' // path only 35 | }, 36 | 37 | // control logging 38 | logLevel: 'silent', 39 | 40 | // use a different lib for logging; 41 | // i.e., write logs to file or server 42 | /* 43 | logProvider: function (provider) { 44 | return winston; 45 | }, 46 | */ 47 | 48 | // subscribe to http-proxy's error event 49 | onError: function onError (err, req, res) { 50 | res.writeHead(500, {'Content-Type': 'text/plain'}) 51 | res.end('Something went wrong. ' + err.toString()) 52 | }, 53 | 54 | // subscribe to http-proxy's proxyRes event 55 | onProxyRes: function (proxyRes, req, res) { 56 | // proxyRes.headers['x-added'] = 'foobar'; // add header to the response 57 | // delete proxyRes.headers['x-removed']; // delete response header 58 | }, 59 | 60 | // subscribe to http-proxy's proxyReq event 61 | onProxyReq: function (proxyReq, req, res) { 62 | // add custom header to request 63 | proxyReq.setHeader('x-forwarded-for', req.connection.remoteAddress) 64 | } 65 | 66 | /** 67 | * The following options are provided by Nodejitsu's http-proxy 68 | */ 69 | 70 | // target 71 | // forward 72 | // agent 73 | // ssl 74 | // ws 75 | // xfwd 76 | // secure 77 | // toProxy 78 | // prependPath 79 | // ignorePath 80 | // localAddress 81 | // changeOrigin 82 | // auth 83 | // hostRewrite 84 | // autoRewrite 85 | // protocolRewrite 86 | // headers 87 | 88 | } 89 | 90 | /** 91 | * Create the proxy middleware, so it can be used in a server. 92 | */ 93 | 94 | module.exports = function (app) { 95 | app.use('/example', proxy(options)) 96 | } 97 | --------------------------------------------------------------------------------