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

Graphql Laravel Laravel Package

rebing/graphql-laravel

Code-first GraphQL integration for Laravel: define schemas, queries, and mutations in PHP classes instead of .graphql files. Supports multiple schemas, resolver middleware, privacy controls, and dataloaders to avoid n+1 queries.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish.

composer require rebing/graphql-laravel
php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"
composer require mll-lab/laravel-graphiql --dev   # optional IDE at /graphiql

2. Flip introspection on for local work. It is disabled by default, so GraphiQL and every IDE/codegen tool will come back with an empty schema until you set GRAPHQL_DISABLE_INTROSPECTION=false in .env.

3. Look at exactly one file first: config/graphql.php. The schemas array is the whole mental model — each schema lists its query, mutation and types classes. Everything else (routes, middleware, security, tracing) hangs off that.

4. Scaffold instead of typing boilerplate. There are 12 generators; run php artisan list make:graphql to see all of them.

php artisan make:graphql:type BookType
php artisan make:graphql:query BooksQuery

5. First working endpoint — a Type, a Query, and a config entry:

// config/graphql.php → schemas.default
'query' => [App\GraphQL\Queries\BooksQuery::class],
'types' => [App\GraphQL\Types\BookType::class],
curl -X POST -H "Content-Type: application/json" \
  -d '{"query":"{ books(title: \"1984\") { id title author } }"}' \
  http://localhost:8000/graphql

6. Write your first test before your second resolver. The package ships test helpers on top of Orchestra Testbench:

$response = $this->httpGraphql('{ books { id title } }');
$response->assertJsonPath('data.books.0.title', 'The Great Gatsby');

That loop — scaffold, register, test — is the day-to-day cadence. The "What's next" chain in the README (Dataloaders → Eloquent queries → mutations → validation → authorization) is a sensible reading order for week one.

Implementation Patterns

Schema definition: config vs. class. Small apps list classes in config/graphql.php. Once a schema exceeds ~10 entries, move to a class implementing ConfigConvertible and reference it by name in config — it keeps query/mutation/types/middleware co-located and makes per-schema middleware obvious in code review.

Reference types through the facade, never new.

use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;

public function type(): Type
{
    return Type::nonNull(Type::listOf(Type::nonNull(GraphQL::type('Book'))));
}

The registry also parses modifier strings — GraphQL::type('[Book!]!') is equivalent to the above and reads better in dense field maps.

Resolver resolution has two routes. Either implement resolve($root, array $args) on the query/mutation, or omit it and let the Type auto-discover resolve{StudlyFieldName}Field() methods on the Type class. The latter is the pattern for relationship-ish fields:

class AuthorType extends GraphQLType
{
    protected $attributes = ['name' => 'Author'];

    public function fields(): array
    {
        return [
            'id'    => ['type' => Type::nonNull(Type::int())],
            'posts' => ['type' => Type::listOf(GraphQL::type('Post'))],
        ];
    }

    public function resolvePostsField($author, array $args): array
    {
        return $author->posts()->get()->all();
    }
}

Validation belongs in rules(), not in the resolver. Per-argument 'rules' => ['required', 'email'] works for simple cases; override rules() on the query/mutation for anything cross-field. Validation failures are wrapped in the package's ValidationError, so the response shape already matches what Laravel API clients expect — don't hand-roll throw new ValidationException.

Authorization belongs in authorize(). Returning false produces an AuthorizationError in the GraphQL error list rather than a 403 that kills the whole request — which is what you want, since sibling fields should still resolve. Use Privacy classes when the rule is field-level ("only the owner sees email") rather than operation-level.

Mutations: input type + rules + return type. The idiom that scales is one InputType per mutation, rules() on the mutation keyed to the input shape, and returning the mutated entity's Type so clients can request exactly what changed in one round trip.

Middleware is three layers, pick deliberately:

Layer Base class Use for
HTTP Laravel middleware, per schema CORS, throttling, auth guards
Execution AbstractExecutionMiddleware APQ, unused-variable checks, context injection, global timing
Resolver Middleware ($middleware prop or resolver_middleware_append) Per-field auth, caching, audit logging

Resolver middleware is where most business cross-cutting lives — it sees $root, $args, $context and ResolveInfo, so it can key a cache on the field name plus arguments.

Data loading: reach for dataloaders early.

// Loader batched per request; resolves N authors in one query
$loader = GraphQL::loader(...);  // register in the type, load by id in the field resolver

Dataloaders use webonyx's deferred resolution and batch by tick — ideal for any N+1-prone relationship. The Eloquent-specific select()/with() optimization now lives in the separate rebing/graphql-laravel-select-fields package; only pull that in when you've measured a real cost.

Multiple schemas for API versioning and audience separation. A public schema and an admin schema with different HTTP middleware (different auth guard) is cheap to set up and far cleaner than if (user()->isAdmin()) inside resolvers. Each schema gets its own queries/mutations/types and its own middleware stack.

File uploads are handled by the UploadType scalar with a multipart request — the README has both Vue and vanilla JS examples.

Batching is supported for clients that send an array of operations; keep the batch limit configured so one client can't starve the pool.

Gotchas and Tips

  • Introspection is off by default. This is the #1 first-hour stumble. Enable locally, keep disabled in production (the README's recommended production config covers depth limiting, complexity analysis, batching caps and GET read-only enforcement — apply it proactively, not after an incident).
  • Config and route caching will bite you. New types/queries not appearing after deploy usually means php artisan config:clear was never run. Schema classes registered by FQCN are fine, but the config array is cached.
  • Don't instantiate Types directly. new BookType() in a field map creates a second instance, and webonyx will reject duplicate named types or throw confusing "type already exists" errors. Always go through GraphQL::type().
  • nonNull failures bubble all the way up. A nonNull field that resolves to null doesn't just null that field — it nulls the nearest nullable ancestor, and with a nonNull top-level list you lose the entire data key. When debugging "whole response is null with one error", look at the path in the error.
  • Two error layers, and they are not interchangeable. src/Error/* (ValidationError, AuthorizationError, APQ errors) are client-safe and surface in errors[]. src/Exception/* (SchemaNotFound, TypeNotFound) are developer/config mistakes and are not client-safe. Throwing a raw RuntimeException from a resolver is the fastest way to leak internals or get an opaque 500 — throw a package Error or override error formatting.
  • N+1 creeps in through Eloquent accessors and $model->relation. The default field resolver is fine, but a type field that touches $book->author->name lazily is one query per row. Audit with DB::listen or the package's SQL-count assertion helpers before shipping a list query.
  • Errors are sanitized by default (trace/file/line stripped in HTTP responses, and the test helper mirrors that). If you need details in staging, change error formatting rather than dumping debug output into a message.
  • resolve() supports container injection. ResolveInfo and any container-resolvable class can be type-hinted — useful for pulling per-request services into resolvers without app() calls. Third-party packages can add their own injectors via Field::registerParameterInjector().
  • Subscriptions are not supported. If real-time is on the roadmap, evaluate Lighthouse before committing, or plan a separate broadcasting/WebSocket layer. Do not assume this package will grow into it.
  • Reusable fields via macros. The GraphQL facade is Macroable; a pagination or timestamps macro applied to a Field map beats copy-pasting argument blocks — and it keeps a single point of change when arguments evolve.
  • Deprecate before deleting. Field deprecation is supported in the field definition; use it so clients get a @deprecated warning from introspection instead of a sudden breaking change.
  • Testing non-default schemas requires passing schemaName to httpGraphql(). Forgetting it silently hits default and produces a "field not found" error that looks like a schema bug.
  • Trace before you guess. The OpenTelemetry driver (optional open-telemetry/api) gives per-field resolver spans; for a slow query, enabling a tracing driver answers "which resolver" in one request instead of five dd() cycles.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin