rebing/graphql-laravel
Code-first GraphQL integration for Laravel built on webonyx/graphql-php. Define schema in PHP (types, queries, mutations), support multiple schemas with per-schema middleware, resolver middleware, privacy rules, and data loading to avoid N+1 (dataloaders/SelectFields).
## Getting Started
### Minimal Steps
1. **Installation**:
```bash
composer require rebing/graphql-laravel
php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"
Review config/graphql.php for schema configuration.
Generate a Type:
php artisan make:graphql:type UserType
Define fields in app/GraphQL/Types/UserType.php:
public function fields(): array {
return [
'id' => ['type' => Type::nonNull(Type::int())],
'name' => ['type' => Type::nonNull(Type::string())],
];
}
Generate a Query:
php artisan make:graphql:query UsersQuery
Implement resolve() in app/GraphQL/Queries/UsersQuery.php:
public function resolve($root, array $args): array {
return User::all()->toArray();
}
Register Schema:
Update config/graphql.php:
'schemas' => [
'default' => [
'query' => [App\GraphQL\Queries\UsersQuery::class],
'types' => [App\GraphQL\Types\UserType::class],
],
],
Test:
curl -X POST -H "Content-Type: application/json" \
-d '{"query": "{ users { id name } }"}' \
http://localhost:8000/graphql
Fetch Eloquent data with filtering:
// In UsersQuery.php
public function args(): array {
return [
'name' => ['type' => Type::string()],
];
}
public function resolve($root, array $args): array {
return User::when($args['name'], fn($q) => $q->where('name', 'like', "%{$args['name']}%"))
->get()
->toArray();
}
Query:
query {
users(name: "John") {
id
name
}
}
Type-Driven Development:
UserType, PostType), then queries/mutations that use them.PostType with author field (type: UserType), then use it in PostsQuery.Resolver Patterns:
return User::find($id)).public function resolve($root, array $args) {
return $this->load('users')->loadMany($args['ids']);
}
Register loader in bootstrap.php:
GraphQL::addDataloader('users', fn() => new UserLoader());
Schema Organization:
'schemas' => [
'admin' => ['query' => [AdminQuery::class]],
'public' => ['query' => [PublicQuery::class]],
],
#[GraphQLSchema('admin')]
class AdminQuery extends Query { ... }
Validation Integration:
public function args(): array {
return [
'email' => [
'type' => Type::nonNull(Type::string()),
'rules' => ['required', 'email'],
],
];
}
public function rules(): array {
return ['email' => ['required', 'email', Rule::unique('users')]];
}
Eloquent Integration:
rebing/graphql-laravel-select-fields for optimized queries:
use Rebing\GraphQL\Laravel\SelectFields\SelectFields;
SelectFields::macro('user', fn($query) => $query->select(['id', 'name']));
UserType:
public function fields(): array {
return [
'posts' => [
'type' => Type::listOf(GraphQL::type('Post')),
'resolve' => fn($user) => $user->posts,
'selectFields' => ['title', 'published_at'],
],
];
}
Middleware Stack:
GraphQL::addHttpMiddleware('admin', fn($request, $next) => auth()->check() ? $next($request) : abort(403));
GraphQL::addExecutionMiddleware('auth', new class implements ExecutionMiddleware {
public function handle($schemaName, $schema, $params, $root, $context, Closure $next) {
$context['user'] = auth()->user();
return $next($schemaName, $schema, $params, $root, $context);
}
});
Testing:
TestCase helpers:
public function test_users_query() {
$response = $this->httpGraphql('{ users { id name } }');
$response->assertJson(['data' => ['users' => [...]]);
}
GraphQL::addResolverMiddleware('mock', fn($root, $args, $context, $info, $next) => [
'id' => 1,
'name' => 'Mock User',
]);
Performance:
DataLoader for N+1 queries:
GraphQL::addDataloader('users', fn() => new DataLoader(fn($ids) => User::whereIn('id', $ids)->get()));
RelayConnection or custom cursors:
use Rebing\GraphQL\Support\Facades\GraphQL;
GraphQL::addType('Connection', RelayConnection::class);
Introspection:
.env:
GRAPHQL_DISABLE_INTROSPECTION=false
Circular Dependencies:
User → Post → User) cause infinite loops.resolveType() for interfaces/unions or lazy-load fields.Non-Null Fields:
nonNull without ensuring data availability causes runtime errors.Type::nonNull(Type::string()) sparingly; prefer nullable fields with default values.Dataloader Caching:
GraphQL::getDataloader('users')->clear();
Validation Timing:
authorize() for post-validation checks.Error Formatting:
config/graphql.php:
'error_formatter' => Rebing\GraphQL\Support\ErrorFormatter::class,
ErrorFormatter to hide sensitive data:
public function formatError(Error $error, array $context) {
return parent::formatError($error, [
'message' => $error->getMessage(),
'extensions' => ['code' => $error->getCode()],
]);
}
Query Complexity:
config/graphql.php:
'query_complexity' => [
'enabled' => env('GRAPHQL_QUERY_COMPLEXITY', false),
'max_complexity' => 1000,
],
GraphQL::addExecutionMiddleware('complexity', new QueryComplexityMiddleware());
Middleware Order:
GraphQL::addExecutionMiddleware('log', new class implements ExecutionMiddleware {
public function handle($schemaName, $schema, $params, $root, $context, Closure $next) {
\Log::info('Execution middleware', ['params' => $params]);
return $next($schemaName, $schema, $params, $root, $context);
}
});
GraphQL::type() with modifiers:
'users
How can I help you explore Laravel packages today?