rmosolgo / graphql-ruby

Ruby implementation of GraphQL
http://graphql-ruby.org
MIT License
5.38k stars 1.39k forks source link

GraphQL Rack REST Mapper #4777

Open arinhouck opened 10 months ago

arinhouck commented 10 months ago

Is your feature request related to a problem? Please describe.

I currently have task I need to solve eventually which is creating a more effective way to replicate our GraphQL queries and mutations into a REST API for an application built using graphql-ruby. Our internal systems consume our GraphQL queries however this application needs a REST API because we have external clients who don't want to use GraphQL in their integration with us. We'd like to write once in GraphQL and then create a pattern to build REST apis automagically from our GraphQL Schema.

Describe the solution you'd like

I was thinking if there was some easy API to traverse the schema in ruby objects I could build a rack middleware to route REST calls to our Graphql controller.

So things come to mind:

Describe alternatives you've considered

There is some options out there like SOFA and Graphql2rest to map via Express servers however that would mean maintaining a proxy server in javascript. It's feasible option but isn't ideal to add yet another layer of complexity in our infrastructure.

rmosolgo commented 10 months ago

Hey, thanks for the suggestion and starting this conversation. I'm definitely open to supporting something like this.

At GitHub, we used GraphQL::Client to build some REST APIs and had mixed success. It was a decent tool for the job but it got awkward when trying to support legacy endpoints that way, because of differences between our GraphQL system and the existing REST system (slightly different permission systems, different response fields, etc...).

But for a greenfield REST API, it'd be a very fast way to get started.

A few design question that come to mind for me:

arinhouck commented 10 months ago

Yeah I agree, it is more effective for greenfield but I think some options like how SOFA approached with a "mapping override" could alleviate some of that friction.

  'Query.getBookById': {
    method: 'POST',
    path: '/book/:id',
    tags: ['Book'],
    description: 'This is a custom detailed description for getBookById',
  },

My use case is for mostly greenfield APIs so I'm definitely lucky in that regard.

How would one build a REST endpoint out of the schema?

I think this could be approached in a number of ways. I think an ideal default would be following rails scaffolding convention which is pretty true to good REST practices on REST route generation.

Regarding GraphQL, interpreting patterns of how different naming conventions of graphql queries and mutation could add complexity. That is where I think starting with a hardcoded mapping is actually sufficient enough. If we wanted to take a stab at "automation" a simple generator command could make naive assumptions like so:

Pluralize resources (this could be approached in variety of ways through the query name or the root type object). Referencing prior art here like SOFA would be a good start. However if we did get it wrong folks could just modify it manually if we generated everything in static definitions.

{
    "endpoints": {
        "/users/:userId": {
            "get": {
                "operation": "getUser"
            },
            "delete": {
                "operation": "removeUser",
                "successStatusCode": 202
            }
        },
        "/users": {
            "post": {
                "operation": "createUser",
                "successStatusCode": 201
            }
        }
    }
}

Patterns can be identified and abstracted, suggesting best practices and building on top of those. Which it sounds like we are on a similar page in regards to.

How can we leverage GraphQL's type system to document the REST API? Generate an OpenAPI spec, or something else?

Great callout, this is another use case we would have to support as we also have the need of documenting these APIs for our external clients.

Are there existing REST API generators that we could integrate with?

That's a good callout, my first thought would be to reduce any dependencies or prevent any "lock in" to more gems. But it could be opened up via adapters like how omniauth works if that is something folks wanted more flexibility on using their favorite tool. It's definitely worth a look if there is anything that would simplify scope that is already out there.

My first initial design thought is how to programmatically define a route mapping which could redirect the GraphQL controller entrypoint. It'd have to reformat query params and body into "variables" and some other nuances. A bit naive as I haven't done any work like this before and also very new to GraphQL.

What would be the best way to distribute this? Inside GraphQL-Ruby, or as a separate gem? Or some other way?

It might make sense to start a separate gem given it could have a pretty wide scope. I don't have any experience packaging gems so I'd take your recommendation on this one.

I'd have to start with a bit of a research spike to gather what rails dependency could achieve this in an elegant way. It sounds like I might need to look into the Visitor API as well with this gem to learn more about traversing a graph definition.

arinhouck commented 9 months ago

So I've got a working solution for building a Rails GraphQL to REST mapper that I am releasing to production very soon (it proxies through rails routing nearly 40 rest endpoints from our query and mutation graph).

Currently it's designed in it's own standalone proxy service to support communication with a federation graph client however could work with an application mounted with GraphQL Ruby. I have yet to make an abstraction into a gem but if folks are interested in this as an open source tool I'd consider it!

High level example:

objects() query as `GET /objects`
object(id) query as `GET /objects/:id`
createObject(...) mutation as `POST /objects`
updateObject(...) mutation  as `PUT /objects/:id`
deleteObject(id) mutation as `DELETE /objects/:id`

Features

Things to add:

agios commented 1 week ago

This sounds very interesting @arinhouck! Did you end up releasing it as open source? We're currently looking into implementing something similar.