Creating a Unified API in GraphQL
Learn how to create a unified api with GraphQL and Hasura so that there is only one endpoint to integrate with and you can get all the data you need in a single place.
Join the DZone community and get the full member experience.
Join For FreeWhat is Unified API?
A unified API is a single place to get all your data. It is one API that acts as an orchestration layer to fetch all downstream APIs. The benefit of this for consumers is that there is only one endpoint to integrate with, and get all the data they need from a single place.
To give an analogy, think of shopping for groceries. The things you need when shopping are things like food, flowers, stationary, etc. There are specific stores that specialize in all these things - such as Trader Joe's for food, Home Depot for flowers, Office Depot for stationery. These stores may be spread out throughout the city, so you will end up making separate trips for each of those items. If you want to optimize, you could go to one single shopping complex and aim to hit all stores in that parking lot. Or you could do yourself multiple trips and go to one store that sells all these items: Target.
This is similar to how an enterprise software gets created. Often, in an enterprise, there are multiple APIs available at our disposal. These APIs would accept similar parameters such as user id, auth token, etc. However, to get our entire set of data, often we will need to integrate with multiple APIs to do the job.
To further explain this, let's build a fictional app. Let's say that we are going to build a "cost-splitting” app. This app helps user split the cost of a dinner, trip, purchase amongst themselves - like a combination of Splitwise and Venmo. For this article, we are only going to consider the API portion of this app. In short, our product requirements are:
Build an API that provides a way to equally distribute money between friends and charge for dinner.
- A user should be able to enter an amount and select their friends from their friends list.
- A friend choose to pay with any payment method, such as credit card, PayPal, Venmo.
- If the friend is not already a customer, we should sign them up.
- Once the friend has paid, display a confirmation message to both the payer and payee.
- We should be able to send email reminders if a friend hasn't paid in 2 weeks.
In an enterprise, often there would be multiple APIs available that give us back part of the data we asked for.
For example, the following endpoints look promising:
- /user: Can be used to display information about a user
- /payments: Can be used to process payments and get payment options
- /notifications: Can be used to send email, mobile, or app reminders
- /signup: Can be used to trigger a new user flow and sign users up
A user can choose to get paid or pay with any payment method such as credit card, PayPal, Venmo, etc.:
- GET payment methods eligible for a user
- POST /payments
- Auth API to pass authenticated user
- Payment processing API to request funds
Process payment from a user:
- GET user profile of user
- GET payment methods eligible for a user
- POST payment processing API to dispatch funds
If the friend is not already a customer, sign them up:
- GET user profile of user
- Call sign up API
Once the friend has paid, display a confirmation:
- Ability to send email reminders
- Send notifications to friends
That's 6 different endpoints we have to integrate with. Every new API integration brings:
- New documentation we need to read
- Errors we need to handle
- Parameter tokens to pass, which may often be same across multiple APIs
- Input validations that we need to do
- New versions to keep up with
That's a lot of time to spend on these tasks. Surely there has to be a better way of doing things.
Going back to our shopping example, what if instead of going to all these stores separately, we could instead make one trip and get all the stuff we need from one store such as Target? Go to one supermarket, and get all your groceries, stationery supplies, electrical needs, plants, bedding supplies from one store at a discounted price.
This is what Unified API is! One API to get all the data you need!
Principles of Unified API
- There is one API to fetch and post data.
- Abstract away complexities
- One time auth
- One version
- One documentation
- Leverages existing downstream micro-services.
Building a Unified API With Existing APIs
Goal: In this example, I will integrate APIs from two different sources - dev.to and GitHub. I will fetch a list of articles from my dev.to account using dev.to's REST API and fetch a list of my recent github repositories using github's GraphQL API. This example will demonstrate how a single GraphQL endpoint can be used to fetch underlying APIs from multiple sources without having to re-integrate.
To get started, here is what we need:
- Existing APIs : REST or GraphQL
- Hasura.io cloud account
- Netlify
Why GraphQL for Unified API?
- Gives clients the power to ask for exactly what they need and nothing more
- One endpoint to call any resource
- Auto documentation - hence easy to generate inline documentation
- Great developer experience with tools like graphiQL
- Constant version hence updates are available to the clients immediately without having to re-integrate
- Powerful field-level instrumentation
Why Hasura?
- Existing graphql API without having to worry about heavy-lifting on the server side.
- Scalable and highly available.
Setup a Unified API
Steps:
Login to Hasura Cloud and create a project.
For Dev.to's REST API, we are going to use "Actions" and for Github's GraphQL API, we are going to use "Remote Schemas" in Hasura Cloud.
Setup Actions for REST API:
- We first need to map our REST responses with the expected format of GraphQL API. Here, we are going to create an action definition - which represents the input parameters of the operation, and output response type.
- Since we are fetching dev.to's API, we will be using GraphQL's Query type. Because we will be fetching articles for a given username, we need to supply type Query
type Query {
articles (
username: String!
): [ArticleResponse]
}
- Now when the response comes back, we will only query the fields we are interested in. These fields will be part of the
ArticleResponse
object -
xxxxxxxxxx
type ArticleResponse {
title : String!
description : String
id : Int!
date_published : String
url : String
reactions : Int
profile_image : String
}
- One thing to note is that the data coming from API must be in the same format as you expect in your action definition. Here is the format of response that comes from dev.to's REST API -
xxxxxxxxxx
[
{
"type_of": "article",
"id": 576802,
"title": "JavaScript Form Validation – How to Check User Input on HTML Forms with JS Example Code",
"description": "Forms are ubiquitous in web applications. Some apps such as Gmail use forms to collect data to sign u...",
"readable_publish_date": "Jan 20",
"slug": "javascript-form-validation-how-to-check-user-input-on-html-forms-with-js-example-code-51fe",
"path": "/shrutikapoor08/javascript-form-validation-how-to-check-user-input-on-html-forms-with-js-example-code-51fe",
"url": "<https://dev.to/shrutikapoor08/javascript-form-validation-how-to-check-user-input-on-html-forms-with-js-example-code-51fe>",
"comments_count": 0,
"public_reactions_count": 39,
"collection_id": 6962,
"published_timestamp": "2021-01-20T18:20:26Z",
"positive_reactions_count": 39,
"cover_image": "<https://res.cloudinary.com/practicaldev/image/fetch/s--8HKxw0Vu--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/i/xbo9znz0mi9ac059ualo.png>",
"social_image": "<https://res.cloudinary.com/practicaldev/image/fetch/s--1cPlMcDj--/c_imagga_scale,f_auto,fl_progressive,h_500,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/i/xbo9znz0mi9ac059ualo.png>",
"canonical_url": "<https://www.freecodecamp.org/news/form-validation-with-html5-and-javascript/>",
"created_at": "2021-01-20T06:02:51Z",
"edited_at": "2021-01-20T18:20:47Z",
"crossposted_at": null,
"published_at": "2021-01-20T18:20:26Z",
"last_comment_at": "2021-01-20T18:20:26Z",
"tag_list": ["javascript", "html", "webdev", "development"],
"tags": "javascript, html, webdev, development",
"user": {
"name": "Shruti Kapoor",
"username": "shrutikapoor08",
"twitter_username": "shrutikapoor08",
"github_username": "shrutikapoor08",
"website_url": null,
"profile_image": "<https://res.cloudinary.com/practicaldev/image/fetch/s--tIWx3yce--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/197069/406b42c3-4552-4a6f-8ce9-39a58e542a5a.jpg>",
"profile_image_90": "<https://res.cloudinary.com/practicaldev/image/fetch/s--G1g_6HrU--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/197069/406b42c3-4552-4a6f-8ce9-39a58e542a5a.jpg>"
}
}
]
In order to do match my expected Query format, I had to tweak my API response a little by creating a handler:
xxxxxxxxxx
const dataMapper = dataJson.reduce( (arr, item) => {
arr.push ({
title: item.title,
id: item.id,
date_published: item["readable_publish_date"],
url: item.url,
reactions: item["public_reactions_count"],
profile_image: item["social_image"]
})
return arr;
}, [])
This handler can be added to the actions like so. Click on Save and open GraphiQL to use this action
5. Setup Remote Schemas for GraphQL API:
- Click on "Add" in Remote Schemas sidebar.
- Enter your GraphQL API endpoint :
https://api.github.com/graphql
- Enter any authorization headers in the Headers section
- Open GraphiQL to use this remote schema along with REST actions.
6. Your explorer now has REST endpoints and GraphQL endpoints integrated as GraphQL queries. Users can use your Hasura GraphQL endpoint /graphql
endpoint to fetch both REST and GraphQL resources without having to change client-side integrations.
I hope this helps and gives you ideas on implementing a Unified API in your own enterprise. Give Unified API a shot with GitHub and Dev.to APIs in Hasura and let me know what you think. Send me a message on Twitter if you have any questions.
Opinions expressed by DZone contributors are their own.
Comments