How to create a GraphQL Mutation Endpoint for Magento 2.3

Author Lars Roettig November 25, 2019 5 min read

In my last Magento 2 tutorial, I showed how to build a basic GraphQL module with filtering. The primary purpose of GraphQL is to retrieve data (query), but every complete data exchange format needs a way to change server-based data (mutations). Therefore I want to show now how we can change data or create new pickup stores via the frontend.

Currently, the Magento 2 GraphQL Endpoint supports only access controls based on customers. This GraphQL will only allow a query on the customer data if the customer’s token must proived in the header section.

Since I would like to show only exemplarily how such a thing could look, I did without access control!

Before we can start here, you can find the first module that we need ground for this tutorial.

Create a new php file app/code/LarsRoettig/GraphQLStorePickup/Model/Resolver/CreatPickUpStore.php

This Class holds the logic for that endpoint is responsible for any GraphQL call that creates a new pickup store in the Database. Every Resolver Class is forced to implements Magento\Framework\GraphQl\Query\ResolverInterface to work correctly.

<?php

declare(strict_types=1);

namespace LarsRoettig\GraphQLStorePickup\Model\Resolver;

use LarsRoettig\GraphQLStorePickup\Model\CreatePickUpStore as CreatPickUpStoreService;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;

class CreatPickUpStore implements ResolverInterface
{
    /**
     * @var CreatPickUpStoreService
     */
    private $creatPickUpStore;

    /**
     * @param CreatPickUpStore $creatPickUpStore
     */
    public function __construct(CreatPickUpStoreService $creatPickUpStore)
    {
        $this->creatPickUpStore = $creatPickUpStore;
    }

    /**
     * @inheritDoc
     */
    public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
    {
        if (empty($args['input']) || !is_array($args['input'])) {
            throw new GraphQlInputException(__('"input" value should be specified'));
        }
     
        return ['pick_up_store' => $this->creatPickUpStore->execute($args['input'])];
    }
}

File app/code/LarsRoettig/GraphQLStorePickup/Model/CreatePickUpStore.php:

The second step is to create a service class that will convert a given array to a pickup store and saves them in the database. A service class help us to have single small bussiness logic that can be used by differnt other php services.

<?php
declare(strict_types=1);

namespace LarsRoettig\GraphQLStorePickup\Model;

use LarsRoettig\GraphQLStorePickup\Api\Data\StoreInterface;
use LarsRoettig\GraphQLStorePickup\Api\Data\StoreInterfaceFactory;
use LarsRoettig\GraphQLStorePickup\Api\StoreRepositoryInterface;
use Magento\Framework\Api\DataObjectHelper;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;

class CreatePickUpStore
{

    /**
     * @var DataObjectHelper
     */
    private $dataObjectHelper;
    /**
     * @var StoreRepositoryInterface
     */
    private $storeRepository;
    /**
     * @var StoreInterfaceFactory
     */
    private $storeFactory;

    /**
     * @param DataObjectHelper $dataObjectHelper
     * @param StoreRepositoryInterface $storeRepository
     * @param StoreInterfaceFactory $storeInterfaceFactory
     */
    public function __construct(
        DataObjectHelper $dataObjectHelper,
        StoreRepositoryInterface $storeRepository,
        StoreInterfaceFactory $storeInterfaceFactory
    ) {
        $this->dataObjectHelper = $dataObjectHelper;
        $this->storeRepository = $storeRepository;
        $this->storeFactory = $storeInterfaceFactory;
    }

    /**
     * @param array $data
     * @return StoreInterface
     * @throws GraphQlInputException
     */
    public function execute(array $data): StoreInterface
    {
        try {
            $this->vaildateData($data);
            $store = $this->saveStore($this->createStore($data));
        } catch (\Exception $e) {
            throw new GraphQlInputException(__($e->getMessage()));
        }

        return $store;
    }

    /**
     * Guard function to handle bad request.
     * @param array $data
     * @throws LocalizedException
     */
    private function vaildateData(array $data)
    {
        if (!isset($data[StoreInterface::NAME])) {
            throw new LocalizedException(__('Name must be set'));
        }
    }

    /**
     * Persists the given store in the data base.
     * +
     * @param StoreInterface $store
     * @return StoreInterface
     * @throws CouldNotSaveException
     */
    private function saveStore(StoreInterface $store): StoreInterface
    {
        $this->storeRepository->save($store);

        return $store;
    }

    /**
     * Create a store dto by given data array.
     *
     * @param array $data
     * @return StoreInterface
     * @throws CouldNotSaveException
     */
    private function createStore(array $data): StoreInterface
    {
        /** @var StoreInterface $store */
        $store = $this->storeFactory->create();
        $this->dataObjectHelper->populateWithArray(
            $store,
            $data,
            StoreInterface::class
        );

        return $store;
    }
}

File app/code/LarsRoettig/GraphQLStorePickup/etc/schema.graphqls

This need to be added to the file:

type Mutation {
    createPickUpStores(input: PickUpStoreInput!): PickUpStoreOutput @resolver(class: "\\LarsRoettig\\GraphQLStorePickup\\Model\\Resolver\\CreatPickUpStore") @doc(description: "Create a new pickup store")
}

type PickUpStoreOutput {
    pick_up_store: PickUpStore!
}

input PickUpStoreInput {
    name: String @doc(description: "")
    street: String @doc(description: "")
    street_num: Int @doc(description: "")
    city: String @doc(description: "")
    postcode: String @doc(description: "")
    latitude:Float @doc(description: "")
    longitude: Float @doc(description: "")
}

Full File:

type Query {
    pickUpStores(
        filter: PickUpStoresFilterInput @doc(description: "")
        pageSize: Int = 5 @doc(description: "How many items should show on the page")
        currentPage: Int = 1 @doc(description: "Allows to ussing paging it start with 1")
    ):PickUpStoresOutput @resolver(class: "\\LarsRoettig\\GraphQLStorePickup\\Model\\Resolver\\PickUpStores") @doc(description: "Allow to query for a pickup store.")
}

type Mutation {
    createPickUpStores(input: PickUpStoreInput!): PickUpStoreOutput @resolver(class: "\\LarsRoettig\\GraphQLStorePickup\\Model\\Resolver\\CreatPickUpStore") @doc(description: "Create a new pickup store")
}

type PickUpStoreOutput {
    pick_up_store: PickUpStore!
}

input PickUpStoreInput {
    name: String @doc(description: "")
    street: String @doc(description: "")
    street_num: Int @doc(description: "")
    city: String @doc(description: "")
    postcode: String @doc(description: "")
    latitude:Float @doc(description: "")
    longitude: Float @doc(description: "")
}

input PickUpStoresFilterInput {
    name: FilterTypeInput  @doc(description: "")
    postcode: FilterTypeInput @doc(description: ""),
    street: FilterTypeInput @doc(description: ""),
    street_num: FilterTypeInput @doc(description: ""),
    city: FilterTypeInput @doc(description: ""),
    latitude:FilterTypeInput @doc(description: ""),
    longitude: FilterTypeInput @doc(description: ""),
    or: PickUpStoresFilterInput
}

type PickUpStoresOutput {
    total_count:  Int @doc(description: "")
    items: [PickUpStore] @doc(description: "")
}

type PickUpStore {
    entity_id: Int @doc(description: ""),
    name: String @doc(description: ""),
    street: String @doc(description: ""),
    street_num: Int @doc(description: ""),
    city: String @doc(description: ""),
    postcode: String @doc(description: ""),
    latitude:Float @doc(description: ""),
    longitude: Float @doc(description: ""),
}

3. Installation:

bin/magento module:enable LarsRoettig_GraphQLStorePickup
bin/magento setup:db-declaration:generate-whitelist --module-name=LarsRoettig_GraphQLStorePickup
bin/magento setup:upgrade

How to call your GraphQL Endpoint

Attention the Url https://your_domain.test/graphql work only with an valid GraphQL-Request!

GrapQL Mutation

GraphQL-Mutation:

mutation {
  createPickUpStores(
    input: {
      name: "Mustation Store"
      street: "sweswq"
      street_num: 12
      postcode: "83059"
      latitude: 22.3
      longitude:22.3
    }
  )
  {
    pick_up_store {
      name
    }
  }
}
Avatar of Lars Roettig
Written by

Lars Roettig

Software Engineer at TechDivision GmbH and Maintainer of the Community Engineering Team at Magento. He has 8 years of professional Software Engineering experience. Lars is passionate about Magento and Open Source.