{"componentChunkName":"component---src-templates-doc-tsx","path":"/blog/rest-api-graphql-wrapper/","result":{"data":{"doc":{"frontmatter":{"title":"Wrapping a REST API in GraphQL","date":"5 May 2016","permalink":"/blog/rest-api-graphql-wrapper/","byline":"Steven Luscher","guestBio":null,"sublinks":null,"layout":"blog"},"id":"a649faaf-f109-5196-a7c1-7054e2e13d73","rawMarkdownBody":"\nTime and time again I hear the same aspiration from front-end web and mobile developers: they're eager to reap the developer efficiency gains offered by new technologies like Relay and GraphQL, but they have years of momentum behind their existing REST API. Without data that clearly demonstrates the benefits of switching, they find it hard to justify an additional investment in GraphQL infrastructure.\n\nIn this post I will outline a rapid, low-investment method that you can use to stand up a GraphQL endpoint atop an existing REST API, using JavaScript alone. No backend developers will be harmed during the making of this blog post.\n\n## A client-side REST wrapper\n\nWe're going to create a _GraphQL schema_ – a type system that describes your universe of data – that wraps calls to your existing REST API. This schema will receive and resolve GraphQL queries all on the client side. This architecture features some inherent performance flaws, but is fast to implement and requires no server changes.\n\nImagine a REST API that exposes a `/people/` endpoint through which you can browse `Person` models and their associated friends.\n\n![A REST API that exposes an index of people][rest-api-people]\n\nWe will build a GraphQL schema that models people and their attributes (like `first_name` and `email`) as well as their association to other people through friendships.\n\n### Installation\n\nFirst we'll need a set of schema building tools.\n\n```\nnpm install --save graphql\n```\n\n### Building the GraphQL Schema\n\nUltimately we will want to export a `GraphQLSchema` that we can use to resolve queries.\n\n```js\nimport { GraphQLSchema } from 'graphql';\n\nexport default new GraphQLSchema({\n  query: QueryType,\n});\n```\n\nAt the root of all GraphQL schemas is a type called `query` whose definition we provide, and have specified here as `QueryType`. Let's build `QueryType` now – a type on which we will define all the possible things one might want to fetch.\n\nTo replicate all of the functionality of our REST API, let's expose two fields on `QueryType`:\n\n* an `allPeople` field – analogous to `/people/`\n* a `person(id: String)` field – analogous to `/people/{ID}/`\n\nEach field will consist of a return type, optional argument definitions, and a JavaScript method that resolves the data being queried for.\n\n```js\nimport {\n  GraphQLList,\n  GraphQLObjectType,\n  GraphQLString,\n} from 'graphql';\n\nconst QueryType = new GraphQLObjectType({\n  name: 'Query',\n  description: 'The root of all... queries',\n  fields: () => ({\n    allPeople: {\n      type: new GraphQLList(PersonType),\n      resolve: root => // Fetch the index of people from the REST API,\n    },\n    person: {\n      type: PersonType,\n      args: {\n        id: { type: GraphQLString },\n      },\n      resolve: (root, args) => // Fetch the person with ID `args.id`,\n    },\n  }),\n});\n```\n\nLet's leave the resolvers as a sketch for now, and move on to defining `PersonType`.\n\n```js\nimport {\n  GraphQLList,\n  GraphQLObjectType,\n  GraphQLString,\n} from 'graphql';\n\nconst PersonType = new GraphQLObjectType({\n  name: 'Person',\n  description: 'Somebody that you used to know',\n  fields: () => ({\n    firstName: {\n      type: GraphQLString,\n      resolve: person => person.first_name,\n    },\n    lastName: {\n      type: GraphQLString,\n      resolve: person => person.last_name,\n    },\n    email: {type: GraphQLString},\n    id: {type: GraphQLString},\n    username: {type: GraphQLString},\n    friends: {\n      type: new GraphQLList(PersonType),\n      resolve: person => // Fetch the friends with the URLs `person.friends`,\n    },\n  }),\n});\n```\n\nNote two things about the definition of `PersonType`. Firstly, we have not supplied a resolver for `email`, `id`, or `username`. The default resolver simply accesses the property of the `person` object that has the same name as the field. This works everywhere except where the property names do not match the field name (eg. the field `firstName` does not match the `first_name` property of the response object from the REST API) or where accessing the property would not yield the object that we want (eg. we want a list of person objects for the `friends` field, not a list of URLs).\n\nNow, let's write resolvers that fetch people from the REST API. Because we need to load from the network, we won't be able to return a value right away. Luckily for us, `resolve()` can return either a value or a `Promise` for a value. We're going to take advantage of this to fire off an HTTP request to the REST API that eventually resolves to a JavaScript object that conforms to `PersonType`.\n\nAnd here we have it – a complete first-pass at the schema:\n\n```js{28,38,45}\nimport {\n  GraphQLList,\n  GraphQLObjectType,\n  GraphQLSchema,\n  GraphQLString,\n} from 'graphql';\n\nconst BASE_URL = 'https://myapp.com/';\n\nfunction fetchResponseByURL(relativeURL) {\n  return fetch(`${BASE_URL}${relativeURL}`).then(res => res.json());\n}\n\nfunction fetchPeople() {\n  return fetchResponseByURL('/people/').then(json => json.people);\n}\n\nfunction fetchPersonByURL(relativeURL) {\n  return fetchResponseByURL(relativeURL).then(json => json.person);\n}\n\nconst PersonType = new GraphQLObjectType({\n  /* ... */\n  fields: () => ({\n    /* ... */\n    friends: {\n      type: new GraphQLList(PersonType),\n      resolve: person => person.friends.map(fetchPersonByURL),\n    },\n  }),\n});\n\nconst QueryType = new GraphQLObjectType({\n  /* ... */\n  fields: () => ({\n    allPeople: {\n      type: new GraphQLList(PersonType),\n      resolve: fetchPeople,\n    },\n    person: {\n      type: PersonType,\n      args: {\n        id: { type: GraphQLString },\n      },\n      resolve: (root, args) => fetchPersonByURL(`/people/${args.id}/`),\n    },\n  }),\n});\n\nexport default new GraphQLSchema({\n  query: QueryType,\n});\n```\n\n\n### Using a client-side schema with Relay\n\nNormally, Relay will send its GraphQL queries to a server over HTTP. We can inject [@taion](https://github.com/taion/)'s custom `relay-local-schema` network layer to resolve queries using the schema we just built. Put this code wherever it's guaranteed to be executed before you mount your Relay app.\n\n```\nnpm install --save relay-local-schema\n```\n\n```\nimport RelayLocalSchema from 'relay-local-schema';\n\nimport schema from './schema';\n\nRelay.injectNetworkLayer(\n  new RelayLocalSchema.NetworkLayer({ schema })\n);\n```\n\nAnd that's that. Relay will send all of its queries to your custom client-resident schema, which will in turn resolve them by making calls to your existing REST API.\n\n## A server-side REST wrapper\n\nThe client-side REST API wrapper demonstrated above should help you get up and running quickly so that you can try out a Relay version of your app (or part of your app).\n\nHowever, as we mentioned before, this architecture features some inherent performance flaws because of how GraphQL is still calling your underlying REST API which can be very network intensive. A good next step is to move the schema from the client side to the server side to minimize latency on the network and to give you more power to cache responses.\n\nTake the next 10 minutes to watch me build a server side version of the GraphQL wrapper above using Node and Express.\n\n<iframe id=\"ytplayer\" type=\"text/html\" width=\"640\" height=\"390\"\n  src=\"https://www.youtube.com/embed/UBGzsb2UkeY?autoplay=0&origin=http://graphql.org&start=900\"\n  frameborder=\"0\"></iframe>\n\n## Bonus round: A truly Relay compliant schema\n\nThe schema we developed above will work for Relay up until a certain point – the point at which you ask Relay to refetch data for records you've already downloaded. Relay's refetching subsystem relies on your GraphQL schema exposing a special field that can fetch any entity in your data universe by GUID. We call this the _node interface_.\n\nTo expose a node interface requires that you do two things: offer a `node(id: String!)` field at the root of the query, and switch all of your ids over to GUIDs (globally-unique ids).\n\nThe `graphql-relay` package contains some helper functions to make this easy to do.\n\n```\nnpm install --save graphql-relay\n```\n\n#### Global ids\n\nFirst, let's change the `id` field of `PersonType` into a GUID. To do this, we'll use the `globalIdField` helper from `graphql-relay`.\n\n```js\nimport {\n  globalIdField,\n} from 'graphql-relay';\n\nconst PersonType = new GraphQLObjectType({\n  name: 'Person',\n  description: 'Somebody that you used to know',\n  fields: () => ({\n    id: globalIdField('Person'),\n    /* ... */\n  }),\n});\n```\n\nBehind the scenes `globalIdField` returns a field definition that resolves `id` to a `GraphQLString` by hashing together the typename `'Person'` and the id returned by the REST API. We can later use `fromGlobalId` to convert the result of this field back into `'Person'` and the REST API's id.\n\n#### The node field\n\nAnother set of helpers from `graphql-relay` will give us a hand developing the node field. Your job is to supply the helper two functions:\n\n* One function that can resolve an object given a GUID.\n* One function that can resolve a typename given an object.\n\n```js\nimport {\n  fromGlobalId,\n  nodeDefinitions,\n} from 'graphql-relay';\n\nconst { nodeInterface, nodeField } = nodeDefinitions(\n  globalId => {\n    const { type, id } = fromGlobalId(globalId);\n    if (type === 'Person') {\n      return fetchPersonByURL(`/people/${id}/`);\n    }\n  },\n  object => {\n    if (object.hasOwnProperty('username')) {\n      return 'Person';\n    }\n  },\n);\n```\n\nThe object-to-typename resolver above is no marvel of engineering, but you get the idea.\n\nNext, we simply need to add the `nodeInterface` and the `nodeField` to our schema. A complete example follows:\n\n```js{27-39,54,61,72}\nimport {\n  GraphQLList,\n  GraphQLObjectType,\n  GraphQLSchema,\n  GraphQLString,\n} from 'graphql';\nimport {\n  fromGlobalId,\n  globalIdField,\n  nodeDefinitions,\n} from 'graphql-relay';\n\nconst BASE_URL = 'https://myapp.com/';\n\nfunction fetchResponseByURL(relativeURL) {\n  return fetch(`${BASE_URL}${relativeURL}`).then(res => res.json());\n}\n\nfunction fetchPeople() {\n  return fetchResponseByURL('/people/').then(json => json.people);\n}\n\nfunction fetchPersonByURL(relativeURL) {\n  return fetchResponseByURL(relativeURL).then(json => json.person);\n}\n\nconst { nodeInterface, nodeField } = nodeDefinitions(\n  globalId => {\n    const { type, id } = fromGlobalId(globalId);\n    if (type === 'Person') {\n      return fetchPersonByURL(`/people/${id}/`);\n    }\n  },\n  object => {\n    if (object.hasOwnProperty('username')) {\n      return 'Person';\n    }\n  },\n);\n\nconst PersonType = new GraphQLObjectType({\n  name: 'Person',\n  description: 'Somebody that you used to know',\n  fields: () => ({\n    firstName: {\n      type: GraphQLString,\n      resolve: person => person.first_name,\n    },\n    lastName: {\n      type: GraphQLString,\n      resolve: person => person.last_name,\n    },\n    email: {type: GraphQLString},\n    id: globalIdField('Person'),\n    username: {type: GraphQLString},\n    friends: {\n      type: new GraphQLList(PersonType),\n      resolve: person => person.friends.map(fetchPersonByURL),\n    },\n  }),\n  interfaces: [ nodeInterface ],\n});\n\nconst QueryType = new GraphQLObjectType({\n  name: 'Query',\n  description: 'The root of all... queries',\n  fields: () => ({\n    allPeople: {\n      type: new GraphQLList(PersonType),\n      resolve: fetchPeople,\n    },\n    node: nodeField,\n    person: {\n      type: PersonType,\n      args: {\n        id: { type: GraphQLString },\n      },\n      resolve: (root, args) => fetchPersonByURL(`/people/${args.id}/`),\n    },\n  }),\n});\n\nexport default new GraphQLSchema({\n  query: QueryType,\n});\n```\n\n## Taming pathological queries\n\nConsider the following friends-of-friends-of-friends query:\n\n```graphql\nquery {\n  person(id: \"1\") {\n    firstName\n    friends {\n      firstName\n      friends {\n        firstName\n        friends {\n          firstName\n        }\n      }\n    }\n  }\n}\n```\n\nThe schema we created above will generate multiple round trips to the REST API for the same data.\n\n![Duplicate queries to the REST API][pathological-query]\n\nThis is obviously something we would like to avoid! At the very least, we need a way to cache the result of these requests.\n\nWe created a library called DataLoader to help tame these sorts of queries.\n\n```\nnpm install --save dataloader\n```\n\nAs a special note, make sure that your runtime offers native or polyfilled versions of `Promise` and `Map`. Read more [at the DataLoader site](https://github.com/facebook/dataloader#getting-started).\n\n#### Creating a data loader\n\nTo create a `DataLoader` you supply a method that can resolve a list of objects given a list of keys. In our example, the keys are URLs at which we access our REST API.\n\n```js\nconst personLoader = new DataLoader(\n  urls => Promise.all(urls.map(fetchPersonByURL))\n);\n```\n\nIf this data loader sees a key more than once in its lifetime, it will return a memoized (cached) version of the response.\n\n#### Loading data\n\nWe can make use of the `load()` and `loadMany()` methods on `personLoader` to load URLs without fear of hitting the REST API more than once per URL. A complete example follows:\n\n```js{36,63,83}\nimport DataLoader from 'dataloader';\nimport {\n  GraphQLList,\n  GraphQLObjectType,\n  GraphQLSchema,\n  GraphQLString,\n} from 'graphql';\nimport {\n  fromGlobalId,\n  globalIdField,\n  nodeDefinitions,\n} from 'graphql-relay';\n\nconst BASE_URL = 'https://myapp.com/';\n\nfunction fetchResponseByURL(relativeURL) {\n  return fetch(`${BASE_URL}${relativeURL}`).then(res => res.json());\n}\n\nfunction fetchPeople() {\n  return fetchResponseByURL('/people/').then(json => json.people);\n}\n\nfunction fetchPersonByURL(relativeURL) {\n  return fetchResponseByURL(relativeURL).then(json => json.person);\n}\n\nconst personLoader = new DataLoader(\n  urls => Promise.all(urls.map(fetchPersonByURL))\n);\n\nconst { nodeInterface, nodeField } = nodeDefinitions(\n  globalId => {\n    const {type, id} = fromGlobalId(globalId);\n    if (type === 'Person') {\n      return personLoader.load(`/people/${id}/`);\n    }\n  },\n  object => {\n    if (object.hasOwnProperty('username')) {\n      return 'Person';\n    }\n  },\n);\n\nconst PersonType = new GraphQLObjectType({\n  name: 'Person',\n  description: 'Somebody that you used to know',\n  fields: () => ({\n    firstName: {\n      type: GraphQLString,\n      resolve: person => person.first_name,\n    },\n    lastName: {\n      type: GraphQLString,\n      resolve: person => person.last_name,\n    },\n    email: {type: GraphQLString},\n    id: globalIdField('Person'),\n    username: {type: GraphQLString},\n    friends: {\n      type: new GraphQLList(PersonType),\n      resolve: person => personLoader.loadMany(person.friends),\n    },\n  }),\n  interfaces: [nodeInterface],\n});\n\nconst QueryType = new GraphQLObjectType({\n  name: 'Query',\n  description: 'The root of all... queries',\n  fields: () => ({\n    allPeople: {\n      type: new GraphQLList(PersonType),\n      resolve: fetchPeople,\n    },\n    node: nodeField,\n    person: {\n      type: PersonType,\n      args: {\n        id: { type: GraphQLString },\n      },\n      resolve: (root, args) => personLoader.load(`/people/${args.id}/`),\n    },\n  }),\n});\n\nexport default new GraphQLSchema({\n  query: QueryType,\n});\n```\n\nNow, our pathological query produces the following nicely de-duped set of requests to the REST API:\n\n![De-duped queries to the REST API][dataloader-query]\n\n### Query planning and beyond\n\nConsider that your REST API might already offer configuration offers that let you eagerly load associations. Maybe to load a person and all of their direct friends you might hit the URL `/people/1/?include_friends`. To take advantage of this in your GraphQL schema you will need the ability to develop a resolution plan based on the structure of the query itself (eg. whether the `friends` field is part of the query or not).\n\nFor those interested in the current thinking around advanced resolution strategies, keep an eye on [pull request #304](https://github.com/graphql/graphql-js/pull/304).\n\n## Thanks for reading\n\nI hope that this demonstration has torn down some of the barriers between you and a functional GraphQL endpoint, and has inspired you to experiment with GraphQL and Relay on an existing project.\n\n[rest-api-people]: /img/blog/20160502-rest-api-graphql-wrapper/rest-api-people.png \"A REST API that exposes an index of people\"\n[pathological-query]: /img/blog/20160502-rest-api-graphql-wrapper/pathological-query.png \"Duplicate queries to the REST API\"\n[dataloader-query]: /img/blog/20160502-rest-api-graphql-wrapper/dataloader-query.png \"De-duped queries to the REST API\"\n"},"nextDoc":null},"pageContext":{"permalink":"/blog/rest-api-graphql-wrapper/","nextPermalink":null,"sideBarData":[{"name":"blog","links":[{"fileAbsolutePath":"/opt/build/repo/src/content/blog/20160914-production-ready.md","parent":{"relativeDirectory":"blog","sourceInstanceName":"content"},"frontmatter":{"title":"Leaving technical preview","permalink":"/blog/production-ready/","next":null,"category":null,"sublinks":null,"sidebarTitle":null,"date":"14 Sep 2016"},"id":"4f311495-5c94-5a32-bf98-632d47845cbd"},{"fileAbsolutePath":"/opt/build/repo/src/content/blog/20160502-rest-api-graphql-wrapper.md","parent":{"relativeDirectory":"blog","sourceInstanceName":"content"},"frontmatter":{"title":"Wrapping a REST API in GraphQL","permalink":"/blog/rest-api-graphql-wrapper/","next":null,"category":null,"sublinks":null,"sidebarTitle":null,"date":"5 May 2016"},"id":"a649faaf-f109-5196-a7c1-7054e2e13d73"},{"fileAbsolutePath":"/opt/build/repo/src/content/blog/20160419-mocking.md","parent":{"relativeDirectory":"blog","sourceInstanceName":"content"},"frontmatter":{"title":"Mocking your server is easy with GraphQL","permalink":"/blog/mocking-with-graphql/","next":null,"category":null,"sublinks":null,"sidebarTitle":null,"date":"19 Apr 2016"},"id":"fe3c944f-6972-58b7-9869-2507c2038f0f"},{"fileAbsolutePath":"/opt/build/repo/src/content/blog/20151016-subscriptions.md","parent":{"relativeDirectory":"blog","sourceInstanceName":"content"},"frontmatter":{"title":"Subscriptions in GraphQL and Relay","permalink":"/blog/subscriptions-in-graphql-and-relay/","next":null,"category":null,"sublinks":null,"sidebarTitle":null,"date":"16 Oct 2015"},"id":"6f1cc952-67b3-5136-ab9a-53ade12311eb"},{"fileAbsolutePath":"/opt/build/repo/src/content/blog/20150914-graphql.md","parent":{"relativeDirectory":"blog","sourceInstanceName":"content"},"frontmatter":{"title":"GraphQL: A data query language","permalink":"/blog/graphql-a-query-language/","next":null,"category":null,"sublinks":null,"sidebarTitle":null,"date":"14 Sep 2015"},"id":"7b33b023-80a1-5d5d-9af0-f17d45e93fd6"}]}],"sourcePath":"src/content/blog/20160502-rest-api-graphql-wrapper.md"}},"staticQueryHashes":["1581580458"]}