Applying a GraphQL Layer on Top of Existing Services With Mesh
Opinions on GraphQL aside, if you ever come across a decision point around using GraphQL in a project, this is an option. As a part of the decision-making process around determining the right tools for the job, just knowing more options can be paramount.
Initial setup
There are a couple of ways to GraphQL-ize your existing services and they'll depend on specific use cases, needs, and/or wants. Today we'll focus on churning out a simplistic GraphQL gateway and handling a JSON-schema use case.
- Run
yarn init
in an empty folder (I named minemesh
), step through all the prompts, andcd
into it. - Run
yarn add graphql @graphql-mesh/cli @graphql-mesh/graphql @graphql-mesh/json-schema
to install the relevant packages. - Add a
.meshrc.yaml
file in that same directory (this is the configuration file thatgraphql-mesh
uses in order to determine how to relate data sources to your graph)
And that's it! The rest of the way will depend on what your schema looks like.
Scenario
Before anything else, here is the Github repository if you want something to follow along with. It also has an existing sample API that I leveraged for the graphql-mesh
work.
Let's say we have an existing RESTful API out there that has an endpoint /users
and you want to be able to consume that through your GraphQL gateway. Let's make use of graphql-mesh
!
The first step is understanding what your source looks like. graphql-mesh
has support for multiple handlers already, but you can also use custom handlers or extend existing ones to meet your specific needs. We'll be looking at how to apply a JSON-schema handler (hence the @graphql-mesh/json-schema
package).
The second step is figuring out what bits and pieces go into your .meshrc.yaml
. A majority of it is self-explanatory. Here's what I've got:
sources:
- name: ProjectBuildoutApi
handler:
jsonSchema:
baseUrl: http://localhost:3000
operations:
- type: Query
field: users
path: /users
method: GET
responseSchema: ./jsonSchemas/usersResponse.json
- The
sources
node is a list of your named sources, each with its own handlers. We only have one and it uses ajsonSchema
typehandler
. handler
refers to thegraphql-mesh
's implementation of how to handle specific sources. There are a few default handlers supported already but is open to extension and/or composition (or an entirely new handler for your specific use case)name
here refers to what you'd like to name this specific source. I named mine 'ProjectBuildoutApi' since this particular source has more than justusers
.baseUrl
is what it sounds like; it's the jumping-off point for youroperations
. I currently have it pointing to a running API service on my local that I've set up for this purpose. You'll potentially have moreoperations
here as you add more functionality (maybe moreGET
's, or aPOST
if you're feeling daring).type
refers to the GraphQL operation type. We're just making a straightforward query.field
refers to the identifier you'd like to use for this data when you make a GraphQL query to this gateway.path
refers to the path to get your users, based off of thebaseUrl
; this includes any string-interpolated values (i.e. if I were to have a/users
endpoint that had room for anid
path argument,path
would be something like/users/{args.id}
.responseSchema
is the location of the schema for the response that you expect from this particular endpoint.
Defining the responseSchema
In order for this to work, you'll want to define the structure of what to expect when you make a call to the endpoint you want to consume. I made a directory to house my schemas called jsonSchemas
and I created a usersResponse.json
file in there with these contents:
{
"type": "object",
"title": "UsersResponse",
"description": "A list of Users in ProjectBuildout",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"title": "User",
"description": "An instance of a User",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string"
}
}
}
}
}
}
- The
title
anddescription
properties document what to expect and what the objects are; you'll want it to be informative, as you would for any domain-driven object. type
refers to whether the property in the JSON at a particular hierarchy is an object or an array.data
is simply the property of the response with a type of array that houses theUser
objects.items
are specific to arraytype
s and it determines the structure of the objects to expect in that array at this property.- For this API, a user only has an
id
,name
andemail
property. We only care aboutname
andemail
for now and are defined here as such. When you query forid
, GraphQL will throw an error because as far as GraphQL is concerned, it's an undefined field.
Running the thing
You're set to go! The last step is to run it. Run yarn graphql-mesh serve
and an instance of GraphiQL should run on your browser. Here's a sample query to use:
{
users {
data {
name
}
}
}
The browser should look like this after running this query:
Congrats! Now you have the flexibility that GraphQL has to offer without sacrificing too much time/effort.
Summary
There are different ways to enable your team to be more flexible when it comes to deciding the tools for your API. If GraphQL is on the table, this is one of the ways (another is to encapsulate this work in an SDK) you can effectively add that layer of flexibility with subjectively minimal costs in dev time/effort and tech debt reconciliation — it always depends, doesn't it?
Resources
Here are a few links to get familiar with what graphql-mesh
is as well and what problem it solves i.e. how do we allow consumers to express what they want to express in the data cost-effectively, with the existing architecture:
- Github Repository — proof of concept to follow along with this article
- Primary Documentation
- In-depth overview — authored by Uri Goldshtein
- GraphQL Galaxy 2020 - conference video playlist that inspired this post. (I believe the rest of the conference gets uploaded a month after the conference, so about a week or so into the new year).