Turn your Python REST API to GraphQL using Hasura Actions

14 August, 2020

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.

In this post, we will look at using Hasura Actions to convert your Python REST API to GraphQL.

Typically, setting up a GraphQL server from scratch would involve writing the GraphQL schema, type definitions and wiring it up with the resolvers. With Hasura Actions, the complexity of writing resolvers is reduced as one can just write a REST API POST endpoint and use that as a resolver for the GraphQL query or mutation.

Hasura gives you instant GraphQL CRUD for databases (currently Postgres) which should cover most of the data fetching and realtime subscription use cases. But when there's some business logic that needs to be written, we can make use of Hasura Actions to map a REST API to be a custom resolver. We will look at how to do this with Python in this tutorial.

Create a Hasura Cloud Project

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.

Hasura Cloud Database Setup

After signing in with Heroku, you should see the option to Create project.

Heroku Database Deployment on Hasura Cloud
Heroku Database Provisioning via Hasura Cloud

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.

Create an Action

Actions are a way to extend Hasura’s schema with custom business logic using custom queries and mutations. Actions can be added to Hasura to handle various use cases such as data validation, data enrichment from external sources and any other complex business logic.

Now let's head to the Actions tab on the Hasura Console and define the GraphQL types for our app.

Action Definition

type Mutation {
  registerUser (
    name: String!
    email: String!
    password: String!
  ): UserOutput
}

Types Definition

type UserOutput {
  id : uuid!
}

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.

Let's verify if the custom mutation was generated by heading to the GraphiQL tab and trying it out.

mutation {
  registerUser(name: "Praveen", email: "[email protected]", password: "mysecretpassword") {
    id
  }
}

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 Python 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 python flask server. Since we are generating the API from scratch, we need the full server setup instead of just the POST handler.

We are using the python framework Flask for this example, there's also python-fast-api boilerplate that can be used to replicate a similar example. In fact, any python server that can serve a POST endpoint can be used to swap with this example.

Business logic required:

  • Receive the action arguments name, email and password on request.
  • Convert the plaintext password input into a hashed secure password.
  • Send a mutation to Hasura to save the newly created user with the hashed password.
  • Return the created user object to signal success, or else error.

The generated codegen (registerUser.py and registerUserTypes.py) can be copied to your filesystem to start making the changes to the handler.

from registerUserTypes import registerUserArgs, UserOutput
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/registerUser', methods=['POST'])
def registerUserHandler():
  args = registerUserArgs.from_request(request.get_json())
  print(args)
  # business logic here
  return UserOutput(id="1").to_json()

if __name__ == '__main__':
  app.run(debug = True, host = '0.0.0.0')
registerUser.py
from dataclasses import dataclass, asdict
from typing import List, Optional
from enum import Enum, auto
import json

@dataclass
class RequestMixin:
    @classmethod
    def from_request(cls, request):
        values = request.get("input")
        return cls(**values)

    def to_json(self):
        return json.dumps(asdict(self))

@dataclass
class UserOutput(RequestMixin):
  id: any

@dataclass
class Mutation(RequestMixin):
  registerUser: Optional[UserOutput]

@dataclass
class registerUserArgs(RequestMixin):
  name: str
  email: str
  password: str
registerUserTypes.py

Once you copy the codegen, you can setup flask and start running the server:

Setup Flask

You can start by installing flask:

pip3 install flask

and then run the python server using:

python3 registerUser.py

With this you should start seeing your flask server running on port 5000.

With this, we need to update our handler URL for the Action so that the HTTP call works. Add the registerUser endpoint that handles the mutation. So the final handler URL will look like http://localhost:5000/registerUser.

Since we need a way to communicate with our python server from the Hasura Cloud instance, we need to expose the python server on a public URL. We can host this server on a cloud instance and point that URL/IP. But for brevity of this tutorial, we can use ngrok, which gives public URLs for exposing your local servers to be communicated by a cloud instance.

Once you have ngrok installed, you can run the following command:

ngrok http 5000

This should expose your local python server running on port 5000 on a public URL (something like http://b4318818ea61.ngrok.io).

Go back to the Modify tab of the registerUser Action that was created on the Hasura Console. Update the handler URL to the above one.

It would look like http://b4318818ea61.ngrok.io/registerUser. Note the usage of registerUser since that's the endpoint we are handling the mutation in.

Finally let's try out the same mutation through GraphiQL and the connection should work returning a dummy <value> for id.

registerUser Mutation served via Python

Alright! We have a working GraphQL API that is resolved using a Python REST API written using Flask.

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.

Connecting to database from inside flask

You can connect to the database and perform any operations. The easier way to perform read and writes to the database is to use the auto-generated GraphQL API of Hasura inside the handler. By making use of Admin Secret you can perform any query from the backend as you will be simulating an admin permission with no restrictions.

Permissions & Relationships

The access control logic for this GraphQL API can be declaratively configured using Hasura's role based permission system.

Head over to the Permissions tab on the Action page to add a role and allow access.

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.

This permission can be applied to both custom queries and mutations. Let's look at how this will apply for a relationship. 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.

Relationship from action to postgres

The related data also conforms to the permissions defined for the respective table. (ie. users)

Existing REST API

So far, what we have been looking at is create a GraphQL API from scratch by mapping it to a new REST endpoint. What if there is an existing REST API endpoint that you want to reuse as a GraphQL API?

It's quite possible to do this, as long as you are able to handle the request body format that Hasura sends to the endpoint. The format looks something like this:

{
  "action": {
    "name": "<action-name>"
  },
  "input": {
    "arg1": "<value>",
    "arg2": "<value>"
  },
  "session_variables": {
    "x-hasura-user-id": "<session-user-id>",
    "x-hasura-role": "<session-user-role>"
  }
}

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 :)

Supported Frameworks

This was an example with Python Flask. But as you can imagine, this would work with any python server framework that can serve a POST Endpoint. So you can technically use frameworks like Django, Bottle, gunicorn, Pyramid, Tornado, web2py and so on.

Deploying Python API

The python API can be deployed as a serverless endpoint, lambda function if you want to architect your application in the 3factor architecture. It really doesn't matter where you deploy since at the end of the day, Hasura just needs a HTTP POST endpoint for each Action handler.

This way, you can host a bunch of endpoints in the same server or hand off to serverless functions for each modular functionality. The GraphQL type system and metadata lives at the Hasura layer and hence your serverless function or endpoint doesn't need to worry about that part of the logic.

search icon

About Hasura

Hasura allows you to mobilize & federate your organisation’s data by building a powerful, secure & flexible GraphQL API, that can query data in your databases, HTTP services, serverless functions as well as third party APIs.
Like what you read? Join our team! We’re hiring

Praveen Durairaju

Praveen Durairaju

Application Engineer. Tech Enthusiast. Follow on Twitter - https://twitter.com/@praveenweb

Read More