Với các bạn làm dự án mà yêu cầu chạy trên môi trường web và app ..., để đồng bộ hóa dữ liệu thì chúng ta sẽ cần xây dựng Api cho dự án của mình, bài viết này mình sẽ hướng dẫn mọi người xây dựng API đúng chuẩn RESTful với framework Laravel version 5.7, vì sử dụng framework Laravel nên mọi người nên tìm hiểu cơ bản về laravel trước khi đọc bài viết.
Để bắt đầu xây dựng API đầu tiền chúng ta sẽ đi tìm hiểu qua về HTTP request, HTTP request có tất cả 9 loại method , 2 loại được sử dụng phổ biến nhất là GET và POST
Phía trên mình đã giới thiệu qua về các method của HTTP request, giờ sẽ đi áp dụng vào router của laravel.
Viết Api thì mọi người sẽ khai báo router vào file routes/api.php thay vì sử dụng file routes/web.php.
Mình sẽ giải thích qua về các setting mặc cho file api.php trong laravel 1 chút
Mọi người có thể tùy chỉnh giá trị mặc định này trong method mapApiRoutes trong file app/Providers/RouteServiceProvider.php
Tiếp theo để thực hiện các thao tác đơn giản như CRUD (Create, Read, Update, Delete) mình sẽ tạo các router
// Lấy danh sách sản phẩm
Route::get('products', 'Api\ProductController@index')->name('products.index');
// Lấy thông tin sản phẩm theo id
Route::get('products/{id}', 'Api\ProductController@show')->name('products.show');
// Thêm sản phẩm mới
Route::post('products', 'Api\ProductController@store')->name('products.store');
// Cập nhật thông tin sản phẩm theo id
# Sử dụng put nếu cập nhật toàn bộ các trường
Route::put('products/{id}', 'Api\ProductController@update')->name('products.update');
# Sử dụng patch nếu cập nhật 1 vài trường
Route::patch('products/{id}', 'Api\ProductController@update')->name('products.update');
// Xóa sản phẩm theo id
Route::delete('products/{id}', 'Api\ProductController@destroy')->name('products.destroy');
Mặc định router đã được gán middleware bindings, nếu muốn sử dụng model binding trong controller thì chúng ta sửa lại tham số trong router như sau:
Route::get('products/{product}', 'Api\ProductController@show')->name('products.show');
Route::put('products/{product}', 'Api\ProductController@update')->name('products.update');
Route::patch('products/{product}', 'Api\ProductController@update')->name('products.update');
Route::delete('products/{product}', 'Api\ProductController@destroy')->name('products.destroy');
Ngoài ra trong laravel cũng hỗ trợ chúng ta 1 cách khai báo ngắn gọn hơn
Route::apiResource('products', 'Api\ProductController');
Nếu không muốn sử dụng toàn bộ method trong apiResource mọi người có thể chỉ định sử dụng 1 vài method bằng hàm only
Route::apiResource('products', 'Api\ProductController')->only(['index', 'show']);
Hoặc nếu muốn loại bỏ đi 1 số method không dùng thì có thể sử dụng hàm except
Route::apiResource('products', 'Api\ProductController')->except(['show', 'update']);
Tương ứng với các Router RESTful ở phía trên, đặc biệt nếu mọi người dùng method apiResource thì laravel cũng hỗ trợ chúng ta các method xử lí tương ứng trong controller
Để tạo ra Resource Controllers chúng ta chạy lệnh sau
php artisan make:controller Api/ProductController -api
FIle ProductController tạo ra sẽ như sau
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class ProductController 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 int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
Ngoài ra nếu muốn sử dụng model binding khi tạo Resource Controllers thì dùng lệnh bên dưới
php artisan make:controller Api/ProductController --api --model=Models/Product
FIle ProductController tạo ra sẽ như sau, mọi người để ý tham số của các phương thức show, update, destroy sẽ thay đổi 1 chút
<?php
namespace App\Http\Controllers\Api;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class ProductController 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 \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function show(Product $product)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Product $product)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
//
}
}
Phía dưới mình có demo 1 đoạn code đơn giản trong controller kết hợp với model binding và route apiResource khi xây dựng API
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Product[]|\Illuminate\Database\Eloquent\Collection
*/
public function index()
{
return Product::all();
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Product|\Illuminate\Database\Eloquent\Model
*/
public function store(Request $request)
{
return Product::create($request->all());
}
/**
* Display the specified resource.
*
* @param Product $product
* @return Product
*/
public function show(Product $product)
{
return $product;
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param Product $product
* @return bool
*/
public function update(Request $request, Product $product)
{
return $product->update($request->all());
}
/**
* Remove the specified resource from storage.
*
* @param Product $product
* @throws \Exception
*/
public function destroy(Product $product)
{
$product->delete();
}
}
Mặc định khi sử dụng router apiResource thì dữ liệu trả về sẽ tự động được chuyển sang kiểu JSON và sẽ có status tương ứng nên mọi người chỉ cần return dữ liệu ra là ok, còn nếu muốn tùy biến status trả về thì có thể tham khảo cách phía dưới của mình, phía dưới mình có sử dụng class Illuminate\Http\Response để lấy status thay vì fix giá trị vào ví dụ như HTTP_OK tương ứng sẽ là 200
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class ProductController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\JsonResponse
*/
public function index()
{
$products = Product::all();
return response()->json($products, Response::HTTP_OK);
}
}
Eloquent Resources
Khi xây dựng API, bạn có thể cần transform dữ liệu từ controller trước khi trả về cho người dùng ứng dụng của bạn, laravel cũng đã hỗ trợ điều này với Eloquent Resources
Để tạo ra 1 class chuyển đổi chúng ta chạy lệnh sau
php artisan make:resource Product
File app/Http/Resources/Product.php sẽ có nội dung như sau
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Product extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}
Mình sẽ tùy chỉnh dữ liệu trả về là chỉ có title và price
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Product extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'title' => $this->title,
'price' => $this->price,
];
}
}
Ở controller thì mình sẽ sửa lại như sau
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Http\Request;
use App\Http\Resources\Product as ProductResource;
class ProductController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Product[]|\Illuminate\Database\Eloquent\Collection
*/
public function index()
{
$products = Product::all();
return ProductResource::collection($products);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Product|\Illuminate\Database\Eloquent\Model
*/
public function store(Request $request)
{
$product = Product::create($request->all());
return new ProductResource($product);
}
/**
* Display the specified resource.
*
* @param Product $product
* @return Product
*/
public function show(Product $product)
{
return new ProductResource($product);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param Product $product
* @return bool
*/
public function update(Request $request, Product $product)
{
return $product->update($request->all());
}
/**
* Remove the specified resource from storage.
*
* @param Product $product
* @throws \Exception
*/
public function destroy(Product $product)
{
$product->delete();
}
}
Ngoài giới hạn dữ liệu trả về như title hay price, laravel cũng hỗ trợ rất nhiều thứ như thêm relationships, data ..., mọi người có thể đọc thêm tại đây https://laravel.com/docs/5.7/eloquent-resources
Ý nghĩa response status trong HTTP
Hiện tại có 3 cơ chế Authorize chính:
Tùy thuộc vào service của bạn, mà hãy chọn loại Authorize có mức độ phù hợp, cố gắng giữ nó càng đơn giản càng tốt.
Ai cũng biết việc viết API docs là cần thiết, tuy nhiên để có một API docs hoàn chỉnh cũng tiêu tốn kha khá thời gian. Nhất là trong lúc dự án gấp rút thì mọi người thường chỉ để API docs ở mức ... dùng được case chính.
API document là một phần tương tự như Unit Test vậy - lấy ngắn để nuôi dài.
Nếu không được chăm sóc kỹ, thì đến lúc maintain hoặc thay đổi spec thì hậu quả sẽ rất thảm khốc, dưới đây là một số lưu ý lúc viết docs:
Unpublished comment
Viết câu trả lời