# Laravel GraphQL Laravel GraphQL is a comprehensive GraphQL integration package for Laravel applications, built on top of the webonyx/graphql-php library. It provides a structured, Laravel-idiomatic way to create GraphQL APIs with support for queries, mutations, types, schemas, validation, authorization, and advanced features like pagination, batching, and automatic persisted queries. The package bridges the gap between GraphQL's powerful query language and Laravel's elegant framework, making it easy to build performant and type-safe APIs. This library extends beyond basic GraphQL functionality by offering Laravel-specific features such as Eloquent integration via SelectFields for optimized database queries, middleware support at multiple levels (HTTP, execution, and resolver), privacy controls on fields, and built-in validation using Laravel's validation system. It supports multiple schemas with different configurations, allowing developers to create separate GraphQL endpoints for different purposes (e.g., public API vs authenticated user endpoints) while sharing common types and logic. ## Installation and Configuration Installing the package via Composer and publishing configuration ```bash # Install the package composer require rebing/graphql-laravel # Publish the configuration file php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider" ``` ```php // config/graphql.php - Basic schema configuration return [ 'route' => [ 'prefix' => 'graphql', 'middleware' => [], ], 'default_schema' => 'default', 'schemas' => [ 'default' => [ 'query' => [ App\GraphQL\Queries\UsersQuery::class, ], 'mutation' => [ App\GraphQL\Mutations\UpdateUserPasswordMutation::class, ], 'types' => [ App\GraphQL\Types\UserType::class, ], 'middleware' => null, 'method' => ['GET', 'POST'], ], 'user' => [ 'query' => [ App\GraphQL\Queries\ProfileQuery::class, ], 'middleware' => ['auth'], 'route_attributes' => [ 'domain' => 'api.example.com', ], ], ], 'types' => [], 'security' => [ 'query_max_complexity' => null, 'query_max_depth' => null, 'disable_introspection' => false, ], ]; ``` ## Creating a GraphQL Type Defining a type that represents your data structure ```php 'User', 'description' => 'A user', 'model' => User::class, ]; public function fields(): array { return [ 'id' => [ 'type' => Type::nonNull(Type::int()), 'description' => 'The id of the user', ], 'email' => [ 'type' => Type::string(), 'description' => 'The email of user', 'resolve' => function($root, array $args) { return strtolower($root->email); } ], 'name' => [ 'type' => Type::string(), 'description' => 'The name of user', ], 'created_at' => [ 'type' => Type::string(), 'description' => 'Creation timestamp', ], 'profile' => [ 'type' => GraphQL::type('Profile'), 'description' => 'User profile relation', ], ]; } } ``` ## Creating a Query Building a query to fetch data from your application ```php 'users', ]; public function type(): Type { return Type::nonNull(Type::listOf(Type::nonNull(GraphQL::type('User')))); } public function args(): array { return [ 'id' => [ 'name' => 'id', 'type' => Type::int(), ], 'email' => [ 'name' => 'email', 'type' => Type::string(), ] ]; } public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) { $query = User::query(); if (isset($args['id'])) { $query->where('id', $args['id']); } if (isset($args['email'])) { $query->where('email', $args['email']); } return $query->get(); } } ``` ```graphql # GraphQL query example query FetchUsers { users(id: 1) { id email name } } ``` ```bash # HTTP request example curl -X POST http://localhost/graphql \ -H "Content-Type: application/json" \ -d '{"query": "query FetchUsers { users(id: 1) { id email name } }"}' ``` ## Creating a Mutation Defining a mutation to modify data ```php 'updateUserPassword' ]; public function type(): Type { return Type::nonNull(GraphQL::type('User')); } public function args(): array { return [ 'id' => [ 'name' => 'id', 'type' => Type::nonNull(Type::int()), 'rules' => ['required', 'exists:users,id'], ], 'password' => [ 'name' => 'password', 'type' => Type::nonNull(Type::string()), 'rules' => ['required', 'min:8'], ] ]; } public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) { $user = User::findOrFail($args['id']); $user->password = bcrypt($args['password']); $user->save(); return $user; } } ``` ```graphql # GraphQL mutation example mutation UpdatePassword { updateUserPassword(id: 1, password: "newsecurepass123") { id email } } ``` ```bash # HTTP request with mutation curl -X POST http://localhost/graphql \ -H "Content-Type: application/json" \ -d '{"query": "mutation { updateUserPassword(id: 1, password: \"newsecurepass123\") { id email } }"}' ``` ## Validation with Rules Implementing Laravel validation in queries and mutations ```php 'updateUserEmail' ]; public function type(): Type { return GraphQL::type('User'); } public function args(): array { return [ 'id' => [ 'name' => 'id', 'type' => Type::int(), ], 'email' => [ 'name' => 'email', 'type' => Type::string(), ] ]; } protected function rules(array $args = []): array { return [ 'id' => ['required', 'exists:users,id'], 'email' => ['required', 'email', 'unique:users,email'], ]; } public function validationErrorMessages(array $args = []): array { return [ 'email.required' => 'Please enter your email address', 'email.email' => 'Please enter a valid email address', 'email.unique' => 'This email address is already in use', ]; } public function resolve($root, array $args) { $user = User::find($args['id']); if (!$user) { return null; } $user->email = $args['email']; $user->save(); return $user; } } ``` ```json // Validation error response format { "data": { "updateUserEmail": null }, "errors": [ { "message": "validation", "extensions": { "validation": { "email": [ "This email address is already in use" ] } }, "locations": [ { "line": 1, "column": 20 } ] } ] } ``` ## Authorization on Queries and Mutations Implementing authorization logic to control access ```php 'user', ]; public function type(): Type { return GraphQL::type('User'); } public function args(): array { return [ 'id' => [ 'name' => 'id', 'type' => Type::nonNull(Type::int()), ], ]; } public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = null, Closure $getSelectFields = null): bool { // Only allow users to query their own data or if they're admin return Auth::check() && (Auth::id() == $args['id'] || Auth::user()->is_admin); } public function getAuthorizationMessage(): string { return 'You are not authorized to view this user'; } public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) { return User::find($args['id']); } } ``` ```bash # Unauthorized request returns error curl -X POST http://localhost/graphql \ -H "Content-Type: application/json" \ -d '{"query": "{ user(id: 2) { id email } }"}' # Response: # { # "errors": [{ # "message": "You are not authorized to view this user" # }] # } ``` ## Field Privacy Controlling field visibility based on context ```php 'User', 'description' => 'A user', 'model' => User::class, ]; public function fields(): array { return [ 'id' => [ 'type' => Type::nonNull(Type::int()), 'description' => 'The id of the user' ], 'email' => [ 'type' => Type::string(), 'description' => 'The email of user', 'privacy' => function(array $args, $ctx): bool { // Only show email to the user themselves return Auth::check() && $args['id'] == Auth::id(); } ], 'public_profile' => [ 'type' => Type::string(), 'description' => 'Public profile information', ] ]; } } ``` ```graphql # When authenticated as user 1 query { user(id: 1) { id email # Will return email } } # When querying another user query { user(id: 2) { id email # Will return null due to privacy } } ``` ## Eager Loading with SelectFields Optimizing database queries to prevent N+1 problems ```php 'users', ]; public function type(): Type { return Type::listOf(GraphQL::type('User')); } public function resolve($root, array $args, $context, ResolveInfo $info, Closure $getSelectFields) { /** @var SelectFields $fields */ $fields = $getSelectFields(); $select = $fields->getSelect(); $with = $fields->getRelations(); return User::select($select)->with($with)->get(); } } ``` ```php 'User', 'model' => User::class, ]; public function fields(): array { return [ 'id' => [ 'type' => Type::nonNull(Type::int()), ], 'profile' => [ 'type' => GraphQL::type('Profile'), 'description' => 'User profile', ], 'posts' => [ 'type' => Type::listOf(GraphQL::type('Post')), 'description' => 'User posts', 'always' => ['title', 'created_at'], ] ]; } } ``` ```graphql # This query will automatically eager load profile and posts query { users { id profile { name bio } posts { title created_at } } } ``` ## Pagination Support Implementing paginated queries for large datasets ```php getRelations()) ->select($fields->getSelect()) ->paginate($args['limit'], ['*'], 'page', $args['page']); } } ``` ```graphql # Paginated query query { posts(limit: 10, page: 1) { data { id title body } total per_page current_page from to } } ``` ```json // Pagination response { "data": { "posts": { "data": [ {"id": 1, "title": "First Post", "body": "Content..."}, {"id": 2, "title": "Second Post", "body": "Content..."} ], "total": 25, "per_page": 10, "current_page": 1, "from": 1, "to": 10 } } } ``` ## File Upload Support Handling file uploads in GraphQL mutations ```php 'userProfilePhoto', ]; public function type(): Type { return GraphQL::type('User'); } public function args(): array { return [ 'user_id' => [ 'name' => 'user_id', 'type' => Type::nonNull(Type::int()), ], 'profilePicture' => [ 'name' => 'profilePicture', 'type' => GraphQL::type('Upload'), 'rules' => ['required', 'image', 'max:1500'], ], ]; } public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) { $file = $args['profilePicture']; $path = $file->store('profile-photos', 'public'); $user = User::findOrFail($args['user_id']); $user->profile_photo = $path; $user->save(); return $user; } } ``` ```javascript // JavaScript/Vue.js example with Axios const formData = new FormData(); formData.set('operations', JSON.stringify({ 'query': `mutation uploadPhoto($file: Upload!, $userId: Int!) { userProfilePhoto(user_id: $userId, profilePicture: $file) { id profile_photo } }`, 'variables': { 'userId': 1, 'file': null } })); formData.set('map', JSON.stringify({'file': ['variables.file']})); formData.append('file', fileInput.files[0]); const response = await axios.post('/graphql', formData, { headers: { 'Content-Type': 'multipart/form-data' } }); ``` ## Input Objects Creating complex input types for mutations ```php 'UserInput', 'description' => 'User input data' ]; public function fields(): array { return [ 'firstName' => [ 'name' => 'firstName', 'type' => Type::nonNull(Type::string()), 'rules' => ['required', 'max:50'], 'alias' => 'first_name', ], 'lastName' => [ 'name' => 'lastName', 'type' => Type::nonNull(Type::string()), 'rules' => ['required', 'max:50'], 'alias' => 'last_name', ], 'email' => [ 'name' => 'email', 'type' => Type::nonNull(Type::string()), 'rules' => ['required', 'email'], ], ]; } } ``` ```php 'createUser' ]; public function type(): Type { return GraphQL::type('User'); } public function args(): array { return [ 'input' => [ 'type' => Type::nonNull(GraphQL::type('UserInput')) ] ]; } public function resolve($root, array $args, $context, ResolveInfo $resolveInfo, Closure $getSelectFields) { $user = User::create($args['input']); return $user; } } ``` ```graphql # Using input objects mutation { createUser(input: { firstName: "John" lastName: "Doe" email: "john.doe@example.com" }) { id email } } ``` ## Resolver Middleware Creating custom middleware for queries and mutations ```php $info->fieldName, 'args' => $args, ]); $result = $next($root, $args, $context, $info); $duration = microtime(true) - $startTime; Log::info('GraphQL Query Completed', [ 'query' => $info->fieldName, 'duration' => $duration, ]); return $result; } public function terminate($root, array $args, $context, ResolveInfo $info, $result): void { Log::debug('GraphQL Query Terminated', [ 'query' => $info->fieldName, 'result_count' => is_countable($result) ? count($result) : null, ]); } } ``` ```php