Skip to content
intermediate Phase 62 · GraphQL Advanced

Custom GraphQL Module

Creating custom GraphQL module in Magento 2: schema.graphqls, resolver class, and type definitions

1h
0 problems
Topic Progress 0%

Module Structure

Custom GraphQL Module

app/code/Vendor/CustomGraphQl/
├── registration.php
├── etc/
│   ├── module.xml
│   ├── schema.graphqls
│   └── di.xml
├── GraphQl/
│   ├── Resolver/
│   │   ├── Query/
│   │   │   ├── ItemResolver.php
│   │   │   └── ItemListResolver.php
│   │   └── Mutation/
│   │       ├── CreateItemResolver.php
│   │       ├── UpdateItemResolver.php
│   │       └── DeleteItemResolver.php
│   └── Model/
│       └── Data/
│           └── Item.php
├── Api/
│   ├── ItemRepositoryInterface.php
│   └── Data/
│       └── ItemDataInterface.php
└── Model/
    ├── ItemRepository.php
    └── ResourceModel/
        └── Item.php

registration.php

<?php
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Vendor_CustomGraphQl',
    __DIR__
);

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="Vendor_CustomGraphQl" setup_version="1.0.0">
        <sequence>
            <module name="Magento_GraphQl"/>
        </sequence>
    </module>
</config>

Schema Definition

schema.graphqls

# Query type
type Query {
    # Get single item by ID
    customItem(id: ID!): CustomItem
    
    # Get items list
    customItems(
        filter: CustomItemFilterInput
        sort: CustomItemSortInput
        pageSize: Int
        currentPage: Int
    ): CustomItemSearchResult!
}

# Mutation type
type Mutation {
    # Create new item
    createCustomItem(
        input: CreateCustomItemInput!
    ): CustomItem!
    
    # Update existing item
    updateCustomItem(
        input: UpdateCustomItemInput!
    ): CustomItem!
    
    # Delete item
    deleteCustomItem(
        input: DeleteCustomItemInput!
    ): Boolean!
}

# Object types
type CustomItem {
    id: ID!
    name: String!
    email: String!
    status: CustomItemStatus!
    description: String
    created_at: DateTime
    updated_at: DateTime
}

type CustomItemStatus {
    code: String!
    label: String!
}

type CustomItemSearchResult {
    items: [CustomItem]!
    total_count: Int
    page_info: PageInfo
}

type PageInfo {
    page_size: Int
    current_page: Int
    total_pages: Int
}

# Input types
input CreateCustomItemInput {
    name: String!
    email: String!
    description: String
    status: String
}

input UpdateCustomItemInput {
    id: ID!
    name: String
    email: String
    description: String
    status: String
}

input DeleteCustomItemInput {
    id: ID!
}

input CustomItemFilterInput {
    name: FilterMatchTypeInput
    email: FilterEqualTypeInput
    status: FilterEqualTypeInput
}

input CustomItemSortInput {
    name: SortOrder
    created_at: SortOrder
}

# Enum
enum SortOrder {
    ASC
    DESC
}

Query Resolvers

Single Item Resolver

<?php
namespace Vendor\CustomGraphQl\GraphQl\Resolver\Query;

use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Resolver\ResolverInterface;
use Vendor\CustomGraphQl\Api\ItemRepositoryInterface;

class ItemResolver implements ResolverInterface
{
    public function __construct(
        private ItemRepositoryInterface $itemRepository,
    ) {
    }
    
    public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        array $value = null,
        array $args = null
    ) {
        $id = (int) $args['id'];
        
        $item = $this->itemRepository->getById($id);
        
        return [
            'id' => $item->getId(),
            'name' => $item->getName(),
            'email' => $item->getEmail(),
            'status' => [
                'code' => $item->getStatus(),
                'label' => $this->getStatusLabel($item->getStatus())
            ],
            'description' => $item->getDescription(),
            'created_at' => $item->getCreatedAt(),
            'updated_at' => $item->getUpdatedAt()
        ];
    }
    
    private function getStatusLabel(string $code): string
    {
        $statuses = [
            'active' => 'Active',
            'inactive' => 'Inactive'
        ];
        
        return $statuses[$code] ?? $code;
    }
}

List Resolver

<?php
namespace Vendor\CustomGraphQl\GraphQl\Resolver\Query;

use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Resolver\ResolverInterface;
use Vendor\CustomGraphQl\Api\ItemRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;

class ItemListResolver implements ResolverInterface
{
    public function __construct(
        private ItemRepositoryInterface $itemRepository,
        private SearchCriteriaBuilder $searchCriteriaBuilder,
    ) {
    }
    
    public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        array $value = null,
        array $args = null
    ) {
        $searchCriteria = $this->searchCriteriaBuilder->create();
        
        // Apply filters
        if (isset($args['filter'])) {
            $this->applyFilters($searchCriteria, $args['filter']);
        }
        
        // Apply sorting
        if (isset($args['sort'])) {
            $this->applySorting($searchCriteria, $args['sort']);
        }
        
        // Apply pagination
        $pageSize = $args['pageSize'] ?? 20;
        $currentPage = $args['currentPage'] ?? 1;
        $searchCriteria->setPageSize($pageSize);
        $searchCriteria->setCurrentPage($currentPage);
        
        $searchResult = $this->itemRepository->getList($searchCriteria);
        
        $items = [];
        foreach ($searchResult->getItems() as $item) {
            $items[] = $this->formatItem($item);
        }
        
        return [
            'items' => $items,
            'total_count' => $searchResult->getTotalCount(),
            'page_info' => [
                'page_size' => $pageSize,
                'current_page' => $currentPage,
                'total_pages' => ceil($searchResult->getTotalCount() / $pageSize)
            ]
        ];
    }
}

Mutation Resolvers

Create Resolver

<?php
namespace Vendor\CustomGraphQl\GraphQl\Resolver\Mutation;

use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Resolver\ResolverInterface;
use Vendor\CustomGraphQl\Api\ItemRepositoryInterface;
use Vendor\CustomGraphQl\Api\Data\ItemDataInterfaceFactory;

class CreateItemResolver implements ResolverInterface
{
    public function __construct(
        private ItemRepositoryInterface $itemRepository,
        private ItemDataInterfaceFactory $itemFactory,
    ) {
    }
    
    public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        array $value = null,
        array $args = null
    ) {
        $input = $args['input'];
        
        $item = $this->itemFactory->create();
        $item->setName($input['name']);
        $item->setEmail($input['email']);
        $item->setDescription($input['description'] ?? null);
        $item->setStatus($input['status'] ?? 'active');
        
        $savedItem = $this->itemRepository->save($item);
        
        return [
            'id' => $savedItem->getId(),
            'name' => $savedItem->getName(),
            'email' => $savedItem->getEmail(),
            'status' => [
                'code' => $savedItem->getStatus(),
                'label' => $this->getStatusLabel($savedItem->getStatus())
            ],
            'created_at' => $savedItem->getCreatedAt()
        ];
    }
}

Update Resolver

public function resolve(
    Field $field,
    $context,
    ResolveInfo $info,
    array $value = null,
    array $args = null
) {
    $input = $args['input'];
    $id = (int) $input['id'];
    
    $item = $this->itemRepository->getById($id);
    
    if (isset($input['name'])) {
        $item->setName($input['name']);
    }
    if (isset($input['email'])) {
        $item->setEmail($input['email']);
    }
    if (isset($input['description'])) {
        $item->setDescription($input['description']);
    }
    if (isset($input['status'])) {
        $item->setStatus($input['status']);
    }
    
    $savedItem = $this->itemRepository->save($item);
    
    return $this->formatItem($savedItem);
}

Delete Resolver

public function resolve(
    Field $field,
    $context,
    ResolveInfo $info,
    array $value = null,
    array $args = null
) {
    $id = (int) $args['input']['id'];
    
    $item = $this->itemRepository->getById($id);
    $this->itemRepository->delete($item);
    
    return true;
}

Quiz

1. Where is the GraphQL schema defined?

Question 1 options

2. What must resolvers implement?

Question 2 options

3. How do you define a query in schema?

Question 3 options

Flashcards

Question

Where is the schema file?

Answer

etc/schema.graphqls

Question

What interface do resolvers implement?

Answer

ResolverInterface

Question

How do you define queries?

Answer

type Query { fieldName: ReturnType }

Question

How do you define mutations?

Answer

type Mutation { fieldName(args): ReturnType }

Question

How do you register resolvers?

Answer

In schema.graphqls with resolver attribute

Revision Notes

Key Takeaways

  • 1. schema.graphqls defines types and operations
  • 2. Resolvers implement ResolverInterface
  • 3. Queries read data, mutations write data
  • 4. Input types define mutation arguments
  • 5. Register resolvers in schema.graphqls

Interview Tips

  • Know the module structure for GraphQL
  • Understand schema definition patterns
  • Be ready to create resolvers
  • Discuss error handling in mutations

Cheat Sheet

Module structure:
  etc/schema.graphqls - Schema
  GraphQl/Resolver/Query/ - Query resolvers
  GraphQl/Resolver/Mutation/ - Mutation resolvers

Schema:
  type Query { item(id: ID!): Item }
  type Mutation { createItem(input: Input!): Item }

Resolver:
  implements ResolverInterface
  resolve(field, context, info, value, args)