Create a Remote Schema to wrap a REST API with Hasura
What is a Remote Schema
A remote schema is simply an external GraphQL service that you can stitch with Hasura GraphQL Engine for a unified GraphQL API. A common way to incrementally adopt GraphQL in an existing infrastructure, is to wrap pre-existing REST APIs within a GraphQL layer. In this blogpost, we will see how you can do this using Hasura easily.
Example REST API
Suppose, you have a REST API like the following:
GET /users
GET /users/:userId
POST /users
The GET /users
endpoint also takes an optional query param i.e. GET /users?name=abc
and the POST /users
endpoint expects a valid JSON payload in the body.
Let's see how we can wrap this in GraphQL.
Bootstrap a GraphQL Service
We can start by picking up a minimal boilerplate for a GraphQL server, say this: https://github.com/hasura/sample-remote-schema (or jump to final implementation here)
This boilerplate is built using the Apollo Server framework. It is simply a hello
schema i.e. on query { hello }
it returns { data": {"hello": "world"} }
const typeDefs = gql`
type Query {
hello: String
}`;
const resolvers = {
Query: {
hello: () => "world",
},
};
Build the Schema, Fill in the Types
For our REST API (from above), lets start defining some new types and fields. The complete definition of types and fields is what makes our schema
:
const typeDefs = gql`
type User {
id: String!
name: String!
balance: Int!
}
type Query {
getUser(id: String!): User
users(name: String): [User]
}
type Mutation {
addUser(name: String!, balance: Int!): User
}
`;
Roughly our REST to GraphQL mapping looks like the following:
REST | GraphQL |
---|---|
GET /users | users (name: String) : [User] |
GET /users/:userId | getUser(id: String!): User |
POST /users | addUser(name: String!, balance: Int!): User |
We can already notice some extra information about the APIs and this is one of the selling points of GraphQL: we can see input and output type information very clearly. Lets now move to implementing the APIs.
Resolve!
In a REST world, we usually talk about writing handlers for endpoints. In GraphQL, we do a similar thing but call it resolvers!
For the schema that we defined in the previous section, lets add their corresponding resolvers:
const resolvers = {
Query: {
getUser: async (_, { id }) => {
return await getData(MY_REST_URL + '/users/' + id);
},
users: async (_, { name }) => {
var nameParams = '';
if (name) {
nameParams = '?name=' + name;
}
return await getData(MY_REST_URL + '/users' + nameParams);
}
},
Mutation: {
addUser: async (_, { name, balance } ) => {
return await postData(MY_REST_URL + 'users', { name, balance } );
}
}
};
Easy! All we did was proxy the incoming GraphQL request to our REST API.
Finally, we need to deploy this service somewhere and get a URL. There are plenty of cloud solutions that allow you to deploy a Node service easily. You can look at the final implementation with deployment instructions on Heroku here.
Add to Hasura
Once you have gotten an HTTP URL for your above GraphQL service, just head over to your Hasura console and add it as a Remote Schema. In case your remote schema defines few types that are same as Hasura, they will get merged automatically too. Notice that you can also add Authorization
and other headers to your Remote Schema in case your REST API expects such headers.
Now, you should be able to call your remote schema APIs alongwith the default Hasura APIs from the Hasura GraphQL Endpoint. And that's it!
Conclusion
It is easy to wrap your existing REST API into a GraphQL API. The advantages of these are many:
1) you can incrementally adopt GraphQL in your existing infrastructure,
2) you get added benefits over REST APIs like type safety and more readability,
3) you get a unified API layer for all your APIs.
With the guide presented above, you can get started with your GraphQL wrapped REST API in minutes. You can then add this GraphQL service as a Remote Schema in Hasura for automatic schema-stitching.