Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

GraphQL

GraphQL is the second kind of API in Magento. Unlike REST, there is a single URL — /graphql — and the client lists the fields it needs in the query and gets exactly those. GraphQL is used by JavaScript storefronts and mobile apps. A module adds its queries to the shared schema and writes a class that returns the data for them — a resolver.

Where the files live

FileWhat is in it
etc/schema.graphqlsThe definition of queries and types
Model/Resolver/Reviews.phpThe resolver — a class that returns data

Both files are created by hand.

app/code/Acme/Hello/
├── Model/
│   └── Resolver/
│       └── Reviews.php         new file
└── etc/
    ├── module.xml              changed
    └── schema.graphqls         new file

In module.xml add the Magento_GraphQl module to the sequence section and run bin/magento setup:upgrade.

The schema

# app/code/Acme/Hello/etc/schema.graphqls
type Query {
    helloReviews(productId: Int!): [HelloReview] @resolver(class: "Acme\\Hello\\Model\\Resolver\\Reviews")
}
 
type HelloReview {
    author: String
    rating: Int
}

The file adds the helloReviews query with a required productId argument to the shared Query type. The query returns a list of HelloReview objects with two fields. The @resolver directive names the class that returns the data; the backslash in the class name is doubled.

The resolver

// app/code/Acme/Hello/Model/Resolver/Reviews.php
namespace Acme\Hello\Model\Resolver;
 
use Acme\Hello\Api\ReviewRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
 
class Reviews implements ResolverInterface
{
    public function __construct(
        private ReviewRepositoryInterface $reviewRepository,
        private SearchCriteriaBuilder $criteriaBuilder,
    ) {}
 
    public function resolve(Field $field, $context, ResolveInfo $info, ?array $value = null, ?array $args = null)
    {
        $criteria = $this->criteriaBuilder
            ->addFilter('product_id', (int) $args['productId'])
            ->create();
 
        $reviews = [];
        foreach ($this->reviewRepository->getList($criteria)->getItems() as $review) {
            $reviews[] = [
                'author' => $review->getAuthor(),
                'rating' => $review->getRating(),
            ];
        }
 
        return $reviews;
    }
}

Query arguments arrive in the $args array. The resolver finds reviews through the repository and returns an array whose keys match the field names of the HelloReview type.

Checking

bin/magento cache:clean
 
curl -X POST http://shop.test/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ helloReviews(productId: 1) { author rating } }"}'

The server answers with a JSON object where data.helloReviews holds the list of authors and ratings of the reviews for product 1. If you remove rating from the query, only authors remain in the response.

The query itself, without the wrapper, looks like this:

{
    helloReviews(productId: 1) {
        author
        rating
    }
}

It is more convenient to send queries from a dedicated GraphQL client: it reads the shop schema and suggests fields.

Queries and mutations

Type in the schemaWhat it is for
QueryReading data
MutationChanging: create, update, delete

A mutation is described the same way — as a field in the Mutation type with the @resolver directive. Its data is usually passed as a single argument of an input type declared with the input keyword.

Errors

If a query cannot be executed, the resolver throws an exception from the Magento\Framework\GraphQl\Exception namespace, for example GraphQlInputException for wrong arguments. The client gets the message in the errors section of the response.

Common mistake

The schema was changed, but the server answers that the helloReviews field does not exist. The merged schema is cached: run bin/magento cache:clean after every edit of schema.graphqls. The second cause is a class name in @resolver written with one backslash instead of two.

Thanks for reading!

Donate to the site — we work hard for you.