Как создать RESTful API Laravel
Опубликовано: 01.09.2018
REST stands for REpresentational State Transfer и представляет собой архитектурное решение для сетевого взаимодействия между приложениями.
HTTP Action.
В API RESTful мы используем HTTP-action как действия, а конечными точками являются ресурсы, на которые воздействуют. Мы будем использовать HTTP-action по их семантическому значению:
POST : создать
GET : получить PUT : обновить DELETE : удалить
Build a RESTful API with Laravel - Pluralsight Course Announcement
CreateReadUpdateDelete ( CRUD ) — эта аббревиатура частенько может попадаться в статьях.
Update Action: PUT vs. POST
Много копий сломано в спорах API RESTful — о том, лучше ли обновлять с помощью POST, PATCH, или PUT.
В этой статье мы будем использовать PUT для действия обновления, так как согласно HTTP RFC, PUT означает создание / обновление ресурса в определенном месте.
Rest Api laravel 5.3 Básico
Краткое отступление: те, кто любит читать, может и далее впитывать знания полнотекстово.
Для киноманов — предоставляю видео тематично статье, но не по содержанию.
Ресурсы
Ресурсы станут целями действий, в нашем случае «Статьи и пользователи», и у них есть свои конечные точки:
/articles
/usersВ этом уроке о api laravel ресурсы будут иметь представление 1:1 для наших моделей данных, но это не является обязательным. Вы можете иметь ресурсы, представленные более чем в одной модели данных (или вообще не представленные в базе данных). В конце концов, вы решаете, как спроектировать ресурсы и модели таким образом, который наиболее подходит вашему приложению.
Пропустим момент установки Laravel 5.4, а заодно и как выбрать хостинг , и перейдем прямо к сути.Создадим модель Article.
Запустим терминал CTRL+ALT+T
перейдем в каталог со свежеустановленным Laravel
cd /path/to/my/projectи введем команду
php artisan make:model Article -mThe опция -m кратко от —migration скажет Artisan создать модель Article и создать миграцию для нее.
use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateArticlesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('articles'); } }Немного подробнее:
The up() and down() — методы будут выполняться при миграции и ее откате; $table->increments('id') установит автоинкремент для поля id; $table->timestamps() установит timestamps для полей —created_at и updated_at, и можно не беспокоиться о дефолтных значениях, Laravel их внесет по мере необходимости. И наконец, Schema::dropIfExists() удалит таблицу, если такая существует.На основе вышесказанного давайте добавим в метод up() следующие строки:
public function up() { Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->text('body'); $table->timestamps(); }); }string() метод создаст VARCHAR столбец, а text() создаст TEXT.
После этого:
php artisan migrateLaravel, из коробки поставляется с двумя миграциями create_users_table и create_password_resets_table.
Мы не будем использовать миграцию password_resets table, but having the users.
Вернемся к нашей модели и добавим атрибуты $fillable чтобы мы могли их использовать в методах Article::create and Article::update нашей модели:
class Article extends Model { protected $fillable = ['title', 'body']; }Поля с атрибутом $fillable доступны для массового заполнения.
Database Seeding
Не будем останавливаться на создании сидирования(автозаполнения) таблиц данными, об этом подробнее можно прочесть здесь .
Routes and Controllers (маршруты и контроллеры)
Давайте создадим конечные точки нашего приложения: создание, получение списка, получение единичной сущности(сингл), обновление, и удаление. В routes/api.php, напишем:
Use App\Article; Route::get('articles', function() { // If the Content-Type and Accept headers are set to 'application/json', // this will return a JSON structure. This will be cleaned up later. return Article::all(); }); Route::get('articles/{id}', function($id) { return Article::find($id); }); Route::post('articles', function(Request $request) { return Article::create($request->all); }); Route::put('articles/{id}', function(Request $request, $id) { $article = Article::findOrFail($id); $article->update($request->all()); return $article; }); Route::delete('articles/{id}', function($id) { Article::find($id)->delete(); return 204; })Маршруты внутри api.php будут иметь префикс / api /, и API Middleware будет автоматически применяться к ним.
Давайте переместим наш код в Controller (Контроллер):
$ php artisan make:controller ArticleControllerArticleController.php:
use App\Article; class ArticleController extends Controller { public function index() { return Article::all(); } public function show($id) { return Article::find($id); } public function store(Request $request) { return Article::create($request->all()); } public function update(Request $request, $id) { $article = Article::findOrFail($id); $article->update($request->all()); return $article; } public function delete(Request $request, $id) { $article = Article::findOrFail($id); $article->delete(); return 204; } }Изменим routes/api.php:
Route::get('articles', ' [email protected] '); Route::get('articles/{id}', ' [email protected] '); Route::post('articles', ' [email protected] '); Route::put('articles/{id}', ' [email protected] '); Route::delete('articles/{id}', ' [email protected] ');Мы можем улучшить наши конечные точки, используя неявные привязки маршрутов.
Route::get('articles', ' [email protected] '); Route::get('articles/{article}', ' [email protected] '); Route::post('articles', ' [email protected] '); Route::put('articles/{article}', ' [email protected] '); Route::delete('articles/{article}', ' [email protected] '); class ArticleController extends Controller { public function index() { return Article::all(); } public function show(Article $article) { return $article; } public function store(Request $request) { $article = Article::create($request->all()); return response()->json($article, 201); } public function update(Request $request, Article $article) { $article->update($request->all()); return response()->json($article, 200); } public function delete(Article $article) { $article->delete(); return response()->json(null, 204); } }Примечание к кодам HTTP статусов и Формату ответа(Responce)
Мы также добавили response()->json() к вызову наших конечных точек. Это позволит нам возвращать явно JSON data а также HTTP код, который будет обрабатываться клиентом.
Общий список кодов:
200: OK. Стандартный код успешного ответа. 201: Объект создан. Полезен при работе с хранилищем(магазином). 204: Отсутствует контент. Когда действие выполнено успешно, но не возвращен контент. 206: Частичный контент. Используется когда вы возвращаете контент постранично(пагинация). 400: Bad request. Стандартная опция для запросов которые не прошли валидацию. 401: Unauthorized. Пользователь не прошел авторизацию. 403: Forbidden. Пользователь авторизован, но у него не хватает прав для выполнения запроса. 404: Not found. возвращается Laravel автоматически когда запрошенный ресурс не найден. 500: Internal server error. В идеале, вы не должны возвращать такой ответ, но когда, что то неожиданно сбоит, то пользователь получит такой ответ. 503: Service unavailable. Довольно понятно, но тоже, код который не будет явно возращен приложением.Отправка корректного 404
Если попытаетесь запросить несуществующий ресурс, то вы получите:
Мы можем исправить это отредактировав исключение нашего класса, расположенного в app/Exceptions/Handler.php, которое вернет JSON:
public function render($request, Exception $exception) { // This will replace our 404 response with // a JSON response. if ($exception instanceof ModelNotFoundException) { return response()->json([ 'error' => 'Resource not found' ], 404); } return parent::render($request, $exception); }Результатом будет:
{ data: "Resource not found" }Если вы используете Laravel для обслуживания других страниц, вы должны отредактировать код для работы с Accept header, в противном случае ошибки 404 из регулярных запросов будут возвращены с таким же успехом.
public function render($request, Exception $exception) { // This will replace our 404 response with // a JSON response. if ($exception instanceof ModelNotFoundException && $request->wantsJson()) { return response()->json([ 'data' => 'Resource not found' ], 404); } return parent::render($request, $exception); }В этом случае, API запросам будет нужен header Accept: application/json.
Аутентификация
Существует много способов аутентификации в Laravel, но статья не об этом, поэтому будем использовать упрощенную.
Для начала нужно добавить поле api_token в таблицу users :
$ php artisan make:migration --table=users adds_api_token_to_users_tableНе забудем о миграции:
public function up() { Schema::table('users', function (Blueprint $table) { $table->string('api_token', 60)->unique()->nullable(); }); } public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn(['api_token']); }); }Создание Регистрации пользователя
Будем использовать RegisterController (в папке Auth ) , который вернет правильный ответ после регистрации пользователя. Он также доступен из коробки, но до сих пор нуждается в доработке, чтобы вернул ответ, который нам нужен.
Контроллер использует Трейт RegistersUsers реализующий регистрацию
public function register(Request $request) { // Here the request is validated. The validator method is located // inside the RegisterController, and makes sure the name, email // password and password_confirmation fields are required. $this->validator($request->all())->validate(); // A Registered event is created and will trigger any relevant // observers, such as sending a confirmation email or any // code that needs to be run as soon as the user is created. event(new Registered($user = $this->create($request->all()))); // After the user is created, he's logged in. $this->guard()->login($user); // And finally this is the hook that we want. If there is no // registered() method or it returns null, redirect him to // some other URL. In our case, we just need to implement // that method to return the correct response. return $this->registered($request, $user) ?: redirect($this->redirectPath()); }Мы просто включим метод registered() в наш RegisterController. Метод принимает $request и $user, и это как раз то что нам нужно. Вот так метод выглядит внутри контроллера:
protected function registered(Request $request, $user) { $user->generateToken(); return response()->json(['data' => $user->toArray()], 201); }Мы можем подключить его в файле routes
Route::post(register, 'Auth\ [email protected] );В приведенном выше разделе мы использовали метод модели User для генерации токена. Это полезно, так что у нас есть только один способ генерации токенов. Добавьте в свою модель пользователя следующий метод:
class User extends Authenticatable { ... public function generateToken() { $this->api_token = str_random(60); $this->save(); return $this->api_token; } }И все.
Вот что мы получим обращаясь к конечной точке
$ curl -X POST http://localhost:8000/api/register \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"name": "John", "email": " [email protected] ", "password": "toptal123", "password_confirmation": "toptal123"}' { "data": { "api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZYo5o2ilZPnohvndH797nDNyAT", "created_at": "2017-06-20 21:17:15", "email": " [email protected] ", "id": 51, "name": "John", "updated_at": "2017-06-20 21:17:15" } }Создание Login
Также как и в случае с регистрацией, мы можем отредактировать LoginController (в папке Auth ) для поддержки API аутентификации. Метод login в трейте AuthenticatesUsers может быть переопределен для поддержки нашей API:
public function login(Request $request) { $this->validateLogin($request); if ($this->attemptLogin($request)) { $user = $this->guard()->user(); $user->generateToken(); return response()->json([ 'data' => $user->toArray(), ]); } return $this->sendFailedLoginResponse($request); }И также подключим его в routes
Route::post('login', 'Auth\ [email protected] ');Теперь, предположим, что пользователи у нас существуют
$ curl -X POST localhost:8000/api/login \ -H "Accept: application/json" \ -H "Content-type: application/json" \ -d "{\"email\": \" [email protected] \", \"password\": \"toptal\" }" { "data": { "id":1, "name":"Administrator", "email":" [email protected] ", "created_at":"2017-04-25 01:05:34", "updated_at":"2017-04-25 02:50:40", "api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw" } }Logging Out
В соответствии с нашей текущей стратегией, если токен ошибочен или отсутствует, пользователь должен получить не прошедший валидацию ответ. Поэтому для мы отправим токен, и он будет удален в базе данных.
routes/api.php: Auth\LoginController.php: public function logout(Request $request) { $user = Auth::guard('api')->user(); if ($user) { $user->api_token = null; $user->save(); } return response()->json(['data' => 'User logged out.'], 200);Используя эту стратегию, любой токен, который есть у пользователя будет недействительным, и API откажет в доступе (используя middlewares, как описано в следующем разделе). Для этого необходимо согласование с клиентской частью(front-end), чтобы пользователь оставался залогиненым, и не имеющим доступа к какому-либо контенту.
Использование Middlewares(Посредников) для запрета доступа.
С созданием api_token, мы можем переключать middleware в файле route :
Route::middleware('auth:api') ->get('/user', function (Request $request) { return $request->user(); });Мы можем управлять доступом текущего пользователя используя метод $request->user() иил посредством фасада Auth
Auth::guard('api')->user(); // instance of the logged user Auth::guard('api')->check(); // if a user is authenticated Auth::guard('api')->id(); // the id of the authenticated userИ получим результат подобный этому:
Это связано с тем, что нам нужно отредактировать текущий unauthenticated метод в нашем Handler class. Текущая версия возвратит JSON только если запрос будет иметь заголовок Accept: application/json давайте изменим это:
protected function unauthenticated($request, AuthenticationException $exception) { return response()->json(['error' => 'Unauthenticated'], 401); }Изменив это, можно изменить наши конечные точки статей, обернув их auth:api middleware. Это можно сделать посредством route groups:
Route::group(['middleware' => 'auth:api'], function() { Route::get('articles', ' [email protected] '); Route::get('articles/{article}', ' [email protected] '); Route::post('articles', ' [email protected] '); Route::put('articles/{article}', ' [email protected] '); Route::delete('articles/{article}', ' [email protected] '); });Таким образом нам не нужно прописывать middleware для каждого маршрута(Route).
В следующей статье Laravel RESTful API. Тестирование я опишу как тестировать наши конечные точки(endpoints).
Оригинал статьи Laravel API Tutorial: How to Build and Test a RESTful API