Laravel GraphQL

repository·master·Indexed 25 days ago

https://github.com/rebing/graphql-laravel

A code-first integration for building GraphQL APIs in Laravel based on webonyx/graphql-php. It allows developers to define schemas using PHP classes instead of SDL files, supporting multiple schemas, HTTP and execution middleware, and N+1 prevention via Dataloaders or the optional SelectFields package. Compatible with PHP ^8.2 and Laravel 12.x - 13.x.

Tokens
22.7K
Snippets
50
Records
88
Agent score
80%

What's inside rebing/graphql-laravel

  1. Overview of Laravel GraphQL features

    master

    Laravel GraphQL is a code-first integration for Laravel based on the webonyx/graphql-php implementation. Instead of using .graphql files, you define your schema entirely in PHP classes (types, queries, and mutations).

    Key Features:

    • Code-First Schema: Define types, queries, and mutations using PHP classes.
    • Multiple Schemas: Support for multiple schemas, each with its own queries, mutations, types, HTTP middlewares, and GraphQL execution middlewares.
    • Middleware Support: Includes HTTP middleware, GraphQL execution middleware, and custom GraphQL resolver middleware for specific queries/mutations.
    • N+1 Prevention: Offers two strategies:
      • Dataloaders: Uses deferred resolution to batch field loads.
      • SelectFields (Optional): An external package that optimizes Eloquent select() and with() calls based on the GraphQL query.
    • Privacy: Queries return types that can have custom privacy settings.

    Note: This package does not support GraphQL subscriptions. For real-time push functionality, use Laravel broadcasting/WebSockets or a solution like Lighthouse.

  2. What is Resolver Middleware and how does it differ from HTTP Middleware?

    master
    Resolver middleware is specific to GraphQL and operates at the query or mutation level, rather than the schema or route level. Unlike Laravel's HTTP middleware, which applies to entire routes, resolver middleware can be applied uniquely to every individual query or mutation. It is not compatible with standard Laravel HTTP middleware and accepts different arguments (including ResolveInfo).
  3. How to prevent N+1 queries with Dataloaders

    master

    To avoid the N+1 query problem, this library supports Dataloaders (deferred resolution).

    Instead of fetching data immediately in a resolver, a Dataloader collects all requested keys during the execution phase. Once the execution reaches a point where it can no longer defer, the Dataloader fires a single batch request to the data source (Eloquent, API, Cache, etc.) using all collected keys. This is the recommended approach for most applications as it works with any data source.

  4. Define and configure GraphQL Schemas

    master

    Schemas define your GraphQL endpoints. You can define multiple schemas in config/graphql.php, each with its own queries, mutations, types, and middleware.

    Key schema configuration options include:

    • query: Array of query classes.
    • mutation: Array of mutation classes.
    • types: Array of type classes.
    • middleware: HTTP middleware applied to the schema.
    • execution_middleware: Middleware applied during GraphQL execution.
    • method: HTTP methods to support (e.g., ['GET', 'POST']). Must be UPPERCASE. Defaults to POST only.
    • route_attributes: Laravel route attributes (e.g., domain, prefix, as, where) applied to the generated route.
    • group_attributes: Route group attributes. Setting guard allows AddAuthUserContextValueMiddleware to populate the GraphQL context ($ctx) from that guard.
    • controller: Override the default controller (supports Class@method or [Class::class, 'method']).
    'schemas' => [
        'user' => [
            'query' => [
                App\GraphQL\Queries\ProfileQuery::class
            ],
            'middleware' => ['auth:api'],
            'method' => ['GET', 'POST'], 
            'execution_middleware' => [
                \Rebing\GraphQL\Support\ExecutionMiddleware\UnusedVariablesMiddleware::class,
            ],
            'route_attributes' => [
                'domain' => 'api.example.com',
            ],
            'group_attributes' => [
                'guard' => 'api',
            ],
        ],
    ],
  5. Understand privacy callback arguments

    master

    When implementing a privacy callback (closure or class), you have access to four parameters:

    • $root: The parent object being resolved (e.g., an Eloquent model instance). This allows for per-row privacy decisions.
    • $args: The arguments declared specifically on the field itself. These are not the root query/mutation arguments.
    • $ctx: The query context value. By default, the AddAuthUserContextValueMiddleware sets this to the authenticated user model (Auth::user()) or null.
    • $info: (Optional) The GraphQL\Type\Definition\ResolveInfo object.
  6. Cross-field validation in nested InputTypes

    master

    When using sibling-referencing rules (like prohibits, required_with, required_if, same, etc.) inside an InputType, the library automatically transforms these references into fully-qualified dot-notation paths. This ensures Laravel's Validator can correctly resolve the sibling field even when the input is nested or part of a list.

    For example, if an InputType has a rule prohibits:mintParams on a field createParams, and that input is used in a list called recipients, the rule is automatically prefixed to recipients.0.mintParams.

    To disable this automatic prefixing, override processCollectedRules() in your Query or Mutation class:

    class MyMutation extends Mutation
    {
        protected function processCollectedRules(array $rules): array
        {
            return $rules; // disable automatic cross-field rule prefixing
        }
    }
  7. Understand the three types of Middleware

    master

    The library supports three layers of middleware to intercept and mutate requests or responses:

    1. HTTP Middleware: Standard Laravel HTTP middleware. Can be applied globally via graphql.route.middleware or per-schema via graphql.schemas.<yourschema>.middleware.
    2. GraphQL Execution Middleware: Intercepts the processing of a GraphQL request. Configured via graphql.execution_middleware (global) or graphql.schemas.<yourschema>.execution_middleware (per-schema).
    3. GraphQL Resolver Middleware: Executes for the specific query or mutation being targeted before the actual resolve() method is called.
  8. Privacy vs Authorization

    master

    It is important to distinguish between these two mechanisms:

    • authorize(): Used on a Query or Mutation. It gates the entire operation. If it fails, the whole request is rejected with an error.
    • privacy: Used on a Type field. It gates individual fields and silently returns null when denied. Use this for field-level visibility.
  9. Handle and format GraphQL errors

    master

    The library distinguishes between two types of errors:

    1. Errors (Rebing\GraphQL\Error\*): Client-safe errors (like ValidationError or AuthorizationError) that appear in the GraphQL JSON response.
    2. Exceptions (Rebing\GraphQL\Exception\*): Developer/configuration errors that are not included in GraphQL responses but are handled by Laravel's exception handler.

    Customizing Error Handling

    You can customize how errors are formatted for the client or how they are handled internally by providing class/method references in config/graphql.php.

    // config/graphql.php
    
    // Custom formatter: returns an array for each GraphQL\Error\Error
    'error_formatter' => [App\GraphQL\ErrorFormatter::class, 'format'],
    
    // Custom handler: receives all errors + the formatter
    'errors_handler' => [App\GraphQL\ErrorHandler::class, 'handle'],
  10. Wrap types to inject extra data

    master

    You can wrap types to add additional information to queries and mutations, similar to how pagination works. This allows you to return a wrapper object containing both the primary data and extra metadata (like messages or status info).

    If you use the rebing/graphql-laravel-select-fields package, your wrapper class must implement the WrapType marker interface.

    public function type(): Type
    {
        return GraphQL::wrapType(
            'PostType',
            'PostMessageType',
            \App\GraphQL\Types\WrapMessagesType::class,
        );
    }
    
    public function resolve($root, array $args)
    {
        return [
            'data' => Post::find($args['post_id']),
            'messages' => new Collection([
                    new SimpleMessage("Congratulations, the post was found"),
                    new SimpleMessage("This post cannot be edited", "warning"),
            ]),
        ];
    }
  11. Compare Dataloaders vs SelectFields for data loading

    master

    When optimizing data fetching, choose between Dataloaders and the optional rebing/graphql-laravel-select-fields package based on your needs:

    FeatureSelectFieldsDataloaders
    Data sourceEloquent onlyAny (Eloquent, APIs, caches, etc.)
    N+1 strategyUpfront eager loading via query AST analysisDeferred batching at resolve time
    Column precisionSelects only requested columnsTypically all columns (customizable per loader)
    Setupcomposer require rebing/graphql-laravel-select-fieldsCreate a loader class, register in the container
    Best forEloquent-heavy apps needing column-level optimizationMost applications; especially mixed data sources

    Note: These approaches are independent and can coexist.

  12. How GraphQL fields and arguments are configured

    master

    In this library, types, queries, and mutations are defined using the $attributes property and methods like args() or fields(). These methods return configuration arrays where:

    • The key is the name of the field or argument.
    • type (required): A GraphQL specifier (e.g., Type::string()) defining the data type.
    • description (optional): A string used for schema introspection.
    • resolve (optional): An override for the default field resolver.
    • deprecationReason (optional): A string explaining why a field is deprecated.