json-rpc-engine

A tool for processing JSON-RPC messages.

Usage no npm install needed!

<script type="module">
  import jsonRpcEngine from 'https://cdn.skypack.dev/json-rpc-engine';
</script>

README

json-rpc-engine

A tool for processing JSON-RPC requests and responses.

Usage

const { JsonRpcEngine } = require('json-rpc-engine')

let engine = new JsonRpcEngine()

Build a stack of JSON-RPC processors by pushing middleware to the engine.

engine.push(function(req, res, next, end){
  res.result = 42
  end()
})

Requests are handled asynchronously, stepping down the stack until complete.

let request = { id: 1, jsonrpc: '2.0', method: 'hello' }

engine.handle(request, function(err, response){
  // Do something with response.result, or handle response.error
})

// There is also a Promise signature
const response = await engine.handle(request)

Middleware have direct access to the request and response objects. They can let processing continue down the stack with next(), or complete the request with end().

engine.push(function(req, res, next, end){
  if (req.skipCache) return next()
  res.result = getResultFromCache(req)
  end()
})

By passing a return handler to the next function, you can get a peek at the result before it returns.

engine.push(function(req, res, next, end){
  next(function(cb){
    insertIntoCache(res, cb)
  })
})

Engines can be nested by converting them to middleware using JsonRpcEngine.asMiddleware():

const engine = new JsonRpcEngine()
const subengine = new JsonRpcEngine()
engine.push(subengine.asMiddleware())

async Middleware

If you require your middleware function to be async, use createAsyncMiddleware:

const { createAsyncMiddleware } = require('json-rpc-engine')

let engine = new RpcEngine()
engine.push(createAsyncMiddleware(async (req, res, next) => {
  res.result = 42
  next()
}))

async middleware do not take an end callback. Instead, the request ends if the middleware returns without calling next():

engine.push(createAsyncMiddleware(async (req, res, next) => {
  res.result = 42
  /* The request will end when this returns */
}))

The next callback of async middleware also don't take return handlers. Instead, you can await next(). When the execution of the middleware resumes, you can work with the response again.

engine.push(createAsyncMiddleware(async (req, res, next) => {
  res.result = 42
  await next()
  /* Your return handler logic goes here */
  addToMetrics(res)
}))

You can freely mix callback-based and async middleware:

engine.push(function(req, res, next, end){
  if (!isCached(req)) {
    return next((cb) => {
      insertIntoCache(res, cb)
    })
  }
  res.result = getResultFromCache(req)
  end()
})

engine.push(createAsyncMiddleware(async (req, res, next) => {
  res.result = 42
  await next()
  addToMetrics(res)
}))

Gotchas

Handle errors via end(err), NOT next(err).

/* INCORRECT */
engine.push(function(req, res, next, end){
  next(new Error())
})

/* CORRECT */
engine.push(function(req, res, next, end){
  end(new Error())
})

However, next() will detect errors on the response object, and cause end(res.error) to be called.

engine.push(function(req, res, next, end){
  res.error = new Error()
  next() /* This will cause end(res.error) to be called. */
})