The goal of this skeleton is to give the minimal structure to implement a µService, that can be invoked locally or remotely.
Its principles are the following:
- very easy to add new service api endpoints
- easy to use client interface, same usage both locally and remotely
- hide deployment details and provide simple-yet-working solution
- promises based
mini-service uses the latest ES6 features, so it requires node 6+
In a nodejs module caller that needs to use this µService service, add the µService as NPM dependency:
> npm install --save-dev service
Then require the exposed interface (given that service exposes an add()api):
const client = require('service')()
// you can also provide options, see below..
console.log(client.version)
// outputs µService NPM version
client.init().then(() => {
client.add(10, 5).then(sum => console.log(sum))
})
// outputs 15Inside your service module, you can expose as many APIs as you want.
Go to the /lib/services/index.js file, and add your own code, e.g.
module.exports = [{
// you can group your API and give them a name
name: 'calc',
// you need to provide an initialization function, that will take options,
// and returns a Promise when APIs are ready to be used
init: () => Promise.resolve({
// each exposed API is a function that takes as many parameters as needed, and returns a Promise
add: (a, b) => Promise.resolve(a + b),
subtract: (a, b) => Promise.resolve(a - b)
})
}]For an example, see sample.js.
Note: you don't need to put all the code in this file, you're free to divide it into different files/modules.
In that case, simply require and add them into the array exported by /lib/services/index.js.
You can use it locally (same node.js instance as the caller code), or remotely (deployed as remote HTTP server). Local is the default mode.
To use remotely, simply:
- start your µService as an Http server by running
> npm start - from the caller code, provide the Http server url to your client:
const client = require('service')({remote: 'http://my-service:8080')
And that's all !
The init() function is the right place to do that:
const fs = require('readFile')
module.exports = [{
name: 'calc'
init: () => new Promise((resolve, reject) => {
// let's say we need to read a configuration file...
fs.readFile('config.json', (err, content) => {
if (err) return reject(err)
// initialization goes on...
// when you're ready, resolve the promise with the exposed functions
resolve({
add: (a, b) => Promise.resolve(a + b)
subtract: (a, b) => Promise.resolve(a - b)
})
})
})
}]The init() function takes a single Object parameter, that can be used for options:
const fs = require('readFile')
module.exports = [{
name: 'calc'
// opts is an object
init: opts => new Promise((resolve, reject) => {
// use file given in options instead of hardcoded the value
fs.readFile(opts.config, (err, content) => {
if (err) return reject(err)
resolve({
add: (a, b) => Promise.resolve(a + b)
subtract: (a, b) => Promise.resolve(a - b)
})
})
})
}]To specify actual parameter values, change the caller code:
-
local µService, in the caller code:
const client = require({ // all options are regrouped under serviceOpts serviceOpts: { // reuse service name as property, and give any value you need calc: {config: './config.json'} } })
-
remote µService, in
./bin/start.jsfile:const startServer = require('../lib/server') startServer({ // all options are grouped under serviceOpts serviceOpts: { // use service name as property, and give any value you need calc: {config: './config.json'} } })
In addition to the specified options, your service init() parameter also contains logger property, which is the overall Bunyan logger.
Instead of putting everything in the same file, you can use as many files as you want. A simple file layout example:
/lib/services/index.js
module.exports = [
{name: 'calc', init: require('./calc')},
{name: 'utilities', init: require('./utilities')}
]/lib/services/calc.js
// you need to provide an initialization function, that will take options,
// and returns a Promise when APIs are ready to be used
module.exports = options => Promise.resolve({
// each exposed API is a function that takes as many parameters as needed, and returns a Promise
add: (a, b) => Promise.resolve(a + b),
subtract: (a, b) => Promise.resolve(a - b)
})Services will be initialized serially, so you can use this order to perform general initialization.
// shared object among services
let sql
module.exports = [{
name: 'global-init',
init: options => new Promise(resolve => {
sql = mysqljs(options)
// no need to expose anything
resolve()
})
}, {
name: 'calc',
// pass your shared object to your init method, as well as other options
init: opts => require('./calc')(sql, opts)
}, {
name: 'utilities',
init: opts => require('./utilities')(sql, opts)
}]This project was kindly sponsored by nearForm.
Copyright Damien Simonin Feugas and other contributors, licensed under MIT.
- Add NSP checks, and upgrade vulnerable dependency
- fix parameter detection
- fix Proxy that is detected as a Thenable object
- fix issue related to parameter name extraction when using arrow functions
- use proxy to delay remotely exposed Apis retrieval to the first effective usage
- activate Travis CI and coveralls reports
- update dependencies
- client functions always returns a real promise (request-promise return a mixed stream + promise object that prevent direct usage in Hapi)
- checks exposed services interface to avoid mistakes
- update dependencies
- use lab configuration file
- fix bug that prevent to specify version when creating the service
- allows to use general logger object within exposed services
- initial release