Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GraphQL Rack REST Mapper #4777

Open
arinhouck opened this issue Jan 10, 2024 · 4 comments
Open

GraphQL Rack REST Mapper #4777

arinhouck opened this issue Jan 10, 2024 · 4 comments

Comments

@arinhouck
Copy link

arinhouck commented Jan 10, 2024

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:

  • Proposing this functionality for graphql-ruby and see if you are open to it (possibly a Pro option)
  • Getting some feedback on which APIs from graphql-ruby would be effective to traverse the schema to start building it myself or possibly any changes to support easy ways to traverse GraphQL schema in a ruby object or json format if there isn't already. I planned on doing my own research on this when this becomes a higher priority for our business but figured I could get a headstart asking here.
  • Getting any feedback as the expert in graphql with ruby on how you would approach this

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
Copy link
Owner

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:

  • How would one build a REST endpoint out of the schema? What might an example default setup look like, and how could someone go beyond that to add customizations? (Personally, my dream would be a really fast default set up, with all kinds of options for customization as you go.)
  • How can we leverage GraphQL's type system to document the REST API? Generate an OpenAPI spec, or something else?
  • Are there existing REST API generators that we could integrate with? (https://www.ruby-toolbox.com/categories/API_Builders)
  • What would be the best way to distribute this? Inside GraphQL-Ruby, or as a separate gem? Or some other way?

@arinhouck
Copy link
Author

arinhouck commented Jan 11, 2024

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
Copy link
Author

arinhouck commented Jan 30, 2024

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

  • one mounted route which translates REST into GraphQL calls
  • query param support (converting string values as integer based on graphql schema)
  • builds open api spec from graphql-ruby using rswag (supports building schema from types, provides request examples by schema, provides response examples by schema, uses all description verbiage from graphql)
  • Simple transforms for standardizing responses
  • Supports connection types (even nested)
  • Supports building open api schema for various complex types (eg. Schema::List, Union)

Things to add:

  • Ability to allow for dynamic field requesting following json api spec sparse fields API. Currently i've hardcoded my types based on a "collection" or "show" fragment at time of request.

@agios
Copy link

agios commented Nov 12, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants