nandang

nandang

Web Developer | Security Engineer

Best Practice Laravel REST API

Building a RESTful API CRUD application in Laravel following best practices involves several key steps. This includes setting up the Laravel application, defining routes, implementing validation, working with models, utilizing resources, creating controllers, applying the Repository design pattern, and integrating models with the database. Here is a step-by-step guide to achieving this:

Table of Contents:

Setting up Laravel

Step carefully to install the Laravel application using Composer.

    composer create-project --prefer-dist laravel/laravel rest-api-crud

Mysql Database Configuration

Laravel 11 default DB_CONNECTION=sqlite.you have to change this DB_CONNECTION=mysql this is in env file.

    LOG_CHANNEL=stack
    LOG_DEPRECATIONS_CHANNEL=null
    LOG_LEVEL=debug

    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=your_database
    DB_USERNAME=root
    DB_PASSWORD=

That’s probably the main message you should take home from this article. I will emphasize it for you, in bold and caps.

YOU ARE FREE TO STRUCTURE YOUR PROJECT HOWEVER YOU WANT.

Create the Product Model with migration

Here are the artisan commands that must be executed.

    php artisan make:model Product -a

Migration

In database/migrations/YYYY_MM_DD_HHMMSS_create_products_table.php, update the up function to match the following.

     public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('details');
            $table->timestamps();
        });
    }

Create Product Interface

Create a repository interface for the Product model. This separation allows for cleaner and more maintainable code.

    php artisan make:interface /Interfaces/ProductRepositoryInterface

in the Interfaces, create a new file called ProductRepositoryInterface.php and add the following code to it.

    <?php

    namespace App\Interfaces;

    interface ProductRepositoryInterface
    {
        public function index();
        public function getById($id);
        public function store(array $data);
        public function update(array $data,$id);
        public function delete($id);
    }

Create Product Respository Class

Next, create a repository class for the Product model.

    php artisan make:class /Repositories/ProductRepository

in the classes, create a new file called ProductRepository.php and add the following code to it.

    <?php
    namespace App\Repository;
    use App\Models\Product;
    use App\Interfaces\ProductRepositoryInterface;

    class ProductReposiotry implements ProductRepositoryInterface
    {
        public function index(){
            return Product::all();
        }

        public function getById($id){
        return Product::findOrFail($id);
        }

        public function store(array $data){
        return Product::create($data);
        }

        public function update(array $data,$id){
        return Product::whereId($id)->update($data);
        }

        public function delete($id){
        Product::destroy($id);
        }
    }

Bind The Interface And The Implementation

Note that all we need to do is bind the ProductRepository to the ProductRepositoryInterface. We do this via a Service Provider. Create one using the following command.

    php artisan make:provider RepositoryServiceProvider

Open app/Providers/RepositoryServiceProvider.php and update the register function to match the following

    <?php
    namespace App\Providers;

    use Illuminate\Support\ServiceProvider;
    use App\Interfaces\ProductRepositoryInterface;
    use App\Repository\ProductReposiotry;

    class RepositoryServiceProvider extends ServiceProvider
    {
        /**
         * Register services.
         */
        public function register(): void
        {
            $this->app->bind(ProductRepositoryInterface::class,ProductReposiotry::class);
        }

        /**
         * Bootstrap services.
         */
        public function boot(): void
        {
            //
        }
    }

Request Validation

We have to make two requests namely StoreProductRequest and UpdateProductRequest, add the following code to it. For the input validation process.

File StoreProductRequest.php

    <?php
    namespace App\Http\Requests;

    use Illuminate\Foundation\Http\FormRequest;
    use Illuminate\Http\Exceptions\HttpResponseException;
    use Illuminate\Contracts\Validation\Validator;

    class StoreProductRequest extends FormRequest
    {
        /**
         * Determine if the user is authorized to make this request.
         */
        public function authorize(): bool
        {
            return true;
        }

        /**
         * Get the validation rules that apply to the request.
         *
         * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
         */
        public function rules(): array
        {
            return [
                'name' => 'required',
                'details' => 'required'
            ];
        }

        public function failedValidation(Validator $validator)
        {
            throw new HttpResponseException(response()->json([
                'success'   => false,
                'message'   => 'Validation errors',
                'data'      => $validator->errors()
            ]));
        }
    }

File UpdateProductRequest.php

    <?php

    namespace App\Http\Requests;

    use Illuminate\Foundation\Http\FormRequest;
    use Illuminate\Http\Exceptions\HttpResponseException;
    use Illuminate\Contracts\Validation\Validator;

    class UpdateProductRequest extends FormRequest
    {
        /**
         * Determine if the user is authorized to make this request.
         */
        public function authorize(): bool
        {
            return true;
        }

        /**
         * Get the validation rules that apply to the request.
         *
         * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
         */
        public function rules(): array
        {
            return [
                'name' => 'required',
                'details' => 'required'
            ];
        }

        public function failedValidation(Validator $validator)
        {
            throw new HttpResponseException(response()->json([
                'success'   => false,
                'message'   => 'Validation errors',
                'data'      => $validator->errors()
            ]));
        }
    }

Common ApiResponseClass Create

This common response class is the best practice thing. Because you can response send con function use. Create one using the following command

    php artisan make:class /Classes/ApiResponseClass

Add the following code to it.

    <?php

    namespace App\Classes;
    use Illuminate\Support\Facades\DB;
    use Illuminate\Http\Exceptions\HttpResponseException;
    use Illuminate\Support\Facades\Log;

    class ApiResponseClass
    {
        public static function rollback($e, $message ="Something went wrong! Process not completed"){
            DB::rollBack();
            self::throw($e, $message);
        }

        public static function throw($e, $message ="Something went wrong! Process not completed"){
            Log::info($e);
            throw new HttpResponseException(response()->json(["message"=> $message], 500));
        }

        public static function sendResponse($result , $message ,$code=200){
            $response=[
                'success' => true,
                'data'    => $result
            ];
            if(!empty($message)){
                $response['message'] =$message;
            }
            return response()->json($response, $code);
        }

    }

Create Product Resource

Create one using the following command.

    php artisan make:resource ProductResource

Add the following code to it.

    <?php
    namespace App\Http\Resources;

    use Illuminate\Http\Request;
    use Illuminate\Http\Resources\Json\JsonResource;

    class ProductResource extends JsonResource
    {
        /**
         * Transform the resource into an array.
         *
         * @return array<string, mixed>
         */
        public function toArray(Request $request): array
        {
            return [
                'id' =>$this->id,
                'name' => $this->name,
                'details' => $this->details
            ];
        }
    }

Productcontroller Class

Run artisan as below

    php artisan make:controller ProductController -r

With our repository in place, let’s add some code to our controller. Open app/Http/Controllers/ProductController.php and update the code to match the following.

    <?php

    namespace App\Http\Controllers;

    use App\Models\Product;
    use App\Http\Requests\StoreProductRequest;
    use App\Http\Requests\UpdateProductRequest;
    use App\Interfaces\ProductRepositoryInterface;
    use App\Classes\ResponseClass;
    use App\Http\Resources\ProductResource;
    use Illuminate\Support\Facades\DB;

    class ProductController extends Controller
    {

        private ProductRepositoryInterface $productRepositoryInterface;

        public function __construct(ProductRepositoryInterface $productRepositoryInterface)
        {
            $this->productRepositoryInterface = $productRepositoryInterface;
        }
        /**
         * Display a listing of the resource.
         */
        public function index()
        {
            $data = $this->productRepositoryInterface->index();

            return ResponseClass::sendResponse(ProductResource::collection($data),'',200);
        }

        /**
         * Show the form for creating a new resource.
         */
        public function create()
        {
            //
        }

        /**
         * Store a newly created resource in storage.
         */
        public function store(StoreProductRequest $request)
        {
            $details =[
                'name' => $request->name,
                'details' => $request->details
            ];
            DB::beginTransaction();
            try{
                $product = $this->productRepositoryInterface->store($details);

                DB::commit();
                return ResponseClass::sendResponse(new ProductResource($product),'Product Create Successful',201);

            }catch(\Exception $ex){
                return ResponseClass::rollback($ex);
            }
        }

        /**
         * Display the specified resource.
         */
        public function show($id)
        {
            $product = $this->productRepositoryInterface->getById($id);

            return ResponseClass::sendResponse(new ProductResource($product),'',200);
        }

        /**
         * Show the form for editing the specified resource.
         */
        public function edit(Product $product)
        {
            //
        }

        /**
         * Update the specified resource in storage.
         */
        public function update(UpdateProductRequest $request, $id)
        {
            $updateDetails =[
                'name' => $request->name,
                'details' => $request->details
            ];
            DB::beginTransaction();
            try{
                $product = $this->productRepositoryInterface->update($updateDetails,$id);

                DB::commit();
                return ResponseClass::sendResponse('Product Update Successful','',201);

            }catch(\Exception $ex){
                return ResponseClass::rollback($ex);
            }
        }

        /**
         * Remove the specified resource from storage.
         */
        public function destroy($id)
        {
            $this->productRepositoryInterface->delete($id);

            return ResponseClass::sendResponse('Product Delete Successful','',204);
        }
    }

Route Api

Executing the subsequent command allows you to publish the API route file:

php artisan install:api

To map each method defined in the controller to specific routes, add the following code to routes/api.php.

    <?php

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Route;
    use App\Http\Controllers\ProductController;
    Route::get('/user', function (Request $request) {
        return $request->user();
    })->middleware('auth:sanctum');


    Route::apiResource('/products',ProductController::class);