Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Hasura Bundle Laravel Package

hasura-extra/hasura-bundle

A Symfony bundle for integrating the Hasura GraphQL engine into PHP applications. It provides configuration, service wiring, and convenience tooling so you can connect to Hasura, manage settings, and query its APIs more easily. Simplify setup and streamline GraphQL workflows from your Symfony projec...

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you composer require anything: this is a Symfony bundle, not a Laravel package. There is no bundle auto-registration, no config/packages/*.yaml, and no autowiring of HasuraClient into your controllers. The bundle is also archived (last release October 2021, zero dependents), so treat it as a reference implementation rather than a dependency you adopt.

What you actually want from hasura-extra/* in a Laravel app is the framework-agnostic layer underneath it:

  • hasura-extra/hasura-client — PSR-18/PSR-17 client with a fluent query builder and typed hydration for the GraphQL endpoint.
  • hasura-extra/hasura-event-dispatcher — maps Hasura event-trigger payloads onto PSR-14 events.
  • hasura-extra/hasura-bundle — only the Symfony glue: DI wiring, config tree, action/event controller helpers.

Where to look first: the bundle's DependencyInjection/Configuration.php (it is the only authoritative list of config keys), then the client package README for the query API. The bundle's Tests/ fixtures are the fastest way to see the intended end-to-end shape.

First use case in a Laravel app — a thin client bound in the container:

// config/services.php
'hasura' => [
    'url'          => env('HASURA_URL', 'http://hasura:8080/v1/graphql'),
    'admin_secret' => env('HASURA_ADMIN_SECRET'),
],
// app/Services/HasuraClient.php
final class HasuraClient
{
    public function __construct(private array $config) {}

    public function query(string $gql, array $vars = [], ?string $role = null): array
    {
        $res = Http::withHeaders(array_filter([
                'X-Hasura-Admin-Secret' => $this->config['admin_secret'],
                'X-Hasura-Role'         => $role,
            ]))
            ->timeout(10)
            ->post($this->config['url'], ['query' => $gql, 'variables' => $vars]);

        $body = $res->json() ?? [];

        if (! empty($body['errors'])) {
            throw new HasuraException($body['errors']);
        }

        return $body['data'] ?? [];
    }
}
// AppServiceProvider::register()
$this->app->singleton(HasuraClient::class,
    fn ($app) => new HasuraClient($app['config']['services.hasura']));

That is roughly what the bundle gives you in Symfony, minus the ceremony. If you are deploying the Symfony side, the install is composer require hasura-extra/hasura-bundle, register HasuraExtra\HasuraBundle\HasuraBundle::class => ['all' => true] in config/bundles.php, and configure hasura.endpoint / hasura.admin_secret from env — confirm the exact key names against Configuration.php for the pinned version, they changed between 1.x and 2.x.

Implementation Patterns

Raw query + variables, never interpolation. Hasura is a single POST endpoint; the shape is always {"query": ..., "variables": {...}}. Build a small set of named DTO-ish methods on your client rather than scattering GraphQL strings through controllers, and hydrate results into arrays or spatie/laravel-data objects yourself — the client does not know your schema.

Action handlers (Hasura → your app). An Action is a webhook: Hasura POSTs {"action": {"name": ...}, "input": {...}, "session_variables": {...}} and expects a JSON body matching your action's output type. In Laravel this is a plain route:

Route::post('/hasura/actions/create-invoice', CreateInvoiceAction::class)
    ->middleware('hasura.action');

Guard it with a shared secret — Hasura cannot sign requests, so you configure a header in the action metadata and compare in middleware:

$given = $request->header('X-Action-Secret', '');
abort_unless(hash_equals(config('services.hasura.action_secret'), $given), 401);

Then read $request->input('input') as your typed argument object, and return the output shape literally (['id' => ..., 'status' => 'created']). The bundle's equivalent is an annotated controller plus its own auth listener; the semantics are identical.

Event triggers. Payload is {"event": {"op", "data": {"new", "old"}}, "table": {...}, "delivery_info": {"id", "current_retry", "max_retries"}}. The pattern that survives contact with production: return 200 immediately, push a job, process asynchronously.

public function __invoke(Request $request)
{
    $p = $request->validate([
        'event.op' => 'required|string',
        'event.data.new' => 'required|array',
        'delivery_info.id' => 'required|string',
    ]);

    ProcessOrder::dispatch($p['event']['data']['new'], $p['delivery_info']['id']);

    return response()->json(['ok' => true]);
}

Use delivery_info.id as your idempotency key (Cache::add("hasura:evt:{$id}", true, now()->addDay())). Hasura retries non-2xx with backoff, so a non-idempotent handler will double-charge someone eventually.

Role switching. With the admin secret you bypass every permission rule. For anything user-facing, drop the admin secret and send X-Hasura-Role plus X-Hasura-User-Id instead, or use admin_secret only for system jobs and pass an explicit role for the rest. Mirror this with a Hasura::asRole('user', $id) scope on your client so it is impossible to forget.

Testing. Http::fake(['*/v1/graphql' => Http::response(['data' => ['orders' => []]])]), then Http::assertSent(fn ($r) => $r['query'] === ...) — assert variables and headers, not the query string. Same for action endpoints: $this->postJson('/hasura/actions/create-invoice', [...])->assertExactJson([...]).

Config caching. Both Symfony's cache:clear and Laravel's config:cache will freeze your Hasura URL. Wire HASURA_* env vars into config/services.php only, never env() outside config files.

Gotchas and Tips

  • GraphQL errors return HTTP 200. A PSR-18 client will happily hand you {"errors": [...]}. Every call site must inspect errors — put that in one place or you will silently get null data for weeks.
  • Archived means frozen. Pin an exact version and expect Composer conflicts against modern psr/http-client ^1.1 / symfony/* v6+; run composer why-not hasura-extra/hasura-bundle <version> before committing to it. No security patches are coming. MIT license means fork-and-vendor is legitimate if you really want the query builder.
  • Zero dependents is a signal. There is no community to answer "why does this throw on nullable fields." Budget a day for reading source, not a week of hope.
  • Never log the admin secret. Hasura echoes session variables and headers into its own logs; if you also dump Http::withHeaders(...) payloads in a debug middleware, redact X-Hasura-Admin-Secret explicitly.
  • Action timeouts are brutal. Hasura gives actions on the order of tens of seconds before it returns an error to the caller — keep handlers under a second and move anything heavy to a queue.
  • N+1 on the wire. Unlike Eloquent, there is nothing batching your calls. If a controller does three HasuraClient::query() calls, that is three HTTP round trips; compose them into one GraphQL document with aliases or accept the latency knowingly.
  • Event handler must be reachable. Hasura runs in a container and resolves your webhook URL from its network namespace. localhost:8000 means the Hasura container, not your laptop — use the service name or host.docker.internal.
  • Debugging playbook: run the exact query in the Hasura console first (it shows the resolved role and permissions), diff your variables, then check the x-hasura-* headers your app actually sent. Nine times out of ten the bug is a role header, not the GraphQL.
  • Extension points, in order of practicality: (1) a decorator around your HasuraClient for tracing/logging, (2) swapping the PSR-18 implementation (Guzzle vs Symfony HttpClient) if you adopt the client package, (3) writing your own action/event middleware — the bundle's are small and easier to reimplement in Laravel than to port.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource