Automagically create HTTP JSON-RPC 2.0 endpoints for any node module!
- Write only 4 lines of code in order to create an HTTP RPC endpoint for any module!
- Small footprint (5 dependencies, total package size < 20 kB)
- Multiple ways of configuration (environment variables, command line flags), useful when deploying in different environments (e.g., local development, staging, production)
- No client library needed: As JSON-RPC 2.0 is used, it is trivial to create RPC requests by hand and parse responses.
Installation is straight forward with npm:
npm i rpc-automagic
As simple as it gets: create a new RPC server, register an endpoint and start listening:
const { RpcServer } = require('rpc-automagic');
const server = new RpcServer({ host: 'localhost', port: 1337 });
server.addEndpoint('/math', Math);
server.start();
Send a POST request to http://localhost:1337/math
--> { "jsonrpc": "2.0", "id": 1, "method": "pow", "params": [2, 10] }
<-- { "jsonrpc": "2.0", "id": 1, "result": 1024}
In this example, we add two endpoints:
/console
: this endpoint exposes theconsole
object/custom
: this endpoint exposes a custom module we created
const { RpcServer } = require('rpc-automagic');
const server = new RpcServer({ host: 'localhost', port: 1337 });
server.addEndpoint('/console', console);
server.addEndpoint('/custom', {
pick(...items) {
return `I like this one best: ${items[Math.floor(Math.random() * items.length)]}`;
},
});
server.start();
As console.log
does not return anything, we send a notification to the server by omitting the id
member of the request object. This way, the server immediately answers our request but with an empty response object.
// POST localhost:1337/console
--> { "jsonrpc": "2.0", "method": "log", "params": ["Hello World"] }
<-- { "jsonrpc": "2.0", "result": {} }
// POST localhost:1337/custom
--> { "jsonrpc": "2.0", "id": 1, "method": "pick", "params": ["Margherita", "Frutti di Mare", "Quattro Formaggi"] }
<-- { "jsonrpc": "2.0", "id": 1, "result": "I like this one best: Margherita" }
The full API documentation can be found here: https://decentro-gmbh.github.io/rpc-automagic/
The following configuration options are available:
host
: Server host (default: 'localhost')port
: Server port (default: 1337)disabled
: Whether the server is disabled by default. Results in the start() method exiting immediately (default: false)authentication
: Enable authentication (default: false)apikeyhash
: Provide the SHA256 hash (in hexadecimal format) of the API key that is used for authentication
There are multiple ways to configure the RPC server during initialization:
- Provide an options object while creating a new
RpcServer
instance. - Provide one or more configuration options as a command line flag (e.g.
--port=1234
,--disabled
, ...) - Provide one or more configuration options as an environment variable. The default prefix is
RPC_SERVER_
but you can customize it using theenvPrefix
option while creating a newRpcServer
instance. Examples:RPC_SERVER_PORT=1234
,RPC_SERVER_DISABLED=true
The order in which the sources of configuration options are considered is the following:
- command line flags
- environment variables
- options object during initialization
For example, starting a server like this:
RPC_SERVER_PORT=1234 ./start-rpc-server --port=4321
would result in the server listening on port 4321 as command line flags are considered before environment variables.
You can globally enable authentication for all endpoints of the RPC server by setting authentication=true
and providing the hash of an API key, e.g.:
apikeyhash= '959c9f50aef1bc129a0e16564319a1b36515d570513079b6c73c72a5709abdce', // sha256('secr3t')
If authentication is enabled, each request must provide the configured API key using the Authorization
HTTP header. Example request:
POST /math HTTP/1.1
Host: localhost:1337
Authorization: secr3t
{ "jsonrpc": "2.0", "id": 123, "method": "pow", "params": [2, 11] }
Some modules might require special care so that they can be transformed into an RPC endpoint. A common problem is the occurrence of callback parameters, as we cannot pass functions over JSON. However, it is quite simple to extend the RpcEndpoint
class and override the execute()
method to solve these kind of issues.
As an example, we want to create an RPC endpoint for the pm2 API, which uses callbacks for each of its functions:
const bluebird = require('bluebird');
const pm2Sync = require('pm2');
const { RpcEndpoint } = require('rpc-automagic');
// Promisify pm2 to eliminate callbacks
const pm2 = bluebird.promisifyAll(pm2Sync);
class Pm2Endpoint extends RpcEndpoint {
constructor() {
super('/pm2', pm2);
}
/** Execute pm2 RPCs, use the async pendant of the requested method */
async execute(method, params) {
return super.execute(`${method}Async`, params);
}
}
We can now use our custom RpcEndpoint class to create an /pm2
endpoint:
const server = new RpcServer();
server.addEndpoint(new Pm2Endpoint());
server.start();
The default logger simply uses console.log
and console.error
for printing log messages:
logger = {
info: (msg) => console.log(`[INFO] ${msg}`),
warn: (msg) => console.log(`[WARNING] ${msg}`),
err: (msg) => console.error(`[ERROR] ${msg}`),
};
However, a custom logger object can be provided as the logger
member of the options object while creating a new RPC server. This way, you can also disable all logging:
logger = {
info: (msg) => {}),
warn: (msg) => {}),
err: (msg) => {}),
};
The format of the request logging can be changed with the requestLoggingFormat
option to any of the available morgan logging formats ('common', 'dev', 'short', 'tiny', ...).