Turn your Go REST API to GraphQL using Hasura Actions
This post is a part of a series which goes over how to convert an existing REST API to GraphQL by just defining types and configuring endpoints.
Introduction
In this post, we will look at using Hasura Actions to convert your Go REST API to GraphQL.
Typically writing a GraphQL server from scratch will involve setting up the GraphQL schema, type definitions and wiring it up with the resolvers in the language/framework of choice. The hard parts about this server component is the resolver code for someone who is used to writing a REST API with GET or POST and handling auth logic for each type; which becomes complex as the app grows bigger.
Hasura reduces bulk of this complexity of setting up a GraphQL API from scratch by giving you instant GraphQL CRUD for databases (currently Postgres) which should cover most of the data fetching and realtime subscription use cases.
In case you are starting from scratch, we will define the GraphQL types required for the Action and create a simple Express app to handle the resolver bit. Let's take the example of registering a user.
Before we begin, let's create a project on Hasura Cloud to setup the Action. Click on the Deploy to Hasura button below, signup for free and create a new project.
Hasura requires a Postgres database to start with. We can make use of Heroku's free Postgres database tier to try this app.
After signing in with Heroku, you should see the option to Create project.
Once you are done creating the project, click on Launch the Console button on the Projects page for the newly created project and the Hasura Console should be visible. By creating this project, you have already got a GraphQL endpoint for the Postgres database that is globally available, secure and scalable from the beginning.
Now let's head to the Actions tab on the Hasura Console and define the GraphQL types for our app.
Here we are defining the Mutation type registerUser which accepts the name, email and password arguments and returns the id of the user.
We can configure the handler URL later. Click on Create to create the Action.
Now let's verify the generated GraphQL mutation by trying out the sample mutation in GraphiQL.
The above mutation should obviously give a http exception since the Handler URL has not been configured yet. But this test is to verify that Hasura generates mutations for you based on the types defined to query on the same GraphQL endpoint.
Now let's go ahead and actually define the Go app and try out the mutation.
Codegen: Auto generate boilerplate code
Now head over to the Codegen tab to autogenerate the boilerplate code for your Golang server. Since we are generating the API from scratch, we need the full server setup instead of just the POST handler.
The console gives you options to generate code for different frameworks including but not limited to nodejs-express, nodejs-zeit, nodejs-azure-function etc.
Let's run the application locally and test that it works. Create a file, for example main.go or app.go in a directory, and paste the contents of registerUser.go and registerUserTypes.go into it.
Then, running go run <filename>.go will start a REST API on http://localhost:3000
You can quickly check that it's functioning correctly by hitting the endpoint with curl and simulating an Action payload:
Now we need to expose our local API so that our Cloud app can reach it. To do this, we can use a service like https://ngrok.com to create a publically accessible tunneled URL to our local app.
Assuming the use of ngrok, running ngrok http 3000 on your terminal should give a unique URL you can use. Go back to the Modify tab of the registerUser Action that was created on the Hasura Console. Update the handler URL to one generated by ngrok.
Finally let's try out the same mutation through GraphiQL and the connection should work returning a dummy for id.
Alright! We have a working GraphQL API that is resolved using a Go REST API in the background.
Now you can modify your handler code as required to do any business logic; like connecting to a different API, connecting to a database (preferrably using Hasura's APIs) or using an ORM for a different databases etc. Hasura will take care of proxying the GraphQL mutation to the right REST API handler internally.
Permissions
What about permissions? Who will be able to make this GraphQL mutation from the client? Right now, it is configured to be admin only. We can configure roles to allow other kind of users to perform this mutation.
In the above example, I have created a role called public that is allowed to make this mutation. To read more about how the whole Authentication and Authorization works with Hasura, you can checkout the docs.
Relationship Data
Now consider that the Postgres database has users table. We can connect the id of the registerUser output to the id of the users table.
The related data also conforms to the permissions defined for the respective table. (ie. users)
Existing REST API
Now you might be wondering what if I already have a Go server with a bunch of endpoints written to handle some custom business logic. In that case, as long as it's a POST endpoint, you can just define the necessary GraphQL types and make some modifications to the way you handle request body to get it working quickly.
So in our example, the input arguments - name, email and password was wrapped inside an input object. Now if you can make necessary modifications to your existing REST API to handle this request body, your GraphQL API will work as expected :)
Query Action
The above example showcased how to perform a GraphQL mutation. The same workflow can be extended to perform a GraphQL query which proxies to a Go REST API in the background. Typical use cases for query actions include computed fields, data enrichment, data transformations, fetching from multiple databases and API sources etc.
Supported Frameworks
The REST API can be in any framework like Gin, Echo, fasthttp, or serverless functions. After all, Hasura just requires a reachable POST endpoint that it can forward the query to. In case you are going to deploy the API to a serverless function, you will need to follow the format of the cloud provider.