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.
The Hasura container can be accessed at http://localhost:8080.
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
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.
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."
}
]
}
]
}
}
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!