How to Wrap a REST API in GraphQL

How to Wrap a REST API in GraphQL

How to Wrap a REST API in GraphQLGraphQL is a powerful tool that we’ve discussed previously at Nordic APIs. As with any emergent tool in the API space, however, there’s some disagreement on exactly how to implement it, and what the best practices for its implementation and use case scenarios are.

Have no fear, dear reader — we’re here to sort this all out. We’re going to discuss GraphQL, where it came from, where it’s going, and why you should consider implementing it. We’ll focus on how to wrap a RESTful API with GraphQL, and how this functions in everyday usage.

What is GraphQL?

For those who are unfamiliar, GraphQL is an application layer query language. It interprets a string from a server or client, returning the data in a pre-defined schema as dictated by the requester. As the GraphQL official site states: “Describe your data, ask for what you want, get predictable results.

The way data is requested is quite clean and elegant. The following is a valid data descriptor:

type Project {
  name: String
  tagline: String
  contributors: [User]
}

When this is requested as such:

  project(name: "GraphQL") {
    tagline
  }
}

It returns a clean, easy, and simple result:

{
  "project": {
    "tagline": "A query language for APIs"
  }
}

A GraphQL implementation results in more elegant data retrieval, greater backend stability, more efficient queries, and improved organization with a language that has low adoption overhead. With this being said, let’s get into the meat of how exactly we can implement GraphQL in a RESTful API.

Defining a Schema

The first step to wrapping a RESTful API is to define a schema. Schemas are essentially like phonebooks — a stated, common methodology of recognizing and organizing your data and the interactions concerning said data.

When properly wrapped, a RESTful API will funnel all influx and outflux of data through the schema itself — this is the main power of the GraphQL system, and is where it gets its universality.

In following with the official GraphQL documentation, the implementation we’re going to show today is simplified, with some issues in terms of performance against more complex and time-consuming alternative implementations. This solution, however, requires no architectural or server augmentations, and is a perfect stepping off point that we can take to move forward.

The implementation as suggested by the GraphQL documentation is as follows:

import {
  GraphQLList,
  GraphQLObjectType,
  GraphQLSchema,
  GraphQLString,
} from 'graphql';

const BASE_URL = 'https://myapp.com/';

function fetchResponseByURL(relativeURL) {
  return fetch(`${BASE_URL}${relativeURL}`).then(res => res.json());
}

function fetchPeople() {
  return fetchResponseByURL('/people/').then(json => json.people);
}

function fetchPersonByURL(relativeURL) {
  return fetchResponseByURL(relativeURL).then(json => json.person);
}

const PersonType = new GraphQLObjectType({
  /* ... */
  fields: () => ({
    /* ... */
    friends: {
      type: new GraphQLList(PersonType),
      resolve: person => person.friends.map(getPersonByURL),
    },
  }),
});

const QueryType = new GraphQLObjectType({
  /* ... */
  fields: () => ({
    allPeople: {
      type: new GraphQLList(PersonType),
      resolve: fetchPeople,
    },
    person: {
      type: PersonType,
      args: {
        id: { type: GraphQLString },
      },
      resolve: (root, args) => fetchPersonByURL(`/people/${args.id}/`),
    },
  }),
});

export default new GraphQLSchema({
  query: QueryType,
});

What this schema is basically doing is attaching JavaScript methods to the variables, and establishing the methodology by which the data is returned. The beginning and end is a necessary statement — the import of the GraphQL strictures, and the export of the GraphQL schema proper:

import { GraphQLSchema } from 'graphql';

export default new GraphQLSchema({
  query: QueryType,
});

By establishing two constants — a data type, and a query type — data is collated internally through the API, while allowing for fetching given a specific set of arguments given by the requester:

const PersonType = new GraphQLObjectType({
  /* ... */
  fields: () => ({
    /* ... */
    friends: {
      type: new GraphQLList(PersonType),
      resolve: person => person.friends.map(getPersonByURL),
    },
  }),
});

const QueryType = new GraphQLObjectType({
  /* ... */
  fields: () => ({
    allPeople: {
      type: new GraphQLList(PersonType),
      resolve: fetchPeople,
    },
    person: {
      type: PersonType,
      args: {
        id: { type: GraphQLString },
      },
      resolve: (root, args) => fetchPersonByURL(`/people/${args.id}/`),
    },
  }),
});

this functionally does is establish the data and accepted query methodology for email, id, and username, and resolves the data by accessing the properties of the person object as attached in the code. While this technique relies on some functionality in Relay, a companion to GraphQL often considered inseparable, the principle remains the same — predictable, queryable data.

Of note for this approach, however, is the fact that the types in question were hand-defined. While this works for small systems, it’s not a tenable solution for larger APIs. In such a case, solutions like Swagger can define type definitions automatically, which can then be “typified” for the GraphQL schema with relative ease.

Alternatives to this Method

Thankfully, there are some very enterprising developers who have taken GraphQL to its logical extent, automating the process of creating the schema itself. One such solution is the graphql-rest-wrapper. Designed to easily create wrapped REST APIs, this technique is simple to employ:

const wrapper = new gqlRestWrapper('https://localhost:9090/restapi', {
    name: 'MyRestAPI',
    generateSchema: true,
    saveSchema: true,
    graphiql: true
})
 
app.use('/graphql', wrapper.expressMiddleware())

This solution is rather simple, but elegant in how it handles the schema production. The “gqlRestWrapper” class creates a GraphQL schema from the REST response. In a way, this is similar to a game of telephone, wherein the middle man takes the data being passed through, and defines it into a usable schema for future interaction.

A few steps need to be taken. First, the npm package must be installed. Then, it needs to be imported. Finally, the code function as stated above needs to be instantiated:

npm i graphql-rest-wrapper


var gqlRestWrapper = require(graphql-rest-wrapper)


new gqlRestWrapped([apiEndpoint], [variableOptions])

Then, middleware, or the interpreter in the telephone game, needs to be attached to the route proper:

app.use([ROUTE], wrapper.expressMiddleware())

And finally, an HTTP GET/POST request can be made:

fetch("https://localhost:9090/graphql",
    {
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        method: "POST",
        body: "{'query': 'query { MyRestAPI { id, name } }'}"
    })
    .then(function (res) {
        console.log(res);
    })

The benefit of this style of wrapping is that it’s entirely automated when properly organized. Whereas the first process is entirely by hand, the second process is entirely handled by an automatic and effective system. This lends itself to problems that the hand-coded method misses, of course — principally, the fact that more complex code can be missed or result in broken schemas.

To Wrap or Not to Wrap

Of course, this begs the question — should we really be wrapping a RESTful API in GraphQL in the first place? This assumes that the API in question is being left in REST for the sole purpose that development of a GraphQL compliant endpoint over a series of hundreds of use cases would be an unworthy time sink.

That may not be true, however, when one considers the lengths to which a provider must go in order to get what they want out of their API. It may simply be more useful to code a GraphQL compliant series of endpoints rather than try to wrap an API in a new skin.

This isn’t an all or nothing proposition, either. At the Nordic APIs 2016 Platform Summit, Zane Claes spoke on the movement from an internal, legacy, monolithic API, to a more consistent group of API functionalities that served data given specific devices, specific use cases, and specific requirements.

It is entirely possible to use a legacy API for a time, and slowly migrate to a GraphQL compliant API, rather than wrapping an existing API as a “stop gap”. What we’re talking about here is the difference between a band-aid and a full-blown hip replacement — the extent of difficulty may not be known until it’s actually attempted.

Conclusion: Wrap or Recode

Thankfully, the methodology of wrapping an existing API comes down to how complicated the situation is. For most API providers, a simple wrapping as stated above would work, with the automated solution being entirely acceptable.

For others, however, especially APIs that are simply monolithic, the process of re-coding an API to be GraphQL compliant is a more effective choice.