From REST to GraphQL a real world experiment
I was following the GraphQL story already quiet a long time. And I finally decided on a very good project to try building a GraphQL server. In this article I am going to use the Hyperwallet REST V3 APIs and expose them via a GraphQL Server.
What is GraphQL?
Original developed by Facebook GraphQL’s website describes it as “A DATA QUERY LANGUAGE AND RUNTIME“. It allows you to request only the information you really need. And that usual in a tree fashion. A lot people are going to think now that this is not really relevant to me since I use a relational database. But if you think about your project 99.00% of the time you can think of your information as a tree too.
See for example the following graph:
You can see in this case even each of this information might be stored in different tables it is possible to map the information into a tree. Just think about your projects and how the data is structured in it. To me it always helps to do a quick whiteboard drawing and I guarantee you, that you will find a lot possible trees for your project too.
Why the Hyperwallet API’s?
You might wonder why I’ve chosen to use the Hyperwallet API’s. You’ve probably haven’t even heard of Hyperwallet. Hyperwallet is a Payout company. Every time a company needs to pay people (such as App stores that need to pay us developers the revenue of our app sales) Hyperwallet comes into play and can ease that money transfer in a fast, secure and cheap ways for companies. And that on a world scale.
I work for Hyperwallet as Software Team Lead and was heavily involved in the development of our new REST API’s. Since we do not have the bandwidth to develop a GraphQL version of our APIs at the moment I thought that would be a great learning experience for me to explore wrapping a REST API behind a GraphQL API.
Can it be useful to wrap REST behind a GraphQL Server?
You won’t get the big performance boost you could get (as you could optimize your database queries according to the requested information) but you still will gain some performance optimization, since the client just needs to do one API call instead of multiple once to gather all information. Server to server calls tend to be way faster and more reliable than for example a mobile device to server API call.
The technology stack
For the server I am going to use express (webserver), express-graphql (middleware for graphql), graphql (graphql core) and superagent (API client). For building the project I am also going to use babel so I can use ES6+ features for this project (that way we can use es6 and still run on older Node versions without any problems).
Where to find the Code?
Before we get started into the details of this implementation you can download the full project from Github (github.com/fkrauthan/hyperwallet-graphql-server). This article itself will just contain snippets out of the project with a proper explanation but the code itself is already very well documented and should help you to understand how this implementation works.
How can I get the code to work?
To get your own set of API credentials to play around with just head over to the Hyperwallet Program Portal and sign up for a free sandbox access. After that, login into your new created account and go to the Credentials page to get your API Username. Your password is going to be the same one you used to login into your Sandbox account. Otherwise you can also change your API password on that page. To have some Users to retrieve via GraphQL I recommend going thru the Tutorial a couple of times (use the auto populate feature to go thru it blazing fast).
Last but not least you have to copy the file config.js.dist
and rename it to config.js
. In that file you save the API credentials we just received. That is all to do. Now you can just start the GraphQL server with npm run dev
and open http://localhost:5000/graphql in your browser to start playing with the API.
Creating a GraphQL Server with express
Luckily with express-graphql it is very easy to start a graphql server.
import express from "express"; import graphqlHTTP from "express-graphql"; import schema from "./schema"; const app = express(); app.use("/graphql", graphqlHTTP({ schema, graphiql: true })); const server = app.listen(process.env.PORT || 5000, () => { const port = server.address().port; console.log(`App listening on port ${port}!`); });
This code should be pretty self-explanatory. We are creating a express app, mounting our graphql server to /graphql and then start the server. The graphiql option turns on a simple web interface to execute graphql calls right from the browser. Especially for our development this is very nice as we do not need to write a graphql client application to test our server.
But you might ask yourself now how does the graphql server know what data to export. That is where the schema comes into play.
Our root schema
Everything starts with a root schema. That pretty much defines what type of root queries we allow. In our example we are going to define two root query.
- List all users
- Get one user by id (token within the REST API)
To set that up we don’t need much code:
import { GraphQLSchema, GraphQLObjectType } from "graphql"; import { allUsers, user } from "./users"; const QueryType = new GraphQLObjectType({ name: "Hyperwallet", description: "Hyperwallet GraphQL API", fields: () => ({ allUsers, user, }), }); export default new GraphQLSchema({ query: QueryType, });
As you can see we first define a GraphQLSchema. The query Property takes a GraphQLObjectType with our root queries (allUsers to list all users and user to load a user based on token).
Defining the user
Our next step is defining the ObjectType User (the schema of our user).
export const UserType = new GraphQLObjectType({ name: "User", description: "A user within the Hyperwallet platform", fields: { id: { type: GraphQLString, resolve: source => source.token, }, status: { type: UserStatusType }, clientUserId: { type: GraphQLString }, profileType: { type: GraphQLString }, firstName: { type: GraphQLString }, lastName: { type: GraphQLString }, email: { type: GraphQLString }, address: { type: AddressType, resolve: source => source, }, allBankAccounts, bankAccount, }, });
This works very similar to how we defined our root query ObjectType. Most of the fields we are exposing here are simple strings. But you might have noticed the status field. Here we set the type to UserStatusType. But what is a UserStatusType? With GraphQL we can define our own types. For example status is a enum. So here is how I defined the UserStatusType:
export const UserStatusType = new GraphQLEnumType({ name: "UserStatus", values: { PRE_ACTIVATED: { value: "PRE_ACTIVATED" }, ACTIVATED: { value: "ACTIVATED" }, LOCKED: { value: "LOCKED" }, FROZEN: { value: "FROZEN" }, DE_ACTIVATED: { value: "DE_ACTIVATED" }, }, });
To see a list of all supported types take a look at the graphql documentation.
Another thing you might have noticed is the address section. The Hyperwallet Rest API returns the address flat within all the other user fields. For the Graph API I thought it might be nice to have the address separated. Luckily again this is very simple. We can create a separate ObjectType for the address (in this case AddressType). And with help of the magic resolve function (we are going to talk about this in more detail a bit later) we just tell GraphQL that the address fields are within the current data object.
The magic resolve function
The resolve method is our key function to map values and load information from a third-party data source (in our case the REST API). As you’ve already seen we have used that resolve method to map the current response object and pass it down to the AddressType so all address related fields are getting extracted from the current data object.
Loading the users
But lets take a look in a bit more complex. If you remember we defined a allUsers and a user field within our root schema. This allows us to load all users or a user based on his token from the API. But so far I haven’t showed you how to do this.
const NO_LOAD_REQUIRED = ["id", "allBankAccounts", "bankAccount"]; export const allUsers = { description: "Load all users", type: new GraphQLList(UserType), resolve: () => loadUsers(), }; export const user = { description: "Load a specific user", type: UserType, args: { id: { description: "The user token", type: GraphQLString, }, }, resolve: (root, { id }, context, info) => resolveSingleItem(id, () => loadUser(id), info, NO_LOAD_REQUIRED), };
This code should look very familiar as it is similar to just defining a field for our regular Schema. And you find our magic resolve method again. Just in this case of just returning the first parameter like in our address example, this time we call our API method loadUsers (for allUsers) or loadUser (for user). You might have noticed that retrieving a single user is wrapped in a custom method called resolveSingleItem.
Filtering out not needed API calls
We could make our GraphQL server really dumb and always execute a GET call to retrieve the user but imagine a query like the following:
{ user(id: "usr-test-token") { bankAccount(id: "trm-test-token") { bankId } } }
In this example we are not loading any information that would require a call to the users endpoint. So this method just checks all requested fields from the query against a static list of fields that do not require a call to the users endpoint. If there is any field requested that is not part of that list it executes the API method.
export default function resolveSingleItem(id, load, info, noLoadRequiredFor) { const rootSelectionSet = info.fieldASTs[0].selectionSet; const hasRequiredField = !!rootSelectionSet.selections .find(el => noLoadRequiredFor.indexOf(el.name.value) === -1); if (hasRequiredField) { return load(); } return { token: id, }; }
The nice thing about resolve is, that if you return a promise it waits until you resolve that promise before filling the specified type. If you do not return a promise you return the object that which will be used to populate the specified type.
As loadUser and loadUsers is just a simple API call I won’t show you the code here but feel free to check the github project for the current implementation.
Loading the bank accounts
If you remember we had a bankAccount and a allBankAccounts field in our user Schema. It shouldn’t be a big surprise to you that this works exactly the same way as our user and allUsers field within the root Schema.
const NO_LOAD_REQUIRED = ["id"]; export const allBankAccounts = { description: "Load all bank accounts for this user", type: new GraphQLList(BankAccountType), resolve: root => loadBankAccounts(root.token), }; export const bankAccount = { description: "Load a specific bank account for this user", type: BankAccountType, args: { id: { description: "The bank account token", type: GraphQLString, }, }, resolve: (root, { id }, context, info) => resolveSingleItem(id, () => loadBankAccount(root.token, id), info, NO_LOAD_REQUIRED), };
What is next?
Now we have already a simple GraphQL server that allows us to retrieve users and bank accounts within the Hyperwallet REST API. We can easily add more endpoints with the described patterns here. It is mainly a thing of writing the Schemas with all the fields.
But things do not stop here. The next thing I am going to look into is creating and updating information (there is already a basic proof of concept in the Github repository). But so far I am not very happy as it looks very verbose to do so.
Another thing that I might look into is, the support of paginating as for large data sets this might be very important.
The last thing I will investigate in the future is the support for authentication. In theory you can easy add any express middleware to run before hitting your graphql endpoint. Within graphql you might have noticed the context parameter within your resolve methods. You can populate anything into this and it will be carried thru out all your resolve calls and allows you to read username, password or any other information you received from your authentication middleware.
Do you use GraphQL?
Thank you for reading my small introduction of how to wrap an existing REST API behind GraphQL to allow your Client applications to do more efficient data requests.
Are you using GraphQL for your projects? Maybe even already wrapped a REST API behind GraphQL? Let me know in the comments what I could improve in my approach or if you have any tips for the things I want to implement in the feature in my “What is next?” section.
- nss-run in 2018 (or what did change over the last 2 years) - April 10, 2018
- From REST to GraphQL a real world experiment - January 17, 2017
- nss-run: A new simple build tool for Node.js - December 12, 2016