inb4: copy-paste from documentation
The guide focuses on the rapid deployment of a minimum set for a full-fledged API development in accordance with best practice, taken from the Laravel 5.7 documentation collected in one place. I wrote for myself and my colleagues as a cheat sheet, I hope it will be useful to someone else.
composer create-project --prefer-dist laravel/laravel scaffold-api
php artisan preset none
Go to the folder, edit the .env file:
DB_CONNECTION=mysql DB_HOST=localhost DB_PORT=3306 DB_DATABASE=api-authentification DB_USERNAME=root DB_PASSWORD=
Perform in consolephp artisan make:model Game -mrc
We get the model, migration and controller:
Model created successfully. Factory created successfully. Created Migration: 2019_02_27_105610_create_games_table Controller created successfully.
Rule migration by adding columns to the table. The most commonly used types are:
increments('id')
string('title')
text('description')
tinyInteger('complexity')
boolean('isActive')
softDeletes()
For optional fields, do not forget to add a default value using ->default()
Apply migrations by performing php artisan migrate
php artisan make:request GameRequest
Open the App/Http/Requests/GameRequest.php
.
In the authorize()
method, set return true
until we add authorization.
The array that is returned in the rules()
method describes the rules for all the columns we listed in the migration. Available rules here
To minimize the code, we use the switch construct for different http verbs, instead of making separate StoreGameRequest, UpdateGameRequest, etc.
public function rules(Request $request) { $rules = [ 'title' => 'required|string|unique:games,title', 'description' => '', 'complexity' => 'required|min:1|max:10', 'minPlayers' => 'required|min:1|max:10', 'maxPlayers' => 'required|min:1|max:10', 'isActive' => 'required|boolean' ]; switch ($this->getMethod()) { case 'POST': return $rules; case 'PUT': return [ 'game_id' => 'required|integer|exists:games,id', // . : unique:games,id,' . $this->route('game'), 'title' => [ 'required', Rule::unique('games')->ignore($this->title, 'title') // , ] ] + $rules; // // case 'PATCH': case 'DELETE': return [ 'game_id' => 'required|integer|exists:games,id' ]; } }
If you need your own error texts, override the messages () method, which returns an array with translations of each rule:
public function messages() { return [ 'date.required' => 'A date is required', 'date.date_format' => 'A date must be in format: Ym-d', 'date.unique' => 'This date is already taken', 'date.after_or_equal' => 'A date must be after or equal today', 'date.exists' => 'This date doesn\'t exists', ]; }
To ensure that not only the parameters passed in the request body, but also the parameters passed to the URL are available in the validation rules, we override the all method (which is usually used in the controller as $ request-> all ()):
public function all($keys = null) { // return $this->all(); $data = parent::all($keys); switch ($this->getMethod()) { // case 'PUT': // case 'PATCH': case 'DELETE': $data['date'] = $this->route('day'); } return $data; }
Open Http\Controllers\GameController
. We delete the generated create(), edit()
methods for rendering forms (since we have REST API, they are not needed).
Replace standard use Illuminate\Http\Request;
on our use App\Http\Requests\GameRequest;
Next, we rule the methods:
public function index() { return Game::all(); }
public function store(GameRequest $request) { $day = Game::create($request->validated()); return $day; }
public function show(Game $game) { return $game = Game::findOrFail($game); }
public function update(GameRequest $request, $id) { $game = Game::findOrFail($id); $game->fill($request->except(['game_id'])); $game->save(); return response()->json($game); }
public function destroy(GameRequest $request, $id) { $game = Game::findOrFail($id); if($game->delete()) return response(null, 204); }
If there is a lot of logic, then it is better to put it in a separate layer Service / Repository
Open the app / Http / Game.php model and add properties:
protected $fillable = ['title', 'description', 'complexity', 'minPlayers', 'maxPlayers', 'isActive']; protected $hidden = ['created_at', 'updated_at', 'deleted_at'];
So that our application always returns json regardless of the transferred headers, create middleware:
php artisan make:middleware ForceJsonResponse
and add the code to it:
public function handle($request, Closure $next) { $request->headers->set('Accept', 'application/json'); return $next($request); }
Register this middleware in app/Http/Kernel.php
:
... 'api' => [ 'throttle:60,1', 'bindings', \App\Http\Middleware\ForceJsonResponse::class, ],
Open routes/api.php
and add:
use Http\Controllers\GameController; Route::apiResource('/games', 'GameController');
The static method Route :: apiResource, unlike the resource method, excludes the edit and create methods, leaving only the index, show, store, update, destroy.
The same can be achieved with a more obvious record:
Route::resource('/games', 'GameController')->only([ 'index', 'show', 'store', 'update', 'destroy' ]);
Now, you can look at the path with the command php artisan route:list
and use.
REST API is ready!
If authorization is required, then the standard Laravel Passport will do.
composer require laravel/passport php artisan make:auth php artisan passport:install php artisan migrate
Add the Laravel\Passport\HasApiTokens
to the App\User
model and call Passport::routesmethod
to the boot
app/AuthServiceProvider
:
public function boot() { $this->registerPolicies(); Passport::routes(); }
In the config/auth.php
change the driver to passport:
'api' => [ 'driver' => 'passport', 'provider' => 'users', ],
Create a controller for authorization 'php artisan make: controller Api / AuthController.php`
We add there the code
use App\User; use Illuminate\Support\Facades\Validator;
public function register (Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6|confirmed', ]); if ($validator->fails()) { return response(['errors'=>$validator->errors()->all()], 422); } $request['password']=Hash::make($request['password']); $user = User::create($request->toArray()); $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); } public function login (Request $request) { $user = User::where('email', $request->email)->first(); if ($user) { if (Hash::check($request->password, $user->password)) { $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); } else { $response = "Password missmatch"; return response($response, 422); } } else { $response = 'User does not exist'; return response($response, 422); } } public function logout (Request $request) { $token = $request->user()->token(); $token->revoke(); $response = 'You have been succesfully logged out!'; return response($response, 200); }
After that, you can use the methods api/register, api/login, api/logout
for authorization, and close access to api. To do this, you need to wrap the routing of our REST controllers in middleware:
Route::middleware('auth:api')->group(function () { ... Route::get('/logout', 'Api\AuthController@logout')->name('logout'); });
There would be more functional tests and documentation generation in the swagger, but this is a bit beyond the scope of the scaffold-tutorial, so about this another time
Source: https://habr.com/ru/post/441946/
All Articles