Skip to content
intermediate Phase 59 · REST Fundamentals

Service Contracts Exposed as API

Understanding how service contracts are exposed as REST API: defining API interfaces, webapi.xml configuration, and implementation

45m
0 problems
Topic Progress 0%

API Interface Definition

Basic API Interface

<?php
namespace Vendor\Module\Api;

use Vendor\Module\Api\Data\ItemDataInterface;

interface ItemRepositoryInterface
{
    /**
     * Get item by ID
     *
     * @param int $id
     * @return ItemDataInterface
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function getById(int $id): ItemDataInterface;
    
    /**
     * Get items list
     *
     * @param array $searchCriteria
     * @return \Magento\Framework\Api\SearchResultsInterface
     */
    public function getList(
        \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
    ): \Magento\Framework\Api\SearchResultsInterface;
    
    /**
     * Save item
     *
     * @param ItemDataInterface $item
     * @return ItemDataInterface
     */
    public function save(ItemDataInterface $item): ItemDataInterface;
    
    /**
     * Delete item
     *
     * @param ItemDataInterface $item
     * @return bool
     */
    public function delete(ItemDataInterface $item): bool;
}

Data Interface

<?php
namespace Vendor\Module\Api\Data;

interface ItemDataInterface
{
    const ID = 'id';
    const SKU = 'sku';
    const NAME = 'name';
    const PRICE = 'price';
    const STATUS = 'status';
    
    /**
     * @return int|null
     */
    public function getId(): ?int;
    
    /**
     * @param int $id
     * @return $this
     */
    public function setId(int $id);
    
    /**
     * @return string
     */
    public function getSku(): string;
    
    /**
     * @param string $sku
     * @return $this
     */
    public function setSku(string $sku);
    
    /**
     * @return string
     */
    public function getName(): string;
    
    /**
     * @param string $name
     * @return $this
     */
    public function setName(string $name);
}

Interface Location

app/code/Vendor/Module/Api/
├── ItemRepositoryInterface.php
├── ItemInterface.php
└── Data/
    └── ItemDataInterface.php

webapi.xml Configuration

Expose Interface via webapi.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
    <router url="/V1">
        <!-- GET /V1/items -->
        <route url="/items" method="get">
            <service class="Vendor\Module\Api\ItemRepositoryInterface" method="getList"/>
            <resources>
                <resource ref="anonymous"/>
            </resources>
        </route>
        
        <!-- GET /V1/items/:id -->
        <route url="/items/:id" method="get">
            <service class="Vendor\Module\Api\ItemRepositoryInterface" method="getById"/>
            <resources>
                <resource ref="anonymous"/>
            </resources>
        </route>
        
        <!-- POST /V1/items -->
        <route url="/items" method="post">
            <service class="Vendor\Module\Api\ItemRepositoryInterface" method="save"/>
            <resources>
                <resource ref="Magento_Backend::admin"/>
            </resources>
        </route>
        
        <!-- PUT /V1/items/:id -->
        <route url="/items/:id" method="put">
            <service class="Vendor\Module\Api\ItemRepositoryInterface" method="save"/>
            <resources>
                <resource ref="Magento_Backend::admin"/>
            </resources>
        </route>
        
        <!-- DELETE /V1/items/:id -->
        <route url="/items/:id" method="delete">
            <service class="Vendor\Module\Api\ItemRepositoryInterface" method="delete"/>
            <resources>
                <resource ref="Magento_Backend::admin"/>
            </resources>
        </route>
    </router>
</config>

di.xml Configuration

<config>
    <type name="Vendor\Module\Api\ItemRepositoryInterface">
        <plugin name="item_repository" type="Vendor\Module\Plugin\ItemRepositoryPlugin"/>
    </type>
</config>

Implementation

Repository Implementation

<?php
namespace Vendor\Module\Model;

use Vendor\Module\Api\ItemRepositoryInterface;
use Vendor\Module\Api\Data\ItemDataInterface;
use Vendor\Module\Model\ResourceModel\Item as ItemResource;
use Vendor\Module\Model\ItemFactory;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterfaceFactory;

class ItemRepository implements ItemRepositoryInterface
{
    public function __construct(
        private ItemResource $resource,
        private ItemFactory $itemFactory,
        private SearchResultsInterfaceFactory $searchResultsFactory,
    ) {
    }
    
    public function getById(int $id): ItemDataInterface
    {
        $item = $this->itemFactory->create();
        $this->resource->load($item, $id);
        
        if (!$item->getId()) {
            throw new \Magento\Framework\Exception\NoSuchEntityException(
                __('Item with ID %1 not found', $id)
            );
        }
        
        return $item;
    }
    
    public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface
    {
        $collection = $this->collectionFactory->create();
        // Apply search criteria
        // Return results
    }
    
    public function save(ItemDataInterface $item): ItemDataInterface
    {
        $this->resource->save($item);
        return $item;
    }
    
    public function delete(ItemDataInterface $item): bool
    {
        $this->resource->delete($item);
        return true;
    }
}

Data Object Implementation

<?php
namespace Vendor\Module\Model\Data;

use Vendor\Module\Api\Data\ItemDataInterface;
use Magento\Framework\Model\AbstractExtensibleModel;

class Item extends AbstractExtensibleModel implements ItemDataInterface
{
    public function getId(): ?int
    {
        return $this->getData(self::ID);
    }
    
    public function getSku(): string
    {
        return $this->getData(self::SKU);
    }
    
    public function setSku(string $sku)
    {
        return $this->setData(self::SKU, $sku);
    }
    
    public function getName(): string
    {
        return $this->getData(self::NAME);
    }
    
    public function setName(string $name)
    {
        return $this->setData(self::NAME, $name);
    }
}

Request/Response Handling

Request Body Example

{
    "item": {
        "sku": "SKU-123",
        "name": "Test Product",
        "price": 29.99,
        "status": 1
    }
}

Response Body Example

{
    "id": 1,
    "sku": "SKU-123",
    "name": "Test Product",
    "price": 29.99,
    "status": 1,
    "created_at": "2024-01-01 00:00:00",
    "updated_at": "2024-01-01 00:00:00"
}

Error Response

{
    "message": "Invalid item data",
    "errors": [
        {
            "message": "The SKU is required.",
            "field": "sku",
            "code": 0
        },
        {
            "message": "The price must be greater than 0.",
            "field": "price",
            "code": 0
        }
    ]
}

Search Criteria Response

{
    "items": [
        {
            "id": 1,
            "sku": "SKU-123",
            "name": "Product 1"
        },
        {
            "id": 2,
            "sku": "SKU-456",
            "name": "Product 2"
        }
    ],
    "search_criteria": {
        "request_name": "quick_search_container"
    },
    "total_count": 2
}

Validation

public function save(ItemDataInterface $item): ItemDataInterface
{
    $errors = $this->validate($item);
    
    if (!empty($errors)) {
        throw new \Magento\Framework\Exception\InputException(
            __('Invalid item data'),
            null,
            0,
            $errors
        );
    }
    
    $this->resource->save($item);
    return $item;
}

private function validate(ItemDataInterface $item): array
{
    $errors = [];
    
    if (empty($item->getSku())) {
        $errors[] = ['message' => 'SKU is required', 'field' => 'sku'];
    }
    
    if ($item->getPrice() <= 0) {
        $errors[] = ['message' => 'Price must be greater than 0', 'field' => 'price'];
    }
    
    return $errors;
}

Quiz

1. Where are API interfaces located?

Question 1 options

2. What maps interfaces to webapi.xml?

Question 2 options

3. What is the response format for lists?

Question 3 options

Flashcards

Question

Where are API interfaces?

Answer

In the Api/ directory

Question

What does webapi.xml map?

Answer

URL routes to service contract methods

Question

What is the response format?

Answer

JSON with data object properties

Question

How do you handle errors?

Answer

Throw InputException with error messages

Question

What is SearchResultsInterface?

Answer

Response format for list endpoints

Revision Notes

Key Takeaways

  • 1. API interfaces define available endpoints
  • 2. webapi.xml maps routes to interface methods
  • 3. Data interfaces define response structure
  • 4. Implementations handle business logic
  • 5. Error responses include validation messages

Interview Tips

  • Explain the service contract pattern
  • Know how to define API interfaces
  • Discuss request/response handling
  • Be ready to create a custom API

Cheat Sheet

Api/Interface.php - Define methods
Api/Data/Interface.php - Define data structure
etc/webapi.xml - Map routes to methods
Model/Repository.php - Implement interface
Model/Data.php - Implement data object