Magento2 How to create a custom GraphQL – Part1: Create a simple graphql query

Magento2 How to create a custom GraphQL – Part1: Create a simple graphql query
()

GraphQL is a query language for APIs, gives clients the power to ask for exactly what they need and nothing more. GraphQL uses mutations to perform CRUD operations.

Today we will only talk about creating and query a simple GraphQL api. Let us begin by creating schema.graphqls under etc directory:

Know/ModuleGraphql/etc/schema.graphqls

Now add following content to the file:

type Query
{
    getPostById(id: Int @doc(descrition: "Post ID. Must be an integer")): Posts
        @resolver(class: "Know\\ModuleGraphql\\Model\\Resolver\\PostById")
        @doc(description: "Retrieve a post by id.")
}
type Posts
{
    id: Int
    title: String
    categories: String
    description: String
}

Next add the Post resolver model: Know/ModuleGraphql/Model/Resolver/PostById.php

<?php
namespace Know\ModuleGraphql\Model\Resolver;

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 Post implements ResolverInterface
{
    public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
    {
        if (!isset($args['id'])) {
            throw new GraphQlInputException(__('Invalid Parameter provided.'));
        }

        $id = $args['id'];

        $allposts = $this->getAllPosts();
        $post = [];
        foreach ($allposts as $item) {
            if ($id == $item['id']) {
                $post = $item;
                break;
            }
        }

        return $post;
    }

    public function getAllPosts(): array
    {
        return [
            [
                "id" => 1,
                "title" => "My Post 1",
                "categories" => "my posts, custom posts",
                "description" => "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
            ],
            [
                "id" => 2,
                "title" => "My Post 2",
                "categories" => "my posts, custom post2",
                "description" => "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
            ],
        ];
    }
}

Time to run our graphQL api. Use postman or a GraphQL client to test the request. I usually use Altair GraphQL chrome extension to run the request.

In url specify {base_url}/graphql and add following query under query or request body section:

query {
  getPostById(
    id: 2
  ) {
    title
    categories
    description
  }
}

The output will be:

Query the dynamic posts

The database is going to involve for dynamic posts. For this purpose let us create a table called “posts”. To follow best practices create a separate module “Know_Module”.

Create Know/Module/etc/module.xml:

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Know_Module" setup_version="1.0.0" />
</config>

Register the module Know/Module/registration.php:

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Know_Module',
    __DIR__
);

Add posts table using db_schema.xml. Create db schema Know/Module/etc/db_schema.xml

<?xml version="1.0" encoding="UTF-8" ?>

<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="posts" resource="default" engine="innodb" comment="Posts Entity">
        <column xsi:type="int" name="entity_id" unsigned="true" nullable="false" identity="true" comment="Entity ID" />
        <column xsi:type="varchar" name="title" nullable="false" comment="Title" length="255" />
        <column xsi:type="text" name="description" nullable="true" comment="Title" />
        <column xsi:type="varchar" name="categories" nullable="false" comment="Title" length="255" />
        <column xsi:type="timestamp" name="created_at" nullable="false" comment="Creation datetime"
                default="CURRENT_TIMESTAMP"  />
        <column xsi:type="timestamp" name="updated_at" nullable="false" on_update="true" default="CURRENT_TIMESTAMP"
                comment="Update datetime" />
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <column name="entity_id" />
        </constraint>
        <constraint xsi:type="unique" referenceId="POSTS_ENTT_ID">
            <column name="entity_id"/>
        </constraint>
    </table>
</schema>


Run setup upgrade command to install the module.

php bin/magento setup:upgrade

Insert some dummy data in posts table.

INSERT INTO `posts` (`entity_id`, `title`, `description`, `categories`, `created_at`, `updated_at`) VALUES
(null, 'What is Lorem Ipsum?', 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', 'Lorem Ipsum', '2022-09-01 12:34:19', '2022-09-01 12:34:19'),
(null, 'Why do we use it?', 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using \'Content here, content here\', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for \'lorem ipsum\' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).', 'Sample text, Text tree', '2022-09-01 12:34:19', '2022-09-01 12:34:19');

Create Post model: Know/Module/Model/Post.php

<?php
namespace Know\Module\Model;

use Magento\Framework\Model\AbstractModel;

class Post extends AbstractModel
{
    public function _construct()
    {
        $this->_init(\Know\Module\Model\ResourceModel\Post::class);
    }
}

Next create a Post Resource model: Know/Module/Model/ResourceModel/Post.php

<?php
namespace Know\Module\Model\ResourceModel;

class Post extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    protected function _construct()
    {
        $this->_init("posts", "entity_id");
    }
}

Now create a Collection model: Know/Module/Model/ResourceModel/Post/Collection.php

<?php
namespace Know\Module\Model\ResourceModel\Post;

class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
    public function _construct()
    {
        $this->_init(
            \Know\Module\Model\Post::class,
            \Know\Module\Model\ResourceModel\Post::class
        );
    }
}

Modify the resolver model and replace with the code below: Know/Module/Model/Resolver/PostById.php

<?php
namespace Know\Module\Model\Resolver;

use Know\Module\Model\ResourceModel\Post\Collection;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;

class PostById implements \Magento\Framework\GraphQl\Query\ResolverInterface
{
    /**
     * @var Collection
     */
    private Collection $collection;

    /**
     * @param Collection $collection
     */
    public function __construct(Collection $collection)
    {
        $this->collection = $collection;
    }

    /**
     * @inheritDoc
     */
    public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
    {
        if (!isset($args['id'])) {
            throw new GraphQlInputException(__("Id is required field."));
        }

        $id = $args['id'];
        $collection = $this->collection->addFieldToFilter("entity_id", $id);
        if (!$collection->count()) {
            return [];
        }

        $postData = [];

        foreach ($collection->getData() as $item) {
            $postData = [
                "id"    => $item["entity_id"],
                "title"    => $item["title"],
                "description"    => $item["description"],
                "categories"    => $item["categories"],
                "created"    => $item["created_at"],
                "updated"    => $item["updated_at"],
            ];
        }

        return $postData;
    }
}

Flush the cache:


php bin/magento c:c

Implement and test the query. The result should look similar to the screenshot below.

Query the dynamic post output

How useful was this post?

Click on a star to rate it!

Average rating / 5. Vote count:

No votes so far! Be the first to rate this post.

As you found this post useful...

Follow us on social media!

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

2 Comments

  • Hi,

    If you give wrong ID in request what is the error data structure?
    How can we add the custom error data if request parameter is wrong.?

    Can u please help on this?

    • The easiest way to customize the error data is to create an interceptor or plugin for Magento\Framework\GraphQl\Query\ErrorHandler class. You can customize it your own way. We will go with a customization example for this scenario:

      Create di.xml under etc folder:

      Know/Module/etc/di.xml

      <?xml version="1.0"?>
      
      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
          <type name="Magento\Framework\GraphQl\Query\ErrorHandler">
              <plugin name="km.graphql.errorHandler" type="Know\Module\Plugin\Graphql\Query\ErrorHandlerPlugin" />
          </type>
      </config>
      
      

      Now create the ErrorHandlerPlugin interceptor.

      Know/Module/Plugin/Graphql/Query/ErrorHandlerPlugin.php

      <?php
      namespace Know\Module\Plugin\Graphql\Query;
      
      class ErrorHandlerPlugin
      {
          public function afterHandle($subject, $result)
          {
              $handleResult = [];
              array_walk($result, function ($value, $key) use (&$handleResult) {
                  if (isset($value["message"])) {
                      $handleResult[$key]["message"] = $value["message"];
                  }
              });
      
              return $handleResult;
          }
      }
      
      

      Flush the cache or remove the generated or static content to see the changes reflected.

      The output can be similar to the screenshots below:

      Mutation:

      Default exception in mutation looks like:

      After Changes:

      Query:

      I hope this will help you.

Leave a Reply

Your email address will not be published. Required fields are marked *