ayimdomnic/graph-ql-l5.3

Facebook GraphQl for Laravel developers
21 3
Install
composer require ayimdomnic/graph-ql-l5.3
Latest Version:v3.1.1
PHP:^8.2 | ^8.3 | ^8.4 | ^8.5
License:MIT
Last Updated:Aug 6, 2026
Links: GitHub  ·  Packagist
Maintainer: ayimdomnic

Laragraph

A modern, feature-rich GraphQL package for Laravel.

Latest Version PHP Version Laravel Version License

Laragraph gives Laravel developers a clean, expressive, code-first API for building GraphQL services — powered by webonyx/graphql-php.


Features

Capability Status
Queries & Mutations
Real-time Subscriptions (Laravel Broadcasting)
Object / Input / Enum / Interface / Union types
Custom scalars (DateTime, Date, JSON, Upload)
Built-in argument validation (Laravel rules)
Per-field authorization
N+1-safe Eloquent relation batching
Relay cursor pagination + simple paginator
Batched queries
File uploads (multipart spec)
Multiple named schemas
Query complexity & depth limiting
Introspection toggle
Per-field tracing (Apollo Tracing format)
GraphiQL browser IDE
Artisan generators
Auto-discovery (no manual registration)
Static analysis (PHPStan / Larastan)

Requirements

  • PHP 8.2+
  • Laravel 10 / 11 / 12

Installation

composer require ayimdomnic/laragraph

Laravel auto-discovers the package. Publish the config:

php artisan vendor:publish --tag=laragraph-config

Quick Start

1. Create a Type

php artisan laragraph:make:type UserType
// app/GraphQL/Types/UserType.php
use Ayimdomnic\Laragraph\Support\Type;
use GraphQL\Type\Definition\Type as GType;

class UserType extends Type
{
    protected array $attributes = [
        'name'        => 'User',
        'description' => 'A registered user.',
    ];

    public function fields(): array
    {
        return [
            'id'    => ['type' => GType::nonNull(GType::id())],
            'name'  => ['type' => GType::string()],
            'email' => ['type' => GType::string()],
        ];
    }
}

2. Create a Query

php artisan laragraph:make:query UsersQuery
// app/GraphQL/Queries/UsersQuery.php
use Ayimdomnic\Laragraph\Support\Query;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;

class UsersQuery extends Query
{
    public function type(): Type
    {
        return Type::listOf(app('laragraph')->type('User'));
    }

    public function args(): array
    {
        return [
            'limit' => ['type' => Type::int(), 'defaultValue' => 10],
        ];
    }

    public function resolve(mixed $root, array $args, mixed $context, ResolveInfo $info): mixed
    {
        return \App\Models\User::limit($args['limit'])->get();
    }
}

3. Create a Mutation

php artisan laragraph:make:mutation CreateUserMutation
// app/GraphQL/Mutations/CreateUserMutation.php
use Ayimdomnic\Laragraph\Support\Mutation;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;

class CreateUserMutation extends Mutation
{
    public function type(): Type
    {
        return app('laragraph')->type('User');
    }

    public function args(): array
    {
        return [
            'name'  => ['type' => Type::nonNull(Type::string())],
            'email' => ['type' => Type::nonNull(Type::string())],
        ];
    }

    public function rules(array $args = []): array
    {
        return [
            'name'  => ['required', 'string', 'max:255'],
            'email' => ['required', 'email', 'unique:users,email'],
        ];
    }

    public function resolve(mixed $root, array $args, mixed $context, ResolveInfo $info): mixed
    {
        return \App\Models\User::create($args);
    }
}

4. Register in config/laragraph.php

'types' => [
    'User' => \App\GraphQL\Types\UserType::class,
],

'schemas' => [
    'default' => [
        'query'    => ['users' => \App\GraphQL\Queries\UsersQuery::class],
        'mutation' => ['createUser' => \App\GraphQL\Mutations\CreateUserMutation::class],
    ],
],

5. Make requests

POST /graphql
Content-Type: application/json

{ "query": "{ users(limit: 5) { id name email } }" }

GraphiQL

Built-in browser IDE at /graphql/graphiql (enabled by default).

'graphiql' => ['enabled' => false], // disable

Artisan Generators

Command Creates
laragraph:make:type UserType app/GraphQL/Types/UserType.php
laragraph:make:query UsersQuery app/GraphQL/Queries/UsersQuery.php
laragraph:make:mutation CreateUserMutation app/GraphQL/Mutations/CreateUserMutation.php
laragraph:make:subscription UserCreatedSubscription app/GraphQL/Subscriptions/UserCreatedSubscription.php
laragraph:make:input CreateUserInput app/GraphQL/Inputs/CreateUserInput.php

Pagination

Relay Cursor Pagination

use Ayimdomnic\Laragraph\Pagination\ConnectionType;

class UsersQuery extends Query
{
    public function type(): Type
    {
        return new ConnectionType('UserConnection', app('laragraph')->type('User'));
    }

    public function args(): array
    {
        return ConnectionType::args(); // first, after, last, before
    }

    public function resolve(mixed $root, array $args, mixed $context, ResolveInfo $info): mixed
    {
        return ConnectionType::paginate(\App\Models\User::query(), $args);
    }
}
{
  users(first: 10) {
    edges { cursor node { id name } }
    pageInfo { hasNextPage endCursor total }
  }
}

Simple Offset Pagination

return ConnectionType::simplePaginate(\App\Models\User::query(), $args);
// → { data, total, per_page, current_page, last_page }

N+1-Safe Eloquent Relations

Every GraphQL request gets a fresh DataLoaderRegistry attached to $context. For hand-written batch loaders, extend BatchResolver:

use Ayimdomnic\Laragraph\DataLoader\BatchResolver;

class UserLoader extends BatchResolver
{
    public function batch(array $keys): array
    {
        return User::whereIn('id', $keys)->get()->keyBy('id')->toArray();
    }
}

// In a resolver:
return $context->dataLoaders->get(UserLoader::class)->load($root->user_id);

For a plain Eloquent relation, skip the hand-written loader entirely — Type::batchRelation() batches it through the relation's own eager-loading machinery (the same code path Model::with() uses), so it works for belongsTo, hasOne, hasMany, belongsToMany, and morph relations alike:

class PostType extends Type
{
    public function fields(): array
    {
        return [
            'id'       => GType::nonNull(GType::id()),
            'comments' => GType::listOf(app('laragraph')->type('Comment')),
        ];
    }

    protected function resolveCommentsField(mixed $root, array $args, mixed $context): mixed
    {
        return $this->batchRelation(Post::class, 'comments', $root, $context);
    }
}

Regardless of how many Post parents are in the result set, comments resolves in a fixed, small number of queries per request instead of one query per post.


Authorization

public function authorize(mixed $root, array $args, mixed $context, ResolveInfo $info): bool
{
    return $context->user()?->isAdmin() ?? false;
}

falseAuthorizationExceptionextensions.category = 'authorization'.


Validation

public function rules(array $args = []): array
{
    return ['email' => ['required', 'email']];
}

Errors appear in extensions.validation:

{
  "errors": [{
    "message": "Validation failed.",
    "extensions": {
      "category": "validation",
      "validation": { "email": ["The email field is required."] }
    }
  }]
}

Built-in Scalars

'types' => [
    'DateTime' => \Ayimdomnic\Laragraph\Scalars\DateTimeType::class,
    'Date'     => \Ayimdomnic\Laragraph\Scalars\DateType::class,
    'JSON'     => \Ayimdomnic\Laragraph\Scalars\JsonType::class,
    'Upload'   => \Ayimdomnic\Laragraph\Scalars\UploadType::class,
],

Multiple Schemas

'schemas' => [
    'default' => ['query' => [...], 'mutation' => [...]],
    'admin'   => ['query' => [...], 'mutation' => [...], 'middleware' => ['auth:api', 'admin']],
],

Endpoints: POST /graphql and POST /graphql/admin.


Security

'security' => [
    'query_max_complexity'  => 200,
    'query_max_depth'       => 10,
    'disable_introspection' => true, // recommended in production
],

Batched Queries

[
  { "query": "{ users { id } }" },
  { "query": "mutation { createUser(name: \"Alice\", email: \"a@b.com\") { id } }" }
]

File Uploads

Follows the GraphQL multipart request spec.

'types' => ['Upload' => \Ayimdomnic\Laragraph\Scalars\UploadType::class],

// In mutation args:
'avatar' => ['type' => app('laragraph')->type('Upload')]

// In resolver — $args['avatar'] is \Illuminate\Http\UploadedFile
$path = $args['avatar']->store('avatars', 'public');

Facade

use Ayimdomnic\Laragraph\Facades\Laragraph;

$result = Laragraph::execute('{ users { id name } }');
$schema = Laragraph::schema('admin');
$type   = Laragraph::type('User');

Subscriptions

webonyx/graphql-php has no subscription transport of its own, so Laragraph provides one on top of Laravel Broadcasting: the initial subscription request registers a subscriber and returns a channel; your app code later calls Laragraph::broadcast() to push a live update to every subscriber on that channel.

'subscriptions' => ['enabled' => true],
// app/GraphQL/Subscriptions/UserCreatedSubscription.php
use Ayimdomnic\Laragraph\Support\Subscription;

class UserCreatedSubscription extends Subscription
{
    public function type(): Type
    {
        return app('laragraph')->type('User');
    }

    public function subscribe(mixed $root, array $args, mixed $context, ResolveInfo $info): mixed
    {
        return 'users'; // the channel clients subscribe to
    }

    public function resolve(mixed $root, array $args, mixed $context, ResolveInfo $info): mixed
    {
        return $root; // $root is the payload passed to Laragraph::broadcast()
    }
}

Trigger an update from anywhere — typically at the end of a mutation:

class CreateUserMutation extends Mutation
{
    public function resolve(mixed $root, array $args, mixed $context, ResolveInfo $info): mixed
    {
        $user = \App\Models\User::create($args);

        Laragraph::broadcast('users', $user);

        return $user;
    }
}

Client flow:

  1. POST the subscription operation like any other query:
    { "query": "subscription { userCreated { id name } }" }
    
    The response carries no data yet — instead:
    { "data": { "userCreated": null }, "extensions": { "subscription": { "channel": "users", "subscriberId": "…" } } }
    
  2. Listen for updates on that subscriber's private channel with Laravel Echo:
    Echo.private(`graphql-subscriber.${subscriberId}`)
        .listen('.GraphQLSubscriptionUpdate', (payload) => {
            console.log(payload.data.userCreated);
        });
    

Delivery uses whichever broadcast driver your app has configured (Reverb, Pusher, …) — Laragraph only decides the channel and payload shape. Set 'subscriptions' => ['driver' => 'log'] to write updates to the log instead, useful for local development without a broadcast server.


Tracing

Enable per-field resolver timing in the Apollo Tracing format, understood out of the box by existing GraphQL tooling:

'tracing' => ['enabled' => true],
{
  "extensions": {
    "tracing": {
      "version": 1,
      "startTime": "2026-08-06T12:00:00.000Z",
      "endTime": "2026-08-06T12:00:00.004Z",
      "duration": 4200000,
      "execution": {
        "resolvers": [
          { "path": ["users", 0, "posts"], "parentType": "User", "fieldName": "posts", "returnType": "[Post]", "startOffset": 120000, "duration": 80000 }
        ]
      }
    }
  }
}

Every resolved field is recorded — root Query/Mutation/Subscription fields and nested Type fields alike. Leave this off in production unless you're actively debugging performance; it adds a small wrapping cost to every resolver call.


Static Analysis

Larastan/PHPStan ships configured out of the box:

composer phpstan

Testing

composer test

Contributing

Contributions, issues, and feature requests are welcome!


License

MIT © Odhiambo Dormnic

Related Packages

php-tmdb/laravel

Laravel Package for TMDB ( The Movie Database ) API. Provides easy access to the...

53,306 162
aimeos/aimeos-laravel

Cloud native, API first Laravel eCommerce package with integrated AI for ultra-f...

230,845 8,673
atehnix/laravel-vk-requester

Laravel package for Vk.com API

3,446 74
mesingh/amazon-mws-laravel

Use Amazon's MWS web services with Laravel 5.x. Based on przemekperon/amazon-mws...

1,623 2