Querying a Single Event by Identifier
Querying a Single Event by Identifier
~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
For a detail page, the list query from chapters 12-14 isn't a good fit - it would need an artificial filter for exactly one record and would still return the entire Events wrapper structure including page_info. A dedicated single-item query that returns an Event directly is cleaner.
The query in the schema
type Query {
event(
identifier: String!
): Event
@resolver(class: "Mironsoft\\Event\\Model\\Resolver\\Event")
@doc(description: "Returns a single event by its unique identifier")
}identifier: String! is deliberately declared as a required argument (chapter 7) - a single-item query without an identifier makes no business sense. The return type Event itself, however, stays nullable: if no matching event exists, the query returns null instead of an error - more on that shortly.
Implementing the resolver
<?php
declare(strict_types=1);
namespace Mironsoft\Event\Model\Resolver;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Mironsoft\Event\Api\EventRepositoryInterface;
/**
* Resolves the event query field.
*/
class Event implements ResolverInterface
{
/**
* @param EventRepositoryInterface $eventRepository Service contract for event access
*/
public function __construct(
private readonly EventRepositoryInterface $eventRepository,
) {
}
/**
* Loads a single event by its identifier argument.
*
* @param Field $field Resolved GraphQL field configuration
* @param mixed $context Resolver context
* @param ResolveInfo $info GraphQL resolve tree info
* @param array|null $value Parent resolver's value, unused for a top-level field
* @param array|null $args Arguments passed to the event field
* @return array<string, mixed>
* @throws GraphQlNoSuchEntityException
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
?array $value = null,
?array $args = null
): array {
$identifier = (string) ($args['identifier'] ?? '');
try {
$event = $this->eventRepository->getByIdentifier($identifier);
} catch (NoSuchEntityException $exception) {
throw new GraphQlNoSuchEntityException(
__('No event found with identifier "%1".', $identifier),
$exception
);
}
return [
'event_id' => $event->getEventId(),
'identifier' => $event->getIdentifier(),
'title' => $event->getTitle(),
'description' => $event->getDescription(),
'location' => $event->getLocation(),
'start_at' => $event->getStartAt(),
'end_at' => $event->getEndAt(),
'capacity' => $event->getCapacity(),
'model' => $event,
];
}
}GraphQlNoSuchEntityException - chapter 19 covers the full family of GraphQL exceptions in depth - translates the repository's internal NoSuchEntityException into an error response that makes sense to GraphQL clients, carrying its own category, instead of a bare, technical PHP error.
Why no dedicated DataProvider class here?
Unlike with Events (chapter 13), the logic here deliberately stays directly inside the resolver - a single repository call plus array mapping is simply too little standalone logic to justify a separate class. The DataProvider convention isn't a goal in itself: it pays off once several arguments need translating into SearchCriteria (as in chapters 13-14), not for every tiny resolver.
query {
event(identifier: "magento-graphql-meetup-berlin") {
title
location
start_at
end_at
}
}Tipp: Using an identifier instead of a numeric ID as the public argument (as here, analogous to url_key on products/categories) prevents clients from depending on internal, auto-incrementing database IDs - IDs stay an internal implementation detail, while the identifier is the stable, public address of an event.
Achtung: An empty string as the identifier argument isn't caught by GraphQL itself - String! only requires that a string is passed at all, not that it's non-empty. The resolver has to handle this business-level edge case itself, here implicitly via getByIdentifier(''), which likewise ends up in NoSuchEntityException and thus in a clean GraphQL error response.
With the list, filtering/sorting, and single-item query in place, block 4 is complete - the events API can be fully read. Block 5 adds write access: mutations and authentication.