holidayextras / jsonapi-server

A config driven NodeJS framework implementing json:api and GraphQL
MIT License
488 stars 115 forks source link

question: server-free port-free direct API for use in Function-as-a-Service / tests ? #219

Open jokeyrhyme opened 7 years ago

jokeyrhyme commented 7 years ago

I don't mean to hammer you with issues today, I'm just darned excited about finding this project. :D

I'm working on a project that will be hosted in a Function-as-a-Service, such as AWS Lambda, Google Cloud Functions, or Azure Functions. Within such an environment, it would be desirable to be able to pass a request to this JSON API framework without involving the network stack.

It is still possible to bind to a port, and then create an internal HTTP request to that port, but I'd rather avoid it. Having that port bound makes testing tricky and adds to the latency of a request.

The Hapi.js server framework provides an inject() method that can be used like this without needing to bind to a port: http://hapijs.com/api#serverinjectoptions-callback

Is there a way to use the existing APIs in this framework to handle a synthetic request without needing to bind to a port or create a real HTTP request? If I get the time, I'll try to see if this is possible with the Express features that this framework exposes.

jokeyrhyme commented 7 years ago

What I have so far is an approach that starts the server then tries to stop it:

const app = express()

jsonApi.setConfig({
  hostname: 'localhost',
  port: 0,
  protocol: 'http',
  router: app
})

function startInnerService () /* : http.Server */ {
  jsonApi.start()

  return app.listen(0)
}

// https://github.com/holidayextras/jsonapi-server/blob/v2.2.0/lib/jsonApi.js#L121
// had to clone and tweak the internal method RE: issue #220
function stopInnerService (server /* : http.Server */) {
  // jsonApi.metrics.emitter.removeAllListeners('data')
  for (const i in jsonApi._resources) {
    const resourceConfig = jsonApi._resources[i]
    if (resourceConfig.handlers.close) resourceConfig.handlers.close()
  }
  server.close()
}

function getInnerHost (server /* : http.Server */) /* : string */ {
  return `localhost:${server.address().port}`
}

Note that this doesn't yet seem to successfully shut everything down. I feel an HTTP-free network-free way of asking the framework to handle a (synthetic?) request would be ideal. But the one-request server approach I've outlined here is very close to being sufficient.

jokeyrhyme commented 7 years ago

I've been reviewing discussions over in https://github.com/expressjs/express/issues

Officially-supported network-free request handling in Express might not be possible until 5.x. :S

theninj4 commented 7 years ago

In lib/rerouter.js you'll find a network-stack bypass that lets you ping requests in without hitting express, I wrote it for a performance boost when doing lots of inclusions and it's used by the GraphQL implementation.

Take a look, it might do what you need although it may need a few small tweaks for non-GET requests :)

theninj4 commented 7 years ago

Thanks for all your contributions @jokeyrhyme, they're awesome :D I'm rammed for the rest of today into the evening, but I promise I'll get to all this first thing tomorrow morning (~22 hours from now) :cake:

jokeyrhyme commented 7 years ago

lib/rerouter.js looks promising, thanks :)

My current approach starts the server in a child process, makes the request, and then kills the child process. This covers closing the MongoDB connections, at least until I get around to that PR. :)

SphtKr commented 7 years ago

Ditto to this, I have an application where the main interface will be JSON:API, but I also need to be able to create one kind of resource anonymously via multipart/form-data POSTs. I retrieved the jsonapi-server's router and added a route for this, but now I need to turn around and format and submit JSON:API requests via HTTP to localhost...which seems...wasteful. I also have to find the related parent resource that the incoming form submission belongs to, so there's a number of operations I would have to do by loopback requests.

repl-chris commented 6 years ago

Has there been any movement on this? Is this possible? This library looks awesome, and I would love to use it for my API gateway+lambda-based API. Would this approach require APIG to basically shovel all the requests through to one monolithic lambda which serves the entire API? or would it still be possible to run individual lambdas for each resource/method/whatever?

paparomeo commented 6 years ago

@repl-chris, conceptually you should be able to have individual lambdas. In practice it will probably not work with the current implementation. Have you experimented at all with running the (monolithic) server in a lambda?

repl-chris commented 6 years ago

No, unfortunately I haven’t had much of a chance yet...I‘m quite new to this ecosystem so I’ve mostly just been playing with express...it is on my todo list tho :)