Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

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

app/code/Mironsoft/Event/etc/schema.graphqls
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

app/code/Mironsoft/Event/Model/Resolver/Event.php
<?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.