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

Laravel Graphql Utils Laravel Package

laragraph/laravel-graphql-utils

A collection of practical GraphQL utilities for Laravel applications, offering reusable helpers for schemas, types, directives, pagination, and testing so you can build robust APIs with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

laragraph/utils is a thin, framework-native layer that turns an incoming Laravel Illuminate\Http\Request into webonyx/graphql-php OperationParams — handling JSON, application/graphql, GET query strings, batched requests, and multipart/form-data file uploads along the way. It is the piece most Laravel GraphQL stacks (Lighthouse, rebing/graphql-laravel, or a hand-rolled StandardServer controller) either use directly or reimplement badly.

Install and orient yourself in this order:

composer require laragraph/utils
  1. Laragraph\Utils\GraphQLRequestParser (v2; RequestParser in v1) — the entry point. Read this class first; everything else is supporting types.
  2. Laragraph\Utils\GraphQLRequestError — the exception thrown for malformed transport-level input (bad JSON, missing query, invalid batch shape). This is what you catch to return a 400.
  3. OperationParams — the parsed result: query, variables, operation name, extensions, and uploaded files.

There is no service provider or config file to publish; wire it into your single GraphQL controller. A minimal first use case:

use GraphQL\Server\StandardServer;
use Laragraph\Utils\GraphQLRequestParser;
use Laragraph\Utils\GraphQLRequestError;

Route::post('/graphql', function (Request $request) {
    try {
        $params = (new GraphQLRequestParser())->parseHttpRequest($request);
    } catch (GraphQLRequestError $e) {
        return response()->json(['errors' => [['message' => $e->getMessage()]]], 400);
    }

    return (new StandardServer($config))->executePsrRequest(/* … */);
});

Implementation Patterns

Parse once, in one place. Put parsing in a dedicated controller (or a middleware that attaches the parsed params to the request) rather than letting each resolver or service touch the raw body. Every consumer downstream should receive OperationParams, never a decoded array.

Batch handling. A GraphQL HTTP body is either a single object or an array of objects. The parser normalizes both into an iterable of OperationParams; iterate, execute each, and preserve the original indexes so the response array lines up with the request array:

$results = [];
foreach ($parser->parseHttpRequest($request) as $index => $params) {
    $results[$index] = $executor->execute($params);
}
return response()->json(count($results) === 1 && ! $batched ? $results[0] : $results);

Note that whether a body is "a batch" is decided by webonyx's own heuristics (presence of a 0 key), not by whether the JSON was an array — see the gotchas below.

Uploads / multipart. For multipart/form-data, the parser resolves the operations + map convention and populates variables with UploadedFile instances. Validate them with normal Laravel rules (mimes:, max:) inside your mutation arguments, or in a FormRequest that reads from the parsed variables rather than $request->file().

Error mapping. Keep two layers distinct: GraphQLRequestError → HTTP 400 with an errors array; GraphQL\Error\Error from execution → HTTP 200 with errors (or your own policy). Mixing them produces 500s on bad client input or 200s on garbage payloads.

Integration with existing stacks. If you use Lighthouse or Rebing, check whether it already uses this package's parser before writing your own; if you need custom coercion, bind a RequestParser implementation in the container instead of forking the controller.

Testing. $this->postJson('/graphql', [...]), $this->call('POST', '/graphql', [], [], ['file' => UploadedFile::fake()->image('a.png')]), and explicit malformed-body cases asserting a 400 JSON envelope.

Gotchas and Tips

  • Batch detection is a footgun, and v2.3.2 tightened it. webonyx treats any JSON object that contains a 0 key as a batch. A body like {"query":"…","0":"x"} — plausible from a sloppy client or a stringified array — was previously passed through and blew up with a TypeError (a 500, and a noisy log entry). v2.3.2 rejects these payloads up front with a GraphQLRequestError, i.e. a clean 400. If you have a broad catch (\Throwable) around your GraphQL endpoint, tighten it so GraphQLRequestError is handled explicitly; otherwise you lose the benefit of the fix.
  • Never call OperationParams::create() directly on a decoded body in your own code. You'll bypass exactly the guard this release adds. Always go through the parser.
  • Content-Type drives parsing. application/json, application/graphql, and multipart/form-data are handled; a missing or wrong header falls back to GET-style query parsing and can silently yield an empty query instead of an error.
  • {} vs [] in variables. With json_decode($raw, true), an empty JSON object and an empty array are indistinguishable, and webonyx expects variables to be a map. Preserve the distinction (decode to objects, or use JSON_OBJECT_AS_ARRAY deliberately) — this is the same class of shape confusion that produced the batch bug.
  • Error text is transport-level. GraphQLRequestError messages ("invalid request", "failed to parse JSON") are safe-ish but not user-friendly; log the exception with the raw body and content-type, and return a generic message to clients.
  • Debugging: when a request 400s unexpectedly, log $request->getContent() before parsing and dump the decoded structure. The parser's messages rarely tell you which key was malformed.
  • Extension point: the RequestParser contract is the intended seam for custom behaviour (e.g. signature verification, alternative content types). Implement it and delegate to the package for the actual decoding so you keep the validation fixes.
  • Upgrade risk for 2.3.2: patch release, no deprecations or API changes. Only observable difference is that previously-500ing payloads now return 400. If your test suite asserts a 500 (or asserts an exact error message) for {"query":"…","0":"…"}-style bodies, update those assertions.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager