Install the Bundle
composer require thecodingmachine/graphqlite-bundle
Register the bundle in config/bundles.php (Symfony auto-discovers it, but explicit registration ensures compatibility):
return [
// ...
TheCodingMachine\GraphQLite\Bundle\GraphQLiteBundle::class => ['all' => true],
];
Configure the Bundle
Add basic config in config/packages/graphqlite.yaml:
graphqlite:
schema:
query: 'App\\GraphQL\\Query'
mutation: 'App\\GraphQL\\Mutation'
debug: '%kernel.debug%'
Define Your First Schema
Create a Query class (e.g., src/GraphQL/Query.php):
namespace App\GraphQL;
use TheCodingMachine\GraphQLite\Annotations\Query;
use TheCodingMachine\GraphQLite\Types\TypeResolverInterface;
class Query
{
/**
* @Query()
*/
public function hello(): string
{
return 'World';
}
}
Access the GraphQL Endpoint Run the dev server and query at:
http://localhost:8000/graphql
Example query:
query {
hello
}
Annotate a Query to Fetch Entities
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use TheCodingMachine\GraphQLite\Annotations\Query;
class Query
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* @Query()
*/
public function user(int $id): User
{
return $this->em->getRepository(User::class)->find($id);
}
}
Query the Entity
query {
user(id: 1) {
id
name
}
}
Auto-Discovery: GraphQLite auto-discovers Doctrine entities as types. Annotate fields to control output:
/**
* @Query()
*/
public function user(int $id): User
{
return $this->em->find(User::class, $id);
}
Outputs all public properties of User by default.
Custom Types: Extend TypeResolverInterface for complex types:
use TheCodingMachine\GraphQLite\Types\TypeResolverInterface;
class CustomTypeResolver implements TypeResolverInterface
{
public function getType(string $typeName): ?string
{
return match ($typeName) {
'App\\GraphQL\\CustomType' => 'CustomGraphQLType',
default => null,
};
}
}
Register in config/packages/graphqlite.yaml:
graphqlite:
type_resolver: App\GraphQL\CustomTypeResolver
Define a Mutation:
use TheCodingMachine\GraphQLite\Annotations\Mutation;
use TheCodingMachine\GraphQLite\Annotations\InputType;
class Mutation
{
/**
* @Mutation()
*/
public function createUser(string $name, string $email): User
{
$user = new User();
$user->setName($name);
$user->setEmail($email);
$this->em->persist($user);
$this->em->flush();
return $user;
}
}
Query:
mutation {
createUser(name: "John", email: "[email protected]") {
id
name
}
}
Input Types: Use @InputType for complex inputs:
#[InputType]
class CreateUserInput
{
public string $name;
public string $email;
}
/**
* @Mutation()
*/
public function createUser(CreateUserInput $input): User
{
// ...
}
Dependency Injection: Inject any Symfony service into your Query/Mutation classes:
use Symfony\Component\Mailer\MailerInterface;
class Query
{
public function __construct(private MailerInterface $mailer) {}
/**
* @Query()
*/
public function sendTestEmail(): bool
{
$this->mailer->send(...);
return true;
}
}
Event Listeners: Listen to GraphQL events (e.g., GraphQLiteEvents::EXECUTE_QUERY):
use TheCodingMachine\GraphQLite\Event\ExecuteQueryEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class GraphQLSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'graphqlite.execute_query' => 'onExecuteQuery',
];
}
public function onExecuteQuery(ExecuteQueryEvent $event)
{
// Log or modify the query
}
}
use TheCodingMachine\GraphQLite\Annotations\Query;
use TheCodingMachine\GraphQLite\Types\Type;
use TheCodingMachine\GraphQLite\Types\CursorConnection;
/**
* @Query()
*/
public function users(int $first = 10, ?string $after = null): CursorConnection
{
$query = $this->em->createQueryBuilder()
->select('u')
->from(User::class, 'u')
->orderBy('u.id');
return new CursorConnection(
$query->getQuery(),
$first,
$after,
new Type(User::class)
);
}
Query:
query {
users(first: 5) {
edges {
node {
id
name
}
}
pageInfo {
hasNextPage
}
}
}
# config/packages/graphqlite.yaml
graphqlite:
middleware:
- App\GraphQL\Middleware\AuthMiddleware
Example middleware:
use TheCodingMachine\GraphQLite\Middleware\MiddlewareInterface;
use TheCodingMachine\GraphQLite\Execution\ExecutionContext;
class AuthMiddleware implements MiddlewareInterface
{
public function handle(ExecutionContext $context): void
{
if (!$context->getRequest()->getUser()) {
throw new \RuntimeException('Unauthorized');
}
}
}
Circular References in Types
User has-many Posts, Post belongs-to User).@IgnoreType or manually define types:
use TheCodingMachine\GraphQLite\Annotations\IgnoreType;
#[IgnoreType]
class User {}
Doctrine Lazy Loading
$user = $this->em->find(User::class, $id, ['posts' => 'posts']);
Type Mismatches
// ❌ Fails if `find()` returns `null`
/**
* @Query()
* @return User
*/
public function user(int $id): ?User
{
return $this->em->find(User::class, $id);
}
Caching Headaches
debug: true in config) to avoid stale schemas:
graphqlite:
debug: true
Enable Debug Mode
Set debug: true in config to see:
Inspect the Schema
Query the __schema introspection endpoint:
query {
__schema {
types {
name
}
}
}
Use graphqlite:dump-schema
Dump the schema to a file for offline inspection:
php bin/console graphqlite:dump-schema
Validate Annotations Run the validator to catch annotation errors:
php bin/console graphql
How can I help you explore Laravel packages today?