Laravel And Hasura for Instant GraphQL

In your infrastructure stack, Laravel and Hasura compliment each other extremely well.

Laravel has it's ORM Eloquent, which makes it super easy to manage your database, tables and model relationships. When running Laravel on Serverless (Laravel Vapor or Bref.sh) it's even easier to wire up your queues with Amazon SQS so you never have to worry about scalability again.

Hasura works really well because it magically converts GraphQL queries to SQL and is able to intelligently pick up your table relationships through the use of Foreign Keys.

Lets Get Started!

Lets setup a fresh installation of Laravel 9.

$  create-project laravel/laravel hasura-laravel-demo

Next, we'll start setting up our infrastructure. Our docker containers will look like this.

  • Laravel App
  • Postgres
  • Hasura

Lets install Laravel Sail to help manage our local docker containers.

$ composer require laravel/sail --dev

Next you'll need to configure Laravel sail with the available services.

$ php artisan sail:install

When running the command, you will be asked which services to install, make sure you select Postgres. Hasura will not work with MySQL.

Your terminal output will look like this.

php artisan sail:install

 Which services would you like to install? [postgres]:
  [0] mysql
  [1] pgsql
  [2] mariadb
  [3] redis
  [4] memcached
  [5] meilisearch
  [6] minio
  [7] mailhog
  [8] selenium
 > 1

Sail scaffolding installed successfully.

Now we will manually add a Hasura container to our docker-compose.yml.

# docker-compose.yml

graphql-engine:
  image: hasura/graphql-engine:v2.3.0
  ports:
    - '8080:8080'
  restart: always
  environment:
    ## postgres database to store Hasura metadata
    HASURA_GRAPHQL_METADATA_DATABASE_URL: postgres://${DB_USERNAME}:${DB_PASSWORD}@pgsql:${FORWARD_DB_PORT:-5432}/${DB_DATABASE}

    HASURA_GRAPHQL_DATABASE_URL: postgres://${DB_USERNAME}:${DB_PASSWORD}@pgsql:${FORWARD_DB_PORT:-5432}/${DB_DATABASE}
    ## enable the console served by server
    HASURA_GRAPHQL_ENABLE_CONSOLE: 'true' # set to "false" to disable console
    ## enable debugging mode. It is recommended to disable this in production
    HASURA_GRAPHQL_DEV_MODE: 'true'
    HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log

    ## uncomment next line to set an admin secret
    # HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_GRAPHQL_ADMIN_SECRET}
    # HASURA_GRAPHQL_UNAUTHORIZED_ROLE: public
  volumes:
    - './hasura/metadata:/hasura-metadata'
  networks:
    - sail
  depends_on:
    - pgsql

You can view the final version of the file docker-compose.yml

We can test our services work by running:

$ sail up

You should be able to see your services running in the browser.

The Laravel container can be accessed at http://localhost.

Laravel container is running

The Hasura container can be accessed at http://localhost:8080.

Hasura container is running

Creating Migrations and Models in Laravel

To test out Hasura, we'll create a few models and migrations in Laravel.

artisan make:model Post --factory --migration
artisan make:model Comment --factory --migration

Now we will edit our database schema.

// database/migrations/2022_03_09_022144_create_posts_table.php

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->string('body');
        $table->boolean('is_active')->default(true);
        $table->boolean('is_published')->default(false);
        $table->timestamps();
    });
}
// database/migrations/2022_03_09_022150_create_comments_table.php

public function up()
{
    Schema::create('comments', function (Blueprint $table) {
        $table->id();

        $table->foreignid('post_id')
            ->constrained()
            ->onDelete('cascade');

        $table->text('body');

        $table->timestamps();
    });
}

We'll add the relationships to the models.

// app/Models/Post.php

class Post extends Model
{
    use HasFactory;

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

We'll wire up a few Database Factories so we can seed data to test.

// database/factories/PostFactory.php

public function definition()
{
    return [
        'title' => $this->faker->sentence(),
        'body' => $this->faker->sentence(),
        'is_active' => true,
        'is_published' => true
    ];
}
// database/factories/CommentFactory.php

public function definition()
{
    return [
        'post_id' => Post::factory(),
        'body' => $this->faker->sentence()
    ];
}

Finally we'll wire up a seeder.

// database/seeders/DatabaseSeeder.php

public function run()
{
    Post::factory()
        ->hasComments(5)
        ->create();
}

Now to seed our database.

$ php artisan migrate:fresh --seed

You should see the following output.

Dropped all tables successfully.

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (16.77ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (5.39ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (5.19ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (7.65ms)
Migrating: 2022_03_09_022144_create_posts_table
Migrated:  2022_03_09_022144_create_posts_table (4.87ms)
Migrating: 2022_03_09_022150_create_comments_table
Migrated:  2022_03_09_022150_create_comments_table (32.87ms)

Database seeding completed successfully.

Now our Laravel database is complete.

Setting up Hasura to track our models and data

Now we'll setup Hasura to start tracking the tables in the database.

In Chrome, go to the following URL http://localhost:8080/console/data/default/schema/public

Hasura Track Tables

You'll notice that Hasura can see our newly created tables and data. Click the track button next to Posts and Comments.

Hasura is able to see the Foreign key relationship between Posts and Comments, make sure we also track those.

Hasura Track Relationships

Writing our First GraphQL Query

In chrome, go to the following URL

http://localhost:8080/console/api/api-explorer.

In the editor we'll write our first GraphQL query.

query MyQuery {
  posts {
    id
    body
    comments {
      id
      body
    }
  }
}

You should be able to see your seed data, such as:

{
  "data": {
    "posts": [
      {
        "id": 1,
        "body": "Occaecati voluptatem in laboriosam eos laudantium.",
        "comments": [
          {
            "id": 1,
            "body": "Debitis sapiente accusantium aut voluptatem tempore."
          },
          {
            "id": 2,
            "body": "Debitis ea illum voluptas quis ex."
          },
          {
            "id": 3,
            "body": "Necessitatibus accusantium possimus neque animi."
          },
          {
            "id": 4,
            "body": "Aperiam numquam libero quis atque impedit et."
          },
          {
            "id": 5,
            "body": "Ut hic sint maiores ut."
          }
        ]
      }
    ]
  }
}
Hasura GraphQL Query

Like Magic! You have an instant GraphQL endpoint over your data.

Hasura Query Syntax

Hasura provides a very SQL like GraphQL syntax that Laravel developers will love!

Out of the box, you can query for the Primary Id like:

query MyQuery {
  posts_by_pk(id: 1) {
    id
    body
    comments {
      id
      body
    }
  }
}

Or if you wanted to search for all active and published posts, you could query like this.

query MyQuery {
  posts(
    where: {
      _and: [{ is_active: { _eq: true } }, { is_published: { _eq: true } }]
    }
  ) {
    id
    body
    comments {
      id
      body
    }
  }
}

Or if you wanted to query with some aggregate functions like count, or min, or max

query MyQuery {
  posts_aggregate {
    aggregate {
      count
    }
  }
}

Or searching for Posts that might contain specifc content.

query MyQuery {
  posts(where: { title: { _like: "%laravel%" } }) {
    id
    body
    comments {
      id
      body
    }
  }
}

I hope you enjoyed this quick intro to Laravel and Hasura, and you enjoy using it as much as I do!

Source Code

You can view the source code for this tutorial on Github here.

Troubleshooting

If you encounter problems here, make sure your database variables are all correct between containers and that you are using the default postgres container called pgsql in your docker-compose.yml.

  • ${DB_USERNAME}
  • ${DB_PASSWORD}
  • ${FORWARD_DB_PORT:-5432}
  • ${DB_DATABASE}

Some times it gets a bit tricky if you change the DB password in your .env after you've created the docker database volume.

When in doubt, you can drop all containers an volumes and start again with

$ docker-compose down --volume

Until next time!