rebing/graphql-laravel
Code-first GraphQL integration for Laravel built on webonyx/graphql-php. Define schemas, types, queries and mutations in PHP with support for multiple schemas, middleware, resolver middleware, privacy rules, and n+1 avoidance via dataloaders or SelectFields.
## Getting Started
### Minimal Steps to First Query (Updated for RC4)
1. **Installation**:
```bash
composer require rebing/graphql-laravel
php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"
Review config/graphql.php for updated default settings (e.g., batching.default = false).
Install SelectFields Package (Required for Eloquent Optimization):
composer require rebing/graphql-laravel-select-fields
Generate a Type:
php artisan make:graphql:type UserType
Edit app/GraphQL/Types/UserType.php:
public function fields(): array {
return [
'id' => ['type' => Type::nonNull(Type::int())],
'name' => ['type' => Type::string()],
];
}
Generate a Query (No SelectFields Boilerplate):
php artisan make:graphql:query UsersQuery
Edit app/GraphQL/Queries/UsersQuery.php:
public function type(): Type { return Type::listOf(GraphQL::type('User')); }
public function resolve($root, array $args) {
return User::all(); // No auto-injected SelectFields
}
Register in Config:
Add to config/graphql.php under schemas.default.types and schemas.default.query:
'types' => [App\GraphQL\Types\UserType::class],
'query' => [App\GraphQL\Queries\UsersQuery::class],
Enable SelectFields Manually: In your resolver, explicitly use the new package:
use Rebing\GraphQL\Laravel\SelectFields;
public function resolve($root, array $args) {
return SelectFields::resolve(
User::query(),
$args,
UserType::class
);
}
Test:
curl -X POST -H "Content-Type: application/json" \
-d '{"query": "{ users { id name } }"}' \
http://localhost:8000/graphql
Define Types First:
Create types for all models/DTOs. Use make:graphql:type for scaffolding.
// app/GraphQL/Types/UserType.php
public function fields() {
return [
'posts' => [
'type' => Type::listOf(GraphQL::type('Post')),
'resolve' => function ($root) {
return $root->posts()->get();
},
],
];
}
Leverage Generators (No SelectFields Boilerplate):
Use make:graphql:query, make:graphql:mutation, and make:graphql:input to scaffold operations.
Example mutation (updated for RC4):
// app/GraphQL/Mutations/CreateUser.php
public function args() {
return [
'input' => ['type' => GraphQL::type('UserInput')],
];
}
public function resolve($root, array $args) {
return User::create($args['input']);
}
Schema Organization: Group related types/queries in schema classes for modularity:
// app/GraphQL/Schemas/UserSchema.php
use Rebing\GraphQL\Support\Config;
class UserSchema implements Config {
public function __invoke() {
return [
'query' => [App\GraphQL\Queries\UserQuery::class],
'mutation' => [App\GraphQL\Mutations\UserMutation::class],
'types' => [App\GraphQL\Types\UserType::class],
];
}
}
Register in config/graphql.php:
'schemas' => [
'users' => App\GraphQL\Schemas\UserSchema::class,
],
Data Loading Strategies (Updated for RC4):
Dataloaders (for batching):
// app/GraphQL/Types/UserType.php
public function fields() {
return [
'posts' => [
'type' => Type::listOf(GraphQL::type('Post')),
'resolve' => fn($root) => $this->loadPosts->load($root->id),
],
];
}
Register loader in GraphQLServiceProvider:
$this->loaders->addLoader('posts', fn() => new UserPostLoader());
SelectFields (Explicit Usage):
Install rebing/graphql-laravel-select-fields and use it manually:
use Rebing\GraphQL\Laravel\SelectFields;
public function resolve($root, array $args) {
return SelectFields::resolve(
User::query(),
$args,
UserType::class
);
}
Middleware Integration (Updated for RC4):
POST only):
Route::graphql([
'middleware' => ['auth:sanctum'],
]);
// app/GraphQL/Middleware/AddAuthUser.php
public function handle($root, array $args, $context, ResolveInfo $info, Closure $next) {
$context['user'] = auth()->user();
return $next($root, $args, $context, $info);
}
Register in config/graphql.php:
'execution_middleware' => [
App\GraphQL\Middleware\AddAuthUser::class,
],
Validation (Updated for RC4): Use Laravel validation rules in queries/mutations:
public function args() {
return [
'email' => [
'type' => Type::nonNull(Type::string()),
'rules' => ['required', 'email'],
],
];
}
Testing (Updated for RC4):
Use TestCase for unit tests and TestCaseDatabase for integration:
public function test_users_query() {
$response = $this->httpGraphql('{ users { id name } }');
$response->assertJson([
'data' => [
'users' => [
['id' => 1, 'name' => 'John Doe'],
],
],
]);
}
New: Resolver Parameter Injection (RC4): Extend resolver dependency injection with custom injectors:
// Register injector
Field::registerParameterInjector(
fn($field, $root, array $args, $context, ResolveInfo $info) => [
'customService' => app(CustomService::class),
]
);
SelectFields Moved to Separate Package:
SelectFields is no longer auto-injected in resolvers.rebing/graphql-laravel-select-fields and use it explicitly:
use Rebing\GraphQL\Laravel\SelectFields;
return SelectFields::resolve(User::query(), $args, UserType::class);
N+1 Queries Without SelectFields:
Always use SelectFields (now explicit) or implement custom dataloaders for batching.
Circular Dependencies in Types: Avoid mutual recursion in type resolvers. Use lazy loading or dataloaders.
Forgetting to Register Types/Queries:
Ensure all types and queries are listed in config/graphql.php under the correct schema.
Introspection Disabled by Default (RC4):
Introspection is now disabled in production (GRAPHQL_DISABLE_INTROSPECTION=true). Enable only in development:
GRAPHQL_DISABLE_INTROSPECTION=false
Query Depth Limits (RC4):
Unbounded queries can crash your server. Configure depth limits in config/graphql.php:
'query_depth_limit' => 10,
'query_max_complexity' => 500,
Type Name Collisions: GraphQL type names must be unique across schemas. Use namespaces or prefixes:
protected $attributes = [
'name' => 'UserProfile', // Not just 'User'
];
Batching Disabled by Default (RC4):
Batching is now disabled by default (batching.default = false). Enable in config/graphql.php if needed:
'batching' => [
'default' => true,
How can I help you explore Laravel packages today?