Фреймворк Yii2 включает набор средств для упрощённой реализации RESTful API.
Основы работы с REST в Yii2 можно прочитать тут.

В данной статье практический пример реализации REST API на основе существующих в фреймворке средств.

В файле REST контроллера vendor/yiisoft/yii2/rest/ActiveController.php можно посмотреть какие действия и типы запросов используются:
protected function verbs()
{
    return [
        'index' => ['GET', 'HEAD'],
        'view' => ['GET', 'HEAD'],
        'create' => ['POST'],
        'update' => ['PUT', 'PATCH'],
        'delete' => ['DELETE'],
    ];
}

С этими действиями можно работать, например изменять, расширять, удалять и тд.

Для серьезного проекта с перспективой роста следует сразу определиться с архитектурой. Я предлагаю использовать такую структуру размещения каталогов и файлов API:



Данная структура похожа на указанную в разделе «Версионирование» документации Yii2: https://github.com/yiisoft/yii2/blob/master/docs/guide-ru/rest-versioning.md

Для примера буду использовать фреймворк версии «advanced».
Каталог «api» создается в корне проекта и представляет из себя отдельный раздел наряду с backend и frontend со своими конфигами, контроллерами, моделями и «точкой входа» - index.php. Таким образом нужно создать для него поддомен или же настроить перенаправление на WEB сервере, чтобы API открывалось, например, по ссылке: api.yoursite.com

Как видно из схемы, можно создать несколько версий API (v1, v2, v3...). Это делается для того, чтобы можно было создавать новый функционал, предоставлять данные в измененном виде и при этом пользователи которые уже используют ваше API могли дальше его использовать с теми же настройками и у них ничего не «ломалось».

Есть пример проекта использующего указанную структуру размещения файлов API: https://github.com/deerawan/yii2-advanced-api
который я брал за основу, но если будете его клонировать, то учтите, что он древний и сходу не заработает, нужно немного доработать. Поэтому я выкладываю в архиве
каталог «api» со всеми файлами, который совместим с текущей версией фреймворка Yii2 (2.0.15).

В архиве есть контроллер, модель и миграции указанного выше проекта (с демо данными) для демонстрации работы API, на базе которых можно создавать свои.
Таким образом, прежде всего нужно перенести папку «api» из архива в корень проекта. Далее перенести файлы миграций в папку с миграциями (console\migrations) и применить миграции.
Так же, желательно, дополнить папку environments проекта файлами из архива, чтобы после клонирования проекта и выполнения команды php init, локальные файлы типа «index.php», «main-local.php» и тд. создавались автоматически в своих папках. Если команда init уже выполнялась (т.е. API создается для уже действующего проекта), то на другом компьютере после команды git pull просто скопировать файлы из папки environments в соответствующие папки каталога «api».

Папка api подключается автоматически в автозагрузку классов фреймворка, т.к. в конфиге указан для нее алиас:
'aliases' => [
    '@api' => dirname(dirname(__DIR__)) . '/api',
],

Основной конфигурационный файл api/config/main.php уже полностью настроен для работы с тестовым API, поэтому, после проверки работы остается только изменить некоторые строки на свои. Так, в секции urlManager указываем свой REST контроллер:
'rules' => [
    [
        'class' => \yii\rest\UrlRule::class,
        'controller' => ['v1/country'],
        'prefix' => 'api',
    ]

В общем ничего сложного и вот уже вы можете тестировать работу REST API.
Получим список стран выполнив GET запрос без параметров:
http://YOUR_SITE/api/v1/countries

Результат:
<response>
<item>
<code>AU</code>
<country>Australia</country>
<population>18886000</population>
</item>
<item>
<code>BR</code>
<country>Brazil</country>
<population>170115000</population>
</item>
<item>
<code>CA</code>
<country>Canada</country>
<population>1147000</population>
</item>
...

Далее информация из практического применения.


Несколько версий (v1, v2…).


Если нужно будет сделать несколько версий, то просто создать в папке api/modules папку с новой версией, например «v2» со своим модулем и др. файлами по аналогии.
Подключить в api/config/main.php:
'modules' => [
    'v1' => [
        'basePath' => '@app/modules/v1',
        'class' => \api\modules\v1\Module::class,
    ],
    'v2' => [
        'basePath' => '@app/modules/v2',
        'class' => \api\modules\v2\Module::class,
    ]
],

…

'rules' => [
    [
        'class' => \yii\rest\UrlRule::class,
        'controller' => ['v1/country', 'v2/country'],
        'prefix' => 'api',
    ]
], 
где «country» - название вашего REST контроллера.


Добавляем свой метод/действие

'rules' => [
    [
        'class' => \yii\rest\UrlRule::class,
        'controller' => ['v1/country'],
        'prefix' => 'api', //api будет доступен по url, начинающимся с /api/
        'extraPatterns' => [
            'GET /' => 'new',
        ],
    ],

Тут указывается, что при вызове методом GET (без параметров) будет выполнен метод actionNew контроллера. Вызывать нужно во множественной форме, не country, а countries: http://YOUR_SITE/api/v1/countries
т.к. свойство 'pluralize' не определено в false.
Пример для других методов:
'extraPatterns' => [
'POST {id}/your_preferred_url' => 'xxxxx', // 'xxxxx' refers to 'actionXxxxx'
],

Пример своего действия REST контроллера:
public function actionNew()
{
    $requestParams = Yii::$app->getRequest()->getBodyParams();
    if (empty($requestParams)) {
        $requestParams = Yii::$app->getRequest()->getQueryParams();
    }

    /* @var $modelClass \yii\db\BaseActiveRecord */
    $modelClass = $this->modelClass;

    $query = $modelClass::find();
    if (!empty($filter)) {
        $query->andWhere($filter);
    }

    return Yii::createObject([
        'class' => ActiveDataProvider::className(),
        'query' => $query,
        'pagination' => [
            'params' => $requestParams,
        ],
        'sort' => [
            'params' => $requestParams,
        ],
    ]);
}

Тут я за основу взял метод prepareDataProvider из vendor/yiisoft/yii2/rest/IndexAction.php.
Метод возвращает объект ActiveDataProvider. Но можно в действии возвращать что угодно, например:
public function actionNew()
{
    $result = $this->modelClass::find()
        ->where(['>', 'population', 70000000])
        ->all();
    return $result;
}

Передаваемые данные сериализуются автоматически и пользователь получит ответ в формате JSON или XML.
Если данных может быть очень много стоит использовать ActiveDataProvider чтобы разбивать передачу на части (пагинация).


Переопределение стандартных действий REST

Стандартные действия (index, view, create и тд.) прописаны в vendor/yiisoft/yii2/rest/ActiveController.php

Например нужно переопределить метод index. Для этого в своем контроллере (типа api/modules/v1/controllers/CountryController.php) переопределяем метод actions:
public function actions()
{
    $actions = [
        'index' => [
            'class' => api\modules\v2\actions\IndexAction::class,
            'modelClass' => $this->modelClass,
            'checkAccess' => [$this, 'checkAccess'],
        ],
    ];
    
    return array_merge(parent::actions(), $actions);
} 
где указываем нужное действие и путь к его классу.
В переопределенном действии обязательным есть метод run. Пример api/modules/v1/actions/IndexAction.php:
<?php

namespace api\modules\v1\actions;

use Yii;
use yii\data\ActiveDataProvider;
use yii\rest\Action;


class IndexAction extends Action
{
    /**
     * @return ActiveDataProvider
     */
    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        return $this->prepareDataProvider();
    }

    /**
     * Prepares the data provider that should return the requested collection of the models.
     * @return ActiveDataProvider
     */
    protected function prepareDataProvider()
    {
        $requestParams = Yii::$app->getRequest()->getBodyParams();
        if (empty($requestParams)) {
            $requestParams = Yii::$app->getRequest()->getQueryParams();
        }

        /* @var $modelClass \yii\db\BaseActiveRecord */
        $modelClass = $this->modelClass;

        $query = $modelClass::find();
        if (!empty($filter)) {
            $query->andWhere($filter);
        }

        return Yii::createObject([
            'class' => ActiveDataProvider::className(),
            'query' => $query,
            'pagination' => [
                'params' => $requestParams,
            ],
            'sort' => [
                'params' => $requestParams,
            ],
        ]);

    }
}

Тут можно добавить какие-то фильтры в выборку из БД. Данные для фильтра можно получать из URL.


Так же можно удалить лишние действия или переопределить какое-то прямо в своем REST контроллере

public function actions()
{
    $actions = parent::actions();

    // disable the "delete" and "create" actions
    unset($actions['delete'], $actions['create']);

    // customize the data provider preparation with the "prepareDataProvider()" method
    $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];

    return $actions;
}

public function prepareDataProvider()
{
    // prepare and return a data provider for the "index" action
}

Добавить условие выборки тут же в методе actions

public function actions()
{
    $actions = parent::actions();
    $actions['index']['prepareDataProvider'] = function ($action) {
        return new ActiveDataProvider([
            'query' => $this->modelClass::find()->where('code=:code', [':code' => $_GET['code']]),
        ]);
    };

    return $actions;
}

Метод поиска/выборки (тут - search) по БД можно разместить и в самой модели, особенно если используются дополнительные условия выборки:
public function actions()
    {
        return [
            'index' => [
                'class' => 'yii\rest\IndexAction',
                'modelClass' => $this->modelClass,
                'prepareDataProvider' => function () {
                    $searchModel = new SearchModel();
                    return $searchModel->search(Yii::$app->request->queryParams);
                },
            ],
        ];
    }