GraphQL powered RESTful API

GraphQL powered RESTful API

At Tines, we have a [REST API](https://hub.tines.com/api/welcome) that allows users to perform operations such as creating stories on their tenants. Typically, we've left this REST API unchanged as we built features like Folders and Teams into the product. This limited the power of these new features, as customers were unable to access them through the REST API.

The REST API lagged behind the Web app because each used a completely different method of fetching and updating data, and both of these had to be developed and maintained separately.

# The web app's approach - GraphQL

[GraphQL](https://graphql.org/) is a query language for building APIs. We use GraphQL extensively at Tines to power the entire front-end of our Web app. We use [queries](https://graphql.org/learn/queries/) to fetch data from the database for the front-end and [mutations](https://graphql.org/learn/queries/#mutations) to modify data on the server-side.

We sought to unify the access path for both the REST API and the Web app. We found that the best way to do this was to **use the existing GraphQL API implementation to power both the Web App and the REST API**.

There are a few advantages of doing this, which include:

- Implement features once: features implemented will be immediately available to the REST API
- Leverage extensive testing we have on both the front-end and backend on all GraphQL queries and mutations.
- We get request parameter validations for free through built-in GraphQL type validation
- We get native support for versioning with GraphQL

You might also ask the question: why didn't we just expose a GraphQL API? This is something we might do in the future, for now, we found that REST is far more accessible to most users, whereas GraphQL has a steeper learning curve. We might also consider releasing a GraphQL API alongside a REST API similar to how other platforms like Github already do.

# How we did it

We set a simple goal to create a pattern for easily creating new REST API endpoints.

We created a declarative syntax for describing a REST API endpoint, meaning that adding an endpoint involves just two steps:

1. Define your REST API controller action and GraphQL query that should be executed for that action.
2. Define how the GraphQL response should be transformed to get the data that will be returned to the REST API

## Defining an API endpoint

```ruby
# /api/v1/my_controller.rb

class API::V1::UsersController < API::V1::GraphQLController
 ROUTES = {
   action_name: {
     query: ->(params, context) {}, # build the GraphQL query to execute
     response: -> (result) {}, # build the API response body
   },
 }
end
```

e.g This is the definition of our recently released [Teams creation REST API endpoint](https://hub.tines.com/api/teams/create).

```ruby
# app/controllers/api/v1/teams_controller.rb

class API::V1::TeamsController < API::V1::GraphQLController
 ROUTES = {
# POST /api/v1/teams
   create: {
     query: ->(params, _context) do
       {
         query: <<~GRAPHQL
           mutation($inputs: TeamCreationInputs!) {
             teamCreation(inputs: $inputs) {
               team {
                 id
                 name
               }
             }
           }
           GRAPHQL,
         variables: { inputs: params }
       }
     end,
     response: ->(result) { result["team_creation", "team"] },
   }
 }
end
```

So with the configuration above, we can generate a corresponding Rails controller method which will:

- Parse the request to the REST API endpoint
- Build a GraphQL query with the parsed request payload
- Execute the GraphQL query (In our case, since we use the [graphql-ruby](https://graphql-ruby.org/) gem, we simply call  ``TinesSchema.execute`` with a query built with the REST API request parameters)
- Translate the GraphQL response into the right format for our REST API

```ruby
# POST /api/v1/teams
def create
 # Build a GraphQL Query
 query =  <<~GRAPHQL
     mutation($inputs: TeamCreationInputs!) {
       teamCreation(inputs: $inputs) {
         team {
           id
           name
         }
       }
     }
     GRAPHQL
 variables = { inputs: params }

 # Execute the GraphQL query
 result =  TinesSchema.execute(
             query,
             variables: camelize_keys(variables),
             context: {
               current_user: current_user,
             },
           )

 result_hash = result.to_hash

 # Convert GraphQL response into REST API response format
 # - snake-case keys `userName -> user_name`
 # - convert graphql ID into ActiveRecord Id `id: "VXNlci0zMA==" -> id: "1"`
 response = transform_graphql_response_into_api_response(result_hash["data"])

 # 4. Render the response
 render json: response["team_creation", "team"], status: 201
end
```

# Conclusion

By using our existing GraphQL implementation, we were able to define a clear pattern for creating and updating REST API endpoints. We've achieved feature parity between the Web App and the REST API. This also serves as a base for future work on the API, such as implementing versioning,  cursor-based pagination and possibly a public GraphQL API.

If you would like to work on problems like this, [we're hiring](https://www.tines.com/careers#open-positions).