Adobe Commerce 2.3 reached end of support in September 2022.

Create a custom REST API

Although Adobe Commerce and Magento Open Source provide numerous REST endpoints, you might need to create your own to manage custom data within your extension. This tutorial describes how you can create such a custom REST API.

In this tutorial, we will create two custom endpoints to read or modify the product information. GET /V1/rest_dev/getProduct/{productId} returns details about a specified product, and PUT /V1/rest_dev/setDescription modifies product description data.

Step 1. Create a custom module

We will use Dev_RestApi as the namespace for this tutorial.

Create the module.xml and registration.php files to get started. About component file structure provides additional information.

app/code/Dev/RestApi/etc/module.xml:

1
2
3
4
5
6
7
8
9
<?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="Dev_RestApi">
        <sequence>
            <module name="Magento_Webapi" />
            <module name="Magento_Catalog" />
        </sequence>
    </module>
</config>

app/code/Dev/RestApi/registration.php:

1
2
3
4
5
6
7
<?php

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Dev_RestApi',
    __DIR__
);

Step 2. Create custom ACL entries

We must create Access Control List (ACL) rules for each endpoint. These rules give full control over who can access them.

Create the required ACL entries in app/code/Dev/RestApi/etc/acl.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Dev_RestApi::products" title="Dev API - Products"
                          translate="title" sortOrder="110">
                    <resource id="Dev_RestApi::products_get" title="Get product"
                              translate="title" sortOrder="10" />
                    <resource id="Dev_RestApi::products_set_description" title="Set description"
                              translate="title" sortOrder="20" />
                </resource>
            </resource>
        </resources>
    </acl>
</config>

Step 3. Define custom endpoints

To define endpoints, we need to configure a web API service as described in app/code/Dev/RestApi/etc/webapi.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
    <route url="/V1/rest_dev/getProduct/:id" method="GET">
        <service class="Dev\RestApi\Api\ProductRepositoryInterface" method="getItem" />
        <resources>
            <resource ref="Dev_RestApi::products_get" />
        </resources>
    </route>
    <route url="/V1/rest_dev/setDescription" method="PUT">
        <service class="Dev\RestApi\Api\ProductRepositoryInterface" method="setDescription" />
        <resources>
            <resource ref="Dev_RestApi::products_set_description" />
        </resources>
    </route>
</routes>

Where:

  • url is the URL of the endpoint. The full address would be <domain>/rest/<store_code>/<url>.
  • method defines the request method (GET, POST, PUT, DELETE).
  • service describes the interface and the method to be called when the endpoint is reached.
  • resource sets the ACL resource that is required to access the endpoint. If you want to make it public (no authentication), use: <resource ref="anonymous" />

Step 4. Create interfaces

In this example, we created the interfaces for the request in app/code/Dev/RestApi/Api/RequestItemInterface.php.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

namespace Dev\RestApi\Api;

interface RequestItemInterface
{
    const DATA_ID = 'id';
    const DATA_DESCRIPTION = 'description';

    /**
     * @return int
     */
    public function getId();

    /**
     * @return string
     */
    public function getDescription();

    /**
     * @param int $id
     * @return $this
     */
    public function setId(int $id);

    /**
     * @param string $description
     * @return $this
     */
    public function setDescription(string $description);
}

The interfaces for the response is in app/code/Dev/RestApi/Api/ResponseItemInterface.php.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php

namespace Dev\RestApi\Api;

interface ResponseItemInterface
{
    const DATA_ID = 'id';
    const DATA_SKU = 'sku';
    const DATA_NAME = 'name';
    const DATA_DESCRIPTION = 'description';

    /**
     * @return int
     */
    public function getId();

    /**
     * @return string
     */
    public function getSku();

    /**
     * @return string
     */
    public function getName();

    /**
     * @return string|null
     */
    public function getDescription();

    /**
     * @param int $id
     * @return $this
     */
    public function setId(int $id);

    /**
     * @param string $sku
     * @return $this
     */
    public function setSku(string $sku);

    /**
     * @param string $name
     * @return $this
     */
    public function setName(string $name);

    /**
     * @param string $description
     * @return $this
     */
    public function setDescription(string $description);
}

We define the ‘ProductRepositoryInterface’ in app/code/Dev/RestApi/Api/ProductRepositoryInterface.php.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace Dev\RestApi\Api;

interface ProductRepositoryInterface
{
    /**
     * Return a filtered product.
     *
     * @param int $id
     * @return \Dev\RestApi\Api\ResponseItemInterface
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function getItem(int $id);

    /**
     * Set descriptions for the products.
     *
     * @param \Dev\RestApi\Api\RequestItemInterface[] $products
     * @return void
     */
    public function setDescription(array $products);
}

Step 5. Create models

Models create classes that implement interfaces and process data.

In this example, we have created models to request, respond to, and process data. In each model, we have defined two methods: getItem, which provides product details of the given product ID, and setDescription which updates the description of the given product.

app/code/Dev/RestApi/Model/Api/RequestItem.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

namespace Dev\RestApi\Model\Api;

use Dev\RestApi\Api\RequestItemInterface;
use Magento\Framework\DataObject;

/**
 * Class RequestItem
 */
class RequestItem extends DataObject implements RequestItemInterface
{
    public function getId() : int
    {
        return $this->_getData(self::DATA_ID);
    }

    public function getDescription() : string
    {
        return $this->_getData(self::DATA_DESCRIPTION);
    }

    /**
     * @param int $id
     * @return $this
     */
    public function setId(int $id) : mixed
    {
        return $this->setData(self::DATA_ID, $id);
    }

    /**
     * @param string $description
     * @return $this
     */
    public function setDescription(string $description) : mixed
    {
        return $this->setData(self::DATA_DESCRIPTION, $description);
    }
}

app/code/Dev/RestApi/Model/Api/ResponseItem.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php

namespace Dev\RestApi\Model\Api;

use Dev\RestApi\Api\ResponseItemInterface;
use Magento\Framework\DataObject;

/**
 * Class ResponseItem
 */
class ResponseItem extends DataObject implements ResponseItemInterface
{
    public function getId() : int
    {
        return $this->_getData(self::DATA_ID);
    }

    public function getSku() : string
    {
        return $this->_getData(self::DATA_SKU);
    }

    public function getName() : string
    {
        return $this->_getData(self::DATA_NAME);
    }

    public function getDescription() : string
    {
        return $this->_getData(self::DATA_DESCRIPTION);
    }

    /**
     * @param int $id
     * @return $this
     */
    public function setId(int $id) : mixed
    {
        return $this->setData(self::DATA_ID, $id);
    }

    /**
     * @param string $sku
     * @return $this
     */
    public function setSku(string $sku) : mixed
    {
        return $this->setData(self::DATA_SKU, $sku);
    }

    /**
     * @param string $name
     * @return $this
     */
    public function setName(string $name) : mixed
    {
        return $this->setData(self::DATA_NAME, $name);
    }

    /**
     * @param string $description
     * @return $this
     */
    public function setDescription(string $description) : mixed
    {
        return $this->setData(self::DATA_DESCRIPTION, $description);
    }
}

app/code/Dev/RestApi/Model/Api/ProductRepository.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
<?php

namespace Dev\RestApi\Model\Api;

use Dev\RestApi\Api\ProductRepositoryInterface;
use Dev\RestApi\Api\RequestItemInterfaceFactory;
use Dev\RestApi\Api\ResponseItemInterfaceFactory;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\ResourceModel\Product\Action;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Model\StoreManagerInterface;

/**
 * Class ProductRepository
 */
class ProductRepository implements ProductRepositoryInterface
{
    /**
     * @var Action
     */
    private $productAction;

    /**
     * @var CollectionFactory
     */
    private $productCollectionFactory;

    /**
     * @var RequestItemInterfaceFactory
     */
    private $requestItemFactory;

    /**
     * @var ResponseItemInterfaceFactory
     */
    private $responseItemFactory;

    /**
     * @var StoreManagerInterface
     */
    private $storeManager;

    /**
     * @param Action $productAction
     * @param CollectionFactory $productCollectionFactory
     * @param RequestItemInterfaceFactory $requestItemFactory
     * @param ResponseItemInterfaceFactory $responseItemFactory
     * @param StoreManagerInterface $storeManager
     */
    public function __construct(
        Action $productAction,
        CollectionFactory $productCollectionFactory,
        RequestItemInterfaceFactory $requestItemFactory,
        ResponseItemInterfaceFactory $responseItemFactory,
        StoreManagerInterface $storeManager
    ) {
        $this->productAction = $productAction;
        $this->productCollectionFactory = $productCollectionFactory;
        $this->requestItemFactory = $requestItemFactory;
        $this->responseItemFactory = $responseItemFactory;
        $this->storeManager = $storeManager;
    }

    /**
     * @inheritDoc
     *
     * @param int $id
     * @return ResponseItemInterface
     * @throws NoSuchEntityException
     */
    public function getItem(int $id) : mixed
    {
        $collection = $this->getProductCollection()
            ->addAttributeToFilter('entity_id', ['eq' => $id]);

        /** @var ProductInterface $product */
        $product = $collection->getFirstItem();
        if (!$product->getId()) {
            throw new NoSuchEntityException(__('Product not found'));
        }

        return $this->getResponseItemFromProduct($product);
    }

    /**
     * @inheritDoc
     *
     * @param RequestItemInterface[] $products
     * @return void
     */
    public function setDescription(array $products) : void
    {
        foreach ($products as $product) {
            $this->setDescriptionForProduct(
                $product->getId(),
                $product->getDescription()
            );
        }
    }

    /**
     * @return Collection
     */
    private function getProductCollection() : mixed
    {
        /** @var Collection $collection */
        $collection = $this->productCollectionFactory->create();

        $collection
            ->addAttributeToSelect(
                [
                    'entity_id',
                    ProductInterface::SKU,
                    ProductInterface::NAME,
                    'description'
                ],
                'left'
            );

        return $collection;
    }

    /**
     * @param ProductInterface $product
     * @return ResponseItemInterface
     */
    private function getResponseItemFromProduct(ProductInterface $product) : mixed
    {
        /** @var ResponseItemInterface $responseItem */
        $responseItem = $this->responseItemFactory->create();

        $responseItem->setId($product->getId())
            ->setSku($product->getSku())
            ->setName($product->getName())
            ->setDescription($product->getDescription());

        return $responseItem;
    }

    /**
     * Set the description for the product.
     *
     * @param int $id
     * @param string $description
     * @return void
     */
    private function setDescriptionForProduct(int $id, string $description) : void
    {
        $this->productAction->updateAttributes(
            [$id],
            ['description' => $description],
            $this->storeManager->getStore()->getId()
        );
    }
}

Step 6. Test your custom endpoint

  • You can use any REST client to send calls. Postman is recommended.
  • Obtain an admin authorization token. All calls in this tutorial require administrator privileges. See Generate the admin token for more information.

Test the GET endpoint

The example uses Magento sample data for product_id: 1 and the endpoint http://local.magentoee.com/rest/V1/rest_dev/getProduct/1.

Request:

GET <domain>rest/V1/<store_code>/rest_dev/getProduct/<product_id>

Response:

1
2
3
4
5
6
{
    "id": 1,
    "sku": "24-MB01",
    "name": "Joust Duffle Bag",
    "description": "<p>The sporty Joust Duffle Bag can't be beat - not in the gym, not on the luggage carousel, not anywhere. Big enough to haul a basketball or soccer ball and some sneakers with plenty of room to spare, it's ideal for athletes with places to go.<p>\n<ul>\n<li>Dual top handles.</li>\n<li>Adjustable shoulder strap.</li>\n<li>Full-length zipper.</li>\n<li>L 29\" x W 13\" x H 11\".</li>\n</ul>"
}

Test the PUT endpoint

Request:

PUT <domain>/rest/V1/<store_code>/rest_dev/setDescription

Payload:

1
2
3
4
5
6
7
8
{
   "products":[
      {
         "id":2,
         "description":"Test description"
      }
   ]
}

Response:

[]