Как создать REST API с помощью Laravel

С появлением мобильных веб-фреймворков и javascript, таких как React и Vue, популярность Restful API возросла. Это связано с тем, что вы можете поддерживать один серверный сервер, обслуживающий несколько интерфейсных клиентов. Laravel предоставляет хорошую среду и экосистему для создания вашего Rest API.

Пакеты сторонних производителей, такие как Laravel Passport и Laravel Sanctum, предоставляют реализацию аутентификации по API, упрощающую аутентификацию.

Laravel Breeze предоставляет начальные шаблоны, которые могут помочь с функциями сброса пароля.

Socialite и Scout предоставляют возможности входа в социальные сети и полнотекстового поиска.

Экосистема laravel предоставляет решения практически для всех проблем, с которыми вы можете столкнуться на пути разработки, обеспечивая тем самым максимальную производительность разработчику.

В этом руководстве будет рассмотрено, как создать Laravel Rest API с аутентификацией с помощью Laravel Sanctum.

Что такое Restful API?

REST расшифровывается как передача репрезентативного состояния и является шаблоном, используемым для обмена данными между приложениями через HTTP. Протокол не имеет состояния, что означает, что сеанс между приложениями не сохраняется. Таким образом, каждый запрос обрабатывается так, как если бы это был новый запрос, даже если он повторяется.

Преимущество REST API заключается в том, что их можно легко кэшировать. Ответ из Rest API легко кэшировать в таком сервисе, как Redis или Memcached, и, таким образом, его легко масштабировать.

Чтобы API считался Restful, он должен иметь следующее

  • Должен быть доступен по URL-адресу или конечной точке
  • Необходимо использовать любой из методов REST
  • Может иметь HTTP-заголовки
  • Должен возвращать действительный код ответа в каждом ответе.

Распространенными методами REST являются:

  • GET - Извлекать ресурсы из API
  • POST - Создание ресурса в API
  • PUT/PATCH - Обновление ресурса в API
  • DELETE - Удаляет ресурс из API

Реализация REST API в Laravel

Создайте новое приложение

Первым шагом является создание нового приложения Laravel.

laravel new rest

Модель и миграция

Следующим шагом является создание модели и соответствующего ей файла миграции.

Это действует как схема для нашей таблицы базы данных. В этом руководстве мы будем использовать продукты в качестве своего ресурса.

php artisan make:model Products -m

Флаг -m даст команду Laravel создать соответствующий файл миграции модели Products.

//App/Models/Products
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Products extends Model
{

    use HasFactory;

}

Сгенерированный файл миграции будет похож на приведенный ниже.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('products');
    }
};

Мы можем начать с обновления файла миграции, добавив больше столбцов в нашу базу данных. Добавим следующие столбцы: название продукта, цена продукта и описание продукта

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->double('price');
            $table->longText('description');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        Schema::dropIfExists('products');
    }
};

Затем мы можем обновить модель продуктов, зарегистрировав переменные, назначаемые по массе. Это помогает предотвратить внедрение SQL, предписывая laravel принимать только данные, содержащие указанные ключи/переменные.

//App/Models/Products
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Products extends Model
{
    use HasFactory;

    protected $fillable = [
        'name', 'price', 'description'
    ];
}

Последним шагом является настройка учетных данных базы данных в файле .env и создание базы данных

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-rest
DB_USERNAME=root
DB_PASSWORD=password

Заключительным шагом является миграция базы данных.

php artisan migrate

Создайте Database Seeder и фабрику

При разработке я предпочитаю использовать поддельные данные, чтобы обеспечить максимально быструю разработку. Laravel предоставляет удобный фабричный интерфейс, который позволяет нам использовать Faker для генерации фиктивных данных.

Мы можем запустить эту команду, чтобы создать фабрику

php artisan make:factory ProductsFactory

Это создаст файл в папке databases/factories

Мы можем обновить файл следующим образом

//database/factories/ProductsFactory
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Products>
 */
class ProductsFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition() {
        return [
            'name' => $this->faker->word,
            'price' => $this->faker->numberBetween(1, 99),
            'description' => $this->faker->sentence()
        ];
    }
}

Теперь, когда наша фабрика готова, мы можем вызвать ее в файле DatabaseSeeder для заполнения нашей базы данных.

//database/seeders/DatabaseSeeder
<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run() {
        \App\Models\Products::factory(10)->create();
    }
}

Теперь мы можем заполнить базу данных

php artisan db:seed

Создайте контроллер

Давайте теперь создадим контроллер, который будет содержать всю бизнес-логику для API.

php artisan make:controller ProductsController -r

Флаг -r сгенерирует контроллер, который является ресурсным. Это означает, что он создаст контроллер со всеми необходимыми методами для Restful API.

Основными методами, которые мы будем использовать, являются индексирование, отображение, хранение, обновление и уничтожение. Мы можем удалить методы создания и редактирования, поскольку они нам не понадобятся. Мы можем обновить контроллер продуктов, как показано ниже:

//App/Http/Controllers/ProductsController

<?php

namespace App\Http\Controllers;

use App\Http\Resources\ProductResource;
use App\Models\Products;
use Illuminate\Http\Request;

class ProductsController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index() {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request) {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param Products $product
     * @return \Illuminate\Http\Response
     */
    public function show(Products $product) {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param \Illuminate\Http\Request $request
     * @param Products $product
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Products $product) {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param Products $product
     * @return \Illuminate\Http\Response
     */
    public function destroy(Products $product) {
        //
    }
}

Эти методы могут быть сопоставлены с HTTP-глаголами по умолчанию (get, post, patch/put и delete).

Index (Получить все товары)

Мы можем использовать этот метод для возврата всех продуктов, присутствующих в базе данных

use App\Models\Products;
public function index() {
   return Products::all();
}

Мы можем дополнительно настроить его, разбив на страницы или кэшируя ответ из базы данных.

Show (Получить один товар)

Мы можем использовать этот метод для возврата единственного продукта, присутствующего в базе данных

Мы можем передать идентификатор продукта в качестве параметра, а затем извлечь его из базы данных

Примечание: Мы используем ресурсы API, которые мы рассмотрим позже в статье.
use App\Http\Resources\ProductResource;
use App\Models\Products;

public function show(Products $product) {
   return new ProductResource($product);
}

Store (Создать товар)

Мы можем создать новую запись о продукте в базе данных, используя этот метод. Метод post может быть применен к этому методу через конечную точку для создания записи

use App\Http\Resources\ProductResource;
use App\Models\Products;

public function store(Request $request) {
        $product_name = $request->input('name');
        $product_price = $request->input('price');
        $product_description = $request->input('description');

        $product = Products::create([
            'name' => $product_name,
            'price' => $product_price,
            'description' => $product_description,
        ]);
        return response()->json([
            'data' => new ProductResource($product)
        ], 201);
}

Update (Обновить сведения о продукте)

Чтобы обновить сведения о продукте, мы можем обновить логику метода обновления

use App\Http\Resources\ProductResource;
use App\Models\Products;

public function update(Request $request, Products $product) {
        $product_name = $request->input('name');
        $product_price = $request->input('price');
        $product_description = $request->input('description');

        $product->update([
            'name' => $product_name,
            'price' => $product_price,
            'description' => $product_description,
        ]);
        return response()->json([
            'data' => new ProductResource($product)
        ], 200);
}

Destroy (Удалить продукт)

Иногда вам может потребоваться удалить продукты из базы данных. Мы можем добавить следующий код для удаления продуктов из базы данных.

use App\Models\Products;

public function destroy(Products $product) {
   $product->delete();
   return response()->json(null,204);
}

Роуты и конечные точки

Давайте теперь создадим конечные точки, которые будут доступны по протоколу HTTP. Мы можем добавить маршруты в routes/api.php файл

//routes/api.php
<?php

use App\Http\Controllers\ProductsController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::get('products', [ProductsController::class, 'index'])->name('products.index');
Route::get('products/{product}', [ProductsController::class, 'show'])->name('products.show');
Route::post('products', [ProductsController::class, 'store'])->name('products.store');
Route::put('products/{product}', [ProductsController::class, 'update'])->name('products.update');
Route::delete('products/{product}', [ProductsController::class, 'destroy'])->name('products.destroy');

Эти конечные точки сопоставляются с методами в ProductsController, который мы создали ранее.

Теперь мы можем протестировать метод index с помощью запроса GET. Это вернет следующий ответ

[
    {
        "id": 1,
        "name": "quo",
        "price": 15,
        "description": "Ut rerum aut deleniti eveniet ad et ullam perferendis.",
        "created_at": "2022-11-18T15:18:13.000000Z",
        "updated_at": "2022-11-18T15:18:13.000000Z"
    },
    {
        "id": 2,
        "name": "maxime",
        "price": 70,
        "description": "Natus officiis repellat vero ea voluptatem mollitia similique.",
        "created_at": "2022-11-18T15:18:13.000000Z",
        "updated_at": "2022-11-18T15:18:13.000000Z"
    }
]

Форматирование Response(ответа)

Приведенный выше ответ возвращается в формате JSON. Он включает в себя сведения из базы данных с именами столбцов в качестве ключей.

Что, если вы хотите отформатировать этот ответ? Одна вещь, которую вы, возможно, не захотите раскрывать, - это данные created_at и updated_at. Возможно, вы также захотите рассчитать и вернуть предопределенную скидку обратно в качестве ответа.

Laravel позволяет нам настраивать наши ответы, используя ресурсы API.

В этом примере я буду предполагать, что на все товары действует скидка 10%. Поэтому я верну цену продукта и цену со скидкой как часть полезной нагрузки в ответе.

php artisan make:resource ProductResource

Это преобразует модель в массив.

//App/Http/Resources/ProductResource
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class ProductResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param \Illuminate\Http\Request $request
     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
     */
    public function toArray($request) {
        return [
            'id' => $this->id,
            'product_name' => $this->name,
            'product_price' => "$" . $this->price,
            'discounted_price' => "$" . ($this->price * 0.9),
            'discount' => "$" . ($this->price * 0.1),
            'product_description' => $this->description,
        ];
    }
}

Давайте обновим наш метод index в Products Controller, чтобы использовать ресурс Product

public function index()
{
   return ProductResource::collection(Products::all());
}

Это вернет заново отформатированный ответ

{
    "data": [
        {
            "id": 1,
            "product_name": "quo",
            "product_price": "$15",
            "discounted_price": "$13.5",
            "discount": "$1.5",
            "product_description": "Ut rerum aut deleniti eveniet ad et ullam perferendis."
        },
{
            "id": 2,
            "product_name": "maxime",
            "product_price": "$70",
            "discounted_price": "$63",
            "discount": "$7",
            "product_description": "Natus officiis repellat vero ea voluptatem mollitia similique."
        }
    ]
}

Коды ответов

Важно всегда возвращать действительный код ответа в каждом запросе. Эти коды ответов могут использоваться потребляющим приложением или клиентом, чтобы узнать, что именно произошло на стороне сервера.

Вот список наиболее распространенных кодов ответа

  • 200 – ок. Это означает, что это код успеха и это код ответа по умолчанию
  • 201- Создан. Это показывает, что ресурс был создан. Это полезно для POST-запросов.
  • 204 - Нет содержимого. Это означает, что действие было выполнено успешно, но содержимое не возвращено. Это полезно для запросов на удаление, поскольку удаленный ресурс не может вернуть какое-либо основное содержимое
  • 400 - Неверный запрос. Это означает, что клиент передал неверный запрос серверу
  • 401 - Несанкционированный доступ. Это означает, что клиент не авторизован для доступа к ресурсу. Обычно он используется в службах аутентификации и авторизации.
  • 403 - Запрещено. Это означает, что пользователь прошел проверку подлинности, но ему не разрешен доступ к ресурсу.
  • 404 - Не найден. Это означает, что ресурс не найден
  • 500 - Внутренняя ошибка сервера. Это означает, что на уровне сервера произошла ошибка

Laravel позволяет нам указать правильный код ответа, используя вспомогательную функцию response()->json(). Важно всегда возвращать код ответа, чтобы клиент /интерфейс мог отображать правильные данные и “корректно завершать работу” в случае ошибки.

response()->json(data,status code)

Области и Миддлвары

Области обычно используются в API-интерфейсах для присвоения возможностей токенам. Они помогают конкретизировать, что пользователь может и чего не может делать в системе.

Думайте о них как о разрешениях, которые вы предоставляете пользователям, как только подключаете их. Некоторые пользователи могут получить доступ к финансовым записям в системе, в то время как другие не могут.

С другой стороны, миддлвары используются для защиты конечных точек от несанкционированного доступа. Думайте о них как о шлюзе, который отфильтровывает действительные запросы от несанкционированных и вредоносных запросов. Они помогают защитить ваши конечные точки от доступа пользователей, которые не имеют к ним никакого отношения.

Мы можем добавить промежуточное программное обеспечение auth:sanctum к конечным точкам продукта, чтобы защитить их. Это означает, что для того, чтобы запрос считался действительным, он должен иметь заголовки Authorization: Bearer token (где токен - это фактический токен доступа, выданный API).

HEADERS {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer <Token>'
    }

Каждый запрос должен включать токен предъявителя в заголовки, иначе сервер ответит 401 (неавторизованный) ответом.

Группировка конечных точек

В большинстве случаев у вас может быть несколько конечных точек, которые имеют некоторые общие параметры, такие как версия (например, v1, v2), префиксы (admin) или промежуточное программное обеспечение.

Важно всегда обновлять свои API-интерфейсы, особенно если они используются общедоступно. Это помогает предотвратить критические изменения, а также обеспечивает обратную совместимость в случае появления новых API.

Мы можем сгруппировать эти конечные точки, используя метод Route::group

Route::group(['middleware' => ['auth:sanctum']], function () {

    //All Routes that share the auth:sanctum middleware
});

Таким образом, мы можем привести наш код в порядок и чистоту. В этом примере я сгруппирую маршруты так, чтобы они были версии 1, и использую промежуточное программное обеспечение auth:sanctum.

Возможно, мы также захотим уменьшить размер api.php файл, заставляя каждый ресурс использовать apiresource. Эта простая функция уменьшает размер файла почти на 60% и упрощает чтение и поддержку кода.

Route::apiResource('products', ProductsController::class);

Метод apiresource под капотом работает путем сопоставления основных функций (индексировать, показывать, сохранять, обновлять и удалять) в нашем контроллере с их различными конечными точками.

Например, products.show сопоставляется с api конечной api/products/{product}.

Вот несколько интересных приемов, которые вы можете использовать, чтобы оптимизировать свой опыт разработки и стать более продуктивным при разработке своих API.

Окончательный routes/api.php файл должен быть похож на приведенный ниже

//routes/api.php
<?php

use App\Http\Controllers\ProductsController;
use App\Http\Controllers\UserAuthenticationController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

//These routes are NOT protected using middleware

Route::prefix('v1')->group(function () {
    Route::post('login', [UserAuthenticationController::class, 'login']);
    Route::post('register', [UserAuthenticationController::class, 'register']);
});

//These routes are protected using middleware
Route::prefix('v1')->middleware('auth:sanctum')->group(function () {
    Route::post('logout', [UserAuthenticationController::class, 'logout']);
    Route::apiResource('products', ProductsController::class);
});

В заключение

В этой статье мы рассмотрели, что такое Rest API и как создать его в Laravel. Мы также рассмотрели аутентификацию по API и использование миддлвары для контроля доступа к вашему API. Мы надеемся, что эта статья была содержательной и помогла вам создать мощный REST API в laravel.