pixelandtonic/graphql-php
PHP implementation of the GraphQL specification (based on graphql-js). Build schemas, execute queries, and add custom types, fields, and resolvers. Install via Composer and explore full docs and ready-to-run examples.
Install the package:
composer require webonyx/graphql-php
Define a basic schema (e.g., app/GraphQL/schema.graphql):
type Query {
hello: String
}
Create a resolver (e.g., app/GraphQL/Resolvers/Query.php):
namespace App\GraphQL\Resolvers;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
class Query {
public function hello() {
return 'Hello, Laravel + GraphQL!';
}
}
Set up the schema in a Laravel service provider (e.g., AppServiceProvider):
use GraphQL\GraphQL;
use GraphQL\Type\Definition\Type;
use GraphQL\Schema;
use GraphQL\Type\Definition\ObjectType;
public function boot() {
$query = new ObjectType([
'name' => 'Query',
'fields' => [
'hello' => [
'type' => Type::string(),
'resolve' => fn() => (new \App\GraphQL\Resolvers\Query())->hello(),
],
],
]);
$schema = new Schema(['query' => $query]);
app()->singleton('graphql.schema', fn() => $schema);
}
Create a route (e.g., routes/web.php):
use GraphQL\Server\ServerConfig;
use GraphQL\Server\StandardServer;
Route::post('/graphql', function () {
$schema = app('graphql.schema');
$config = new ServerConfig();
$server = new StandardServer($schema, $config);
return $server->executePsrRequest($_SERVER, $_GET, $_POST);
});
Test with a GraphQL client (e.g., Postman or Apollo Studio):
query {
hello
}
Use the package to expose Laravel Eloquent models as GraphQL types. For example:
// Define a UserType
$userType = new ObjectType([
'name' => 'User',
'fields' => [
'id' => ['type' => Type::id()],
'name' => ['type' => Type::string()],
'email' => ['type' => Type::string()],
],
]);
// Resolve fields dynamically
$schema = new Schema([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'user' => [
'type' => $userType,
'args' => [
'id' => ['type' => Type::id()],
],
'resolve' => function ($root, $args) {
return \App\Models\User::find($args['id']);
},
],
],
]),
]);
Define schemas in .graphql files and load them dynamically:
use GraphQL\Type\Definition\Type;
use GraphQL\Schema;
use GraphQL\Type\Definition\ObjectType;
$schemaConfig = [
'types' => [
'Query' => [
'fields' => [
'posts' => [
'type' => Type::listOf($postType),
'resolve' => fn() => \App\Models\Post::all(),
],
],
],
],
];
$schema = new Schema($schemaConfig);
For large schemas, use lazy loading to improve performance:
use GraphQL\Type\Definition\TypeResolver;
use GraphQL\Type\Definition\TypeResolverInterface;
class LazyTypeResolver implements TypeResolverInterface {
public function resolve($typeName) {
if ($typeName === 'Post') {
return $this->loadPostType();
}
return null;
}
private function loadPostType() {
// Load and return PostType dynamically
}
}
$schemaConfig = [
'typeResolvers' => [new LazyTypeResolver()],
];
Organize resolvers into classes for better maintainability:
class PostResolver {
public function getPosts() {
return \App\Models\Post::all();
}
public function getPost($root, $args) {
return \App\Models\Post::find($args['id']);
}
}
// In schema definition:
'posts' => [
'type' => Type::listOf($postType),
'resolve' => fn() => (new PostResolver())->getPosts(),
],
Use closures for simple or one-off resolvers:
'user' => [
'type' => $userType,
'args' => ['id' => ['type' => Type::id()]],
'resolve' => function ($root, $args) {
return \App\Models\User::find($args['id']);
},
],
Define input types for mutations to validate incoming data:
$createUserInputType = new InputObjectType([
'name' => 'CreateUserInput',
'fields' => [
'name' => ['type' => Type::nonNull(Type::string())],
'email' => ['type' => Type::nonNull(Type::string())],
],
]);
$schemaConfig = [
'mutation' => new ObjectType([
'name' => 'Mutation',
'fields' => [
'createUser' => [
'type' => $userType,
'args' => [
'input' => ['type' => new GraphQLNonNull($createUserInputType)],
],
'resolve' => function ($root, $args) {
return \App\Models\User::create($args['input']);
},
],
],
]),
];
Inject Laravel services (e.g., repositories, HTTP clients) into resolvers:
use Illuminate\Support\Facades\Http;
class ExternalApiResolver {
public function __construct(private Http $http) {}
public function fetchData() {
return $this->http->get('https://api.example.com/data')->json();
}
}
// Bind in Laravel service provider:
app()->bind(\App\GraphQL\Resolvers\ExternalApiResolver::class, function ($app) {
return new \App\GraphQL\Resolvers\ExternalApiResolver($app['http']);
});
Use Laravel middleware to protect GraphQL endpoints:
Route::middleware(['auth:sanctum'])->post('/graphql', function () {
$schema = app('graphql.schema');
$config = new ServerConfig();
$server = new StandardServer($schema, $config);
return $server->executePsrRequest($_SERVER, $_GET, $_POST);
});
Cache GraphQL query results using Laravel's cache:
use Illuminate\Support\Facades\Cache;
$schemaConfig = [
'queryCache' => function ($query, $variables) {
return Cache::remember("graphql:{$query}", 60, fn() => null);
},
'queryCacheStore' => function ($query, $variables, $result) {
Cache::put("graphql:{$query}", $result, 60);
},
];
"false" as a string won't coerce to false).Type::nonNull() for required fields.
'email' => ['type' => Type::nonNull(Type::string())],
DebugFlag to expose detailed errors in development:
$config = new ServerConfig([
'debugFlag' => DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE,
]);
DataLoader for batching and caching:
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
$promiseAdapter = new PromiseAdapter();
$dataLoader = new DataLoader(fn($ids) => \App\Models\User::whereIn('id', $ids)->get());
$schemaConfig = [
'query
How can I help you explore Laravel packages today?