youshido/graphql
A PHP GraphQL library for building schemas and executing queries with a type-safe, object-oriented API. Define types, fields, resolvers, and middleware in code, with support for input validation, custom scalars, and introspection for PHP apps.
Installation
composer require youshido/graphql
Add to config/app.php under providers:
YouShido\GraphQL\GraphQLServiceProvider::class,
Define a Basic Schema
Create a schema class (e.g., app/GraphQL/Schema.php):
use YouShido\GraphQL\Type\ObjectType;
use YouShido\GraphQL\Type\StringType;
use YouShido\GraphQL\Schema;
$schema = new Schema();
$schema->type('Query', new class extends ObjectType {
protected function fields(): array {
return [
'hello' => fn() => new StringType('Hello, GraphQL!')
];
}
});
Register Schema in Laravel
In AppServiceProvider@boot():
GraphQL::schema($schema);
First Query Use a middleware or route to handle GraphQL requests:
Route::post('/graphql', function (Request $request) {
return GraphQL::execute($request->input('query'));
});
Test with:
query {
hello
}
Define a UserType and resolver:
$schema->type('User', new class extends ObjectType {
protected function fields(): array {
return [
'name' => fn() => new StringType($this->rootValue->name),
'email' => fn() => new StringType($this->rootValue->email)
];
}
});
$schema->type('Query', new class extends ObjectType {
protected function fields(): array {
return [
'user' => fn() => new UserType(User::find(1))
];
}
});
Define Types First Model your domain as GraphQL types before writing resolvers. Example:
$schema->type('Post', new class extends ObjectType {
protected function config(): array {
return [
'description' => 'A blog post'
];
}
protected function fields(): array {
return [
'title' => fn() => new StringType($this->rootValue->title),
'content' => fn() => new StringType($this->rootValue->content)
];
}
});
Compose Queries/Mutations
Use ObjectType for root queries/mutations:
$schema->type('Query', new class extends ObjectType {
protected function fields(): array {
return [
'posts' => fn() => new ListType(PostType::class, Post::all()),
'createPost' => fn() => new MutationType(/* ... */)
];
}
});
Reuse Types with Interfaces/Unions
$schema->type('Node', new InterfaceType([
'resolveType' => fn($value) => $value instanceof User ? 'User' : 'Post'
]));
$schema->type('User', new class extends ObjectType {
protected function interfaces(): array { return ['Node']; }
});
Dependency Injection Resolve Laravel services in resolvers:
'posts' => fn() => new ListType(PostType::class, app(PostRepository::class)->all())
Authentication Use middleware to attach context:
GraphQL::middleware(function ($request, $next) {
$request->merge(['user' => auth()->user()]);
return $next($request);
});
Validation Leverage Laravel’s validation in input types:
$schema->type('CreatePostInput', new InputObjectType([
'fields' => [
'title' => ['type' => new NonNullType(StringType::of())],
'content' => ['type' => new NonNullType(StringType::of())]
],
'validate' => function ($input) {
$validator = Validator::make($input, [
'title' => 'required|max:255',
'content' => 'required'
]);
if ($validator->fails()) {
throw new ValidationError($validator->errors());
}
}
]));
Define a Mutation
$schema->type('Mutation', new class extends ObjectType {
protected function fields(): array {
return [
'createUser' => fn() => new FieldType([
'type' => new UserType(),
'args' => ['input' => new Argument(['type' => new NonNullType(CreateUserInputType::of())])],
'resolve' => fn($root, $args) => User::create($args['input'])
])
];
}
});
Optimistic UI Return resolved data immediately for client-side updates:
'resolve' => fn($root, $args) => [
'user' => User::create($args['input']),
'clientMutationId' => $args['input']['clientMutationId']
]
Circular References
Avoid infinite loops in resolvers. Use lazy() for deferred resolution:
'author' => fn() => new LazyType(fn() => new UserType($this->rootValue->author))
Type Mismatches
Ensure resolver return types match declared field types. Use NonNullType for required fields:
'id' => fn() => new NonNullType(IDType::of())
Performance
eagerLoad() or with() in resolvers:
'posts' => fn() => new ListType(PostType::class, Post::with('author')->get())
Deprecated API The package is last updated in 2019. Check for:
graphql-php/graphql for active maintenance.Enable Introspection Add to schema config:
$schema->config(['introspection' => true]);
Query schema via:
query IntrospectionQuery {
__schema {
types {
name
kind
}
}
}
Logging Execution
Use a custom ExecutionStrategy to log queries:
GraphQL::executionStrategy(new class extends ExecutionStrategy {
public function execute(ExecutionContext $context) {
Log::debug('GraphQL Query:', ['query' => $context->getQuery()]);
return parent::execute($context);
}
});
Validation Errors
Catch ValidationError in resolvers:
try {
return User::create($args['input']);
} catch (ValidationError $e) {
throw new GraphQLException($e->getMessage());
}
Custom Scalars
Extend ScalarType for custom types (e.g., DateTime):
$schema->type('DateTime', new class extends ScalarType {
public function serialize($value) {
return $value->format('Y-m-d H:i:s');
}
public function parseValue($value) {
return new DateTime($value);
}
});
Middleware Pipeline Add pre/post-processing:
GraphQL::middleware(function ($request, $next) {
// Pre-processing
$response = $next($request);
// Post-processing
return $response;
});
Plugin System
Use Schema::extend() to add global functionality:
Schema::extend(function ($schema) {
$schema->type('Query')->fields['time'] = fn() => new StringType(now()->toIso8601String());
});
Caching Schema Cache the compiled schema in Laravel’s cache:
$schema = Cache::remember('graphql.schema', now()->addHours(1), function() {
return new Schema(/* ... */);
});
Testing
Use GraphQL::execute() in tests:
$result = GraphQL::execute('query { hello }');
$this->assertEquals('Hello, GraphQL!', $result->data['hello']);
API Resources
Convert Laravel ApiResources to GraphQL types:
$schema->type('UserResource', new class extends ObjectType {
protected function fields(): array {
return collect
How can I help you explore Laravel packages today?