Hasura GraphQL Practical Guide

Julia Suarez
Apr 22 · 17 min read
Hasura console

During this guide you will have a playground available to understand how to build and maintain a GraphQL API using Hasura, it’s recommended that you are familiar with what is GraphQL and its advantages over REST since this is a practical guide, you can learn the basics about GraphQL in this tutorial.

The code is available in this Github repository.

Technology Stack

Before starting this tutorial, make sure you have installed and understand the basics of:

  1. git: Code version management
  2. docker: Tool to build and manage containers
  3. docker-compose: Tool to manage several containers
  4. NodeJS and npm: Node is used only to set up Hasura actions and triggers.

Note: To fully explore Hasura, some examples use a basic NodeJS server with express however this is just for demonstrating purposes and understanding NodeJS is NOT required.

Quick Start

For this guide, I have prepared a playground repository with everything you need to fully test the tool and build your own GraphQL API from scratch. To avoid the overhead to set up a relational database with data and relationships, we will use an existing one called Pagila a Postgres Database Sample taken from this great article with a list of example databases, this database is about a DVD rental store.

I recommend you to use a database explorer tool like DbBeaveror DataGripso you can take a better look at the database structure, however, it’s not important to understand everything, you just need to get familiar with it since we will query some data and change it.

Note: I wrote this tutorial so you can set up everything step by step, however, if for some reason you just want to run and test the final results, this document explains how to do it

Steps to set up the project

  1. Clone the repository:
    git clone https://github.com/jsilversun/hasura_tutorial
  2. Install the dependencies:
    npm install
  3. Clone the Pagila SQL scripts to the database folder
    npm run database:clone
  4. Run npx hasura init hasura-tutorial --admin-secret secret afterward, you will see a new folder named hasura-tutorial this will contain all the settings you need for your particular Hasura instance like metadata, actions, and migrations
  5. Run docker-compose up -d to start the docker containers specified in the docker-compose.yml file

Note: By running docker-compose down you can delete your containers and then run docker-compose up -d to start from scratch (This will reset both the API and the database).

Then you will see the following file structure:

Note: If you are interested in understanding the project’s file structure, you can read this doc.

And you can see your running containers with the docker ps command, which confirms we have a GraphQL API running in the 8080 port and the database is using the 5432.

Now we can use the Hasura Console which is a graphical tool to explore or change settings in our new GraphQL API.

Run npx hasura console --project=hasura-tutorial

Then you will be redirected to http://localhost:9695/ and you will see how the Hasura Console looks like:

Now we are ready to start!

Set up the GraphQL API

  1. Go to the Data tab
  2. Click on Track all tables
  3. Click on Track all relations

Now you have your own GraphQL API ready to go!

Basics

In this section we will learn the three main operations you can do over a GraphQL API. Go to the GraphiQL Tab.

Query

Remember to check Hasura queries documentation so you can learn more about the API, in this tutorial we will explore different examples but you can try your own examples from whatever you want to try in the database.

Queries are a simple way to get data from a backend, in this case, we can get information from the database by using Hasura.

Get all available actors

query getActors {
actor {
actor_id
first_name
last_name
}
}

Get an actor by id

query getActorById {
actor_by_pk(actor_id: 1) {
actor_id
first_name
last_name
}
}

Get an actor by id with his films

query getActors {
actor_by_pk(actor_id: 1) {
actor_id
first_name
last_name
film_actors {
film {
film_id
description
}
}
}
}

Get films with a replacement cost lower than 10

query getFilmsFrom {
film(where: {replacement_cost: {_lt: 10}}) {
film_id
description
replacement_cost
}
}

Aggregation queries

Get the average rental rate for films

query {
film_aggregate {
aggregate {
avg {
rental_rate
}
}
}
}

Subscription

Hasura subscriptions documentation

“A GraphQL subscription is essentially a query where the client receives an update whenever the value of any field changes upstream.

Subscriptions are supported for all kinds of queries. All the concepts of queries hold true for subscriptions as well.”

Subscriptions are an incredible feature for automatic updates in the client, let’s see it in action.

Run the following GraphQL subscription:

subscription getActorById {
actor_by_pk(actor_id: 1) {
actor_id
first_name
last_name
}
}

You will see it's still running since it has a pause button, now open another Hasura console in a different browser tab, go to the Data section, look for the actor table click the editbutton and change the name of the actor from PENELOPE to PENELOPE!

Go back to your original tab and you will see how the result changed automatically

This amazing feature will allow the frontend clients to get automatic updates so your system will be refreshed in real-time, subscriptions apply to any Hasura query, test this feature further so you can see its capabilities.

Mutation

Let’s roll back the name of the actor we changed earlier in the subscription example from PENELOPE!to PENELOPE

mutation updateActorById {
update_actor_by_pk(
pk_columns: {
actor_id: 1
},
_set: {
first_name: "PENELOPE"
}
) {
actor_id
first_name
last_name
}
}

Then if you refresh the actortable in the Data tab and you will see the record was updated successfully.

Authentication

For authentication, we will explain how to use firebase to get a JSON Web Tokens (JWT) and then perform queries to the API, since I like practical examples I prepared two endpoints that you can use to get some JWT’s to use with Hasura.

Note: If you want to learn how these endpoints work and how you can run them locally please read this doc.

For more information in this link you can read more about what JWT are, in this guide we will focus on how to actually use them and see their content so we can build our secure API.

Authentication

  1. To get a JWT for the staff role visit the URL https://hasura-guide.herokuapp.com/staff and copy the token
  2. To get a JWT for the customer role visit the URL https://hasura-guide.herokuapp.com/customer and copy the token
  3. With each one of the tokens, go to the Hasura console
  4. Add anAuthorization header and paste the token like this: Bearer <pasted_token>
  5. Press the Decode JWT button shown in the screenshot below

In the FULLY PAYLOAD DATA section you will see the entire JWT, in few words the entire token you copied from the page is nothing more than a JSON encrypted as a string and signed by firebase.

Authorization using JWT is just sending this token in the headers of any HTTP request, then Hasura will decode the JWT and get the user identifiers like the user id and email, which can be used to see if the user is authorized or not to do the requested operation, but first, how does Hasura know that he can trust the JWT received?

Well, it’s important to notice, Hasura receives an environment variable HASURA_GRAPHQL_JWT_SECRET this is another JSON used to set up the JWT authentication feature.

Extract from the docker-compose.yml file to see how the HASURA_GRAPHQL_JWT_SECRET is passed to the GraphQL Engine

If we visit the URL provided in the jwk_url property we will see the following:

I know this looks a bit confusing but this is just a JSON Web Key, it’s a public key that can be used to verify if a JWT has been signed using it. Hasura will perform an HTTP request to the jwk_url specified to get the public key(JWK) and then it will proceed to verify each JWT received to see if it has been signed with it, if the verification succeeds, then it means Hasura can trust the JWT content and then use the values to perform validations and see if the user is authorized or not.

Hasura documentation about this subject is great, it has all the information you need to know, in this guide I just want to give you the easiest example so you can test it on your own.

Note: Please notice in the jwk_url we are specifying the url where firebase publish their JWK used to sign the tokens, however you should specify the audience in the HASURA_GRAPHQL_JWT_SECRET to guarantee that only signed tokens by firebase for your specific application are considered valid otherwise any other JWT signed by firebase for other projects will be marked as valid for hasura and this is not safe, for the simplicity of the example this setting is omitted, make sure you read hasura documentation to understand this better.

Authorization

Now we have two sample JWT that Hasura recognizes as valid, however, if we attempt to perform any query with them we will get an error because none of those roles are recognized by Hasura.

Note: You need to disable the x-hasura-admin-secret header so hasura will use the JWT to perform the authentication and authorization.

Let’s allow customers to query their own information.

Go to the Data tab, look for the customer table and go to Permissions tab, enter the role customer in the “Enter new role” input and click on the pencil in the select section.

Now click the With custom check radio button and you will see a form that will help you build a JSON used to specify the permissions, however, you can just write the JSON on your own and then paste it in the small input above, paste the following JSON into the mentioned input.

{
"_and": [
{
"active": {
"_eq": 1
}
},
{
"email": {
"_eq": "X-Hasura-User-Email"
}
}
]
}

This JSON is saying: A user can see the record(s) in the customer table where the record’semail is the same as the one provided in the JWT and the record is active.

Also, we can specify which fields can be queried through Hasura, this way we can keep hidden internal fields like the active and activebool fields since it’s not useful to have them visible for the customers.

After you finish selecting the fields that will be visible, you can click on Save permissions.

Pretty simple but powerful. Now let’s test this, first, we need to change the email of one of the customers to customer@example.com since the Pagila database had different email examples.

Let’s change the email of the first customer from MARY.SMITH@sakilacustomer.org to customer@example.com

Then we go to the GraphiQL tab and perform the same query we did before:

Notice how we are not using any filter operators and yet we only get one customer record because it matches the email from the JWT which we can trust since it was signed by firebase. If the customer table had the user id from firebase we would have been able to use it as well.

Now, let’s allow the customer to query their rental records. Go to the Data > Rental > Permissions then paste the following JSON in the same way we did before, select all the fields and afterward save the permissions.

{
"customer": {
"email": {
"_eq": "X-Hasura-User-Email"
}
}
}

Now we can perform a query like this one:

{
customer {
customer_id
email
first_name
last_name
rentals {
rental_date
}
}
}

And also you can query the rentals directly without having to query the customer first, the permissions will work anyway.

{
rental {
customer_id
rental_id
rental_date
return_date
}
}

You can even allow the user to perform aggregation queries by checking a field in the permissions tab.

Then we can perform aggregation queries like counting the total rentals for the authenticated user

Migrations

Migrations are SQL instructions to indicate how the database structure and data can change over time without having to wipe the entire database to do so.

For example, I realized the activebool field in the table customer is not useful since all the records have a truevalue and there is an existing active field with values of 1 or 0 to indicate if the customer is active or not, to remove the unused field we can use the Hasura UI to remove the field.

Then we will get this message:

So, a cool Hasura feature, is that you can use the Hasura console (Web UI) to modify the database structure and automatically generate migrations so the system will be modified automatically.

Let’s take a look at the migrations folder and we can see what was generated:

We have our first migration!

Each migration is a folder with the timestamp as a prefix, this is used to guarantee migrations run in the order shown in the folder so they can mutate the database and the data with it.

The up.sql file just contains a SQL sentence to remove the column.

And the down.sql has the following SQL sentences to restore the field.

So what can we do if we realize we need to roll back our system since it was a mistake to remove the activebool?

First, we can use the hasura cli to ask the migrations status by running at the hasura-tutorial folder:

npx hasura migrate status

And get something like the following:

Now we can ask Hasura to roll back that migration by its version (The number in the migration folder we saw earlier).

hasura migrate apply --version 1611884791540 --type down

If we go back to the Data tab we will notice the activebool field is back and since we had existing data, the value was set to NULL.

We can modify the down migration so it can set the activebool field to true that way we can “travel in time” in the database structure, this example is just meant to teach you that you can modify the generated SQL’s as you please since it’s normal that you might want to perform more complex operations to the database that cannot be generated by using the Hasura UI automatically.

We will simplify the SQL sentence of the down migration like this:

ALTER TABLE "public"."customer" ADD COLUMN "activebool" bool DEFAULT true NOT NULL;

Then we can run the following sentences to remove the field again and to rollback one more time

If we refresh the Hasura console we will see the activebool field is back with true as the default value for all the records as it was initially.

As a final tool about database migrations, what happens if you want to do any complex operation like adding a trigger or several updates to the database?

Well you can go to Data > Migrations section and write some SQL change the default value of the activebool column to false

This will generate a new migration.

However, we can see it doesn’t have a down.sql file, it’s important we add one so it’s possible to roll back database changes whenever possible.

Do the same process mentioned for the first migration to roll back and apply again the migrations to verify everything is working as expected.

Metadata

Hasura metadata is a data structure used to specify the GraphQL API settings like permissions per role or fields, Hasura defines it as:

“All the actions performed on the console, like tracking tables/views/functions, creating relationships, configuring permissions, creating event triggers and remote schemas, etc. can be exported as a JSON/yaml metadata file which can be version controlled. The metadata file can be later imported to another Hasura instance to get the same configuration. You can also manually edit the metadata file to add more objects to it and then use it to update the instance.”

This data structure is critical and understanding it will help you debug any problems when your API is unable to run due to inconsistent metadata.

As mentioned earlier, all settings are stored in the Hasura folder we created named hasura-tutorial in here we have a folder called metadata which looks like this:

Hasura has many features, more than the ones we are covering in this tutorial, however as you go testing features I recommend you to get familiar with each structure so you can debug problems in case they occur. The main one is the tables.yaml:

It’s actually a simple data structure once you get familiar with it. It defines all the tables available with their name, fields, relationships, and even permissions as we can see below.

In this section we can see how Hasura stores the information we saved in their UI in the first part of this tutorial, this specifically describes the select permissions over the customer table for the customer role.

Besides the data structure is simple and you can understand it by being familiar with yaml files, inconsistent metadata is a scenario that can happen when for example a field is removed from the database by using a migration but the field is still present in the metadata, if you restart your GraphQL container you will end up with your API not being able to run due to inconsistent metadata.

The first step is to always look for the logs to understand what might be causing the issue, and that if it’s a situation like I described earlier about some field that is no longer present, then you can update the metadata manually to fix the issue and then restart the container.

Tip

You might use the command docker logs <container_name> to read its logs but I recommend using the dashboard for docker

There are other tools that can improve the development experience and help you debug any problems with metadata like portainer, choose the best for your use case.

As a final recommendation, make sure you do a code review over the metadata changes on your project, don’t treat these changes like something generated since this can break your GraphQL API.

Actions

Hasura is an amazing tool to generate an API that allows CRUD operations over a database, but what if we need some custom logic like using a payment gateway for the movie rental?

For these scenarios we can use actions to extend our existing Graphql API, in few words we can tell Hasura to register custom mutations and then act as a middleware between a REST endpoint and the final client. Let’s see this with an example.

Go to the Actions section

And create a new one like this:

Click on Create

Go back to the project folder and run npm run server:startthis will start a basic NodeJS server with an endpoint to return a string like “Greetings, <name>! Have a nice day”.

This is the sample code for both the Hasura trigger and Hasura action.

Triggers

To finish this tutorial we will use Hasura triggers, which is a sure feature where a REST endpoint can be called when a change occurs in the database either an INSERT, UPDATE or DELETE.

Go to the Events tab and click on the Create button.

Now, we will go and update the activebool field in the customer table

Now you can go to Events > Processed Events tab and you can see the event was processed successfully.

You can see both the request and the response of the trigger.

This feature is useful to automate any action that must happen when something changes in the database, maybe like sending an email or notification, start background processing tasks, etc.

Conclusion

I hope this tutorial has been useful to you to learn how to quickly build a GraphQL API from an existing database and how to extend it with custom logic or background processing tasks, Hasura has many features that are not covered in this guide, and the best way you find out about them is for you to read the documentation and keep testing everything on your own.