Skip to main content

Quick Start

This guide walks you through building a complete REST API for a Product resource in under 5 minutes. By the end, you’ll have a fully functional API with filtering, pagination, and relation loading.

Prerequisites

  • PHP 8.0 or higher
  • Laravel 12.x
  • Composer

Installation

1

Install the Package

Install via Composer:
composer require ronu/rest-generic-class
The package uses Laravel’s auto-discovery, so no need to register the service provider.
2

Publish Configuration (Optional)

If you want to customize settings, publish the config file:
php artisan vendor:publish --tag=rest-generic-class-config
This creates config/rest-generic-class.php. The package works with sensible defaults, so publishing is optional.
That’s it! The package is installed and ready to use. No migrations, no additional setup required.

Basic Setup

Let’s build a Product API with category relationships. We’ll create four files:

Step 1: Create the Model

Create app/Models/Product.php:
app/Models/Product.php
<?php

namespace App\Models;

use Ronu\RestGenericClass\Core\Models\BaseModel;

class Product extends BaseModel
{
    protected $fillable = ['name', 'price', 'stock', 'category_id'];

    /**
     * Model identifier used in route parameters
     */
    const MODEL = 'product';

    /**
     * Allowlisted relations for security
     * Only these relations can be filtered and loaded
     */
    const RELATIONS = ['category', 'reviews'];

    /**
     * Define the category relationship
     */
    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    /**
     * Define the reviews relationship
     */
    public function reviews()
    {
        return $this->hasMany(Review::class);
    }
}
The RELATIONS constant is critical for security. Only relations listed here can be queried or loaded. This prevents attackers from accessing unintended data.

Step 2: Create the Service

Create app/Services/ProductService.php:
app/Services/ProductService.php
<?php

namespace App\Services;

use App\Models\Product;
use Ronu\RestGenericClass\Core\Services\BaseService;

class ProductService extends BaseService
{
    public function __construct()
    {
        parent::__construct(Product::class);
    }

    // All CRUD methods are inherited from BaseService:
    // - list_all($params)           - List with filters, pagination, relations
    // - get_one($params)            - Get single record with relations
    // - create($data)               - Create new record
    // - update($data, $id)          - Update existing record
    // - destroy($id)                - Delete record
    // - update_multiple($data)      - Bulk update
    // - show($params, $id)          - Show with relations
}
The service is minimal because all standard CRUD logic is inherited. You can override methods to add custom business logic when needed.

Step 3: Create the Controller

Create app/Http/Controllers/Api/ProductController.php:
app/Http/Controllers/Api/ProductController.php
<?php

namespace App\Http\Controllers\Api;

use App\Models\Product;
use App\Services\ProductService;
use Ronu\RestGenericClass\Core\Controllers\RestController;

class ProductController extends RestController
{
    /**
     * Model class for this controller
     */
    protected $modelClass = Product::class;

    /**
     * Inject the service via constructor
     */
    public function __construct(ProductService $service)
    {
        $this->service = $service;
    }

    // All REST endpoints are inherited from RestController:
    // - index(Request)           - GET    /products
    // - show(Request, $id)       - GET    /products/{id}
    // - store(Request)           - POST   /products
    // - update(Request, $id)     - PUT    /products/{id}
    // - destroy($id)             - DELETE /products/{id}
    // - updateMultiple(Request)  - POST   /products/update-multiple
}

Step 4: Define Routes

Add to routes/api.php:
routes/api.php
use App\Http\Controllers\Api\ProductController;
use Illuminate\Support\Facades\Route;

Route::prefix('v1')->group(function () {
    // Standard REST endpoints (index, store, show, update, destroy)
    Route::apiResource('products', ProductController::class);

    // Additional bulk operation endpoint
    Route::post('products/update-multiple', [ProductController::class, 'updateMultiple']);
});
Laravel’s apiResource automatically creates these routes:
  • GET /api/v1/products → index
  • POST /api/v1/products → store
  • GET /api/v1/products/{id} → show
  • PUT /api/v1/products/{id} → update
  • DELETE /api/v1/products/{id} → destroy

Test Your API

Your API is now ready! Let’s test it:

Create a Product

curl -X POST http://localhost:8000/api/v1/products \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mechanical Keyboard",
    "price": 129.99,
    "stock": 50,
    "category_id": 1
  }'
Response:
{
  "success": true,
  "model": {
    "id": 1,
    "name": "Mechanical Keyboard",
    "price": 129.99,
    "stock": 50,
    "category_id": 1,
    "created_at": "2026-03-05T10:30:00.000000Z",
    "updated_at": "2026-03-05T10:30:00.000000Z"
  }
}

List Products with Filtering

curl -X GET "http://localhost:8000/api/v1/products" \
  -H "Content-Type: application/json" \
  -d '{
    "select": ["id", "name", "price"],
    "oper": {
      "and": [
        "price|>=|50",
        "stock|>|0"
      ]
    },
    "pagination": {
      "page": 1,
      "pageSize": 10
    }
  }'
Response:
{
  "current_page": 1,
  "data": [
    {
      "id": 1,
      "name": "Mechanical Keyboard",
      "price": 129.99
    }
  ],
  "total": 1,
  "per_page": 10,
  "last_page": 1
}

Load with Relations

curl -X GET "http://localhost:8000/api/v1/products?relations=[\"category:id,name\"]" \
  -H "Content-Type: application/json"
Response:
{
  "data": [
    {
      "id": 1,
      "name": "Mechanical Keyboard",
      "price": 129.99,
      "stock": 50,
      "category_id": 1,
      "category": {
        "id": 1,
        "name": "Electronics"
      }
    }
  ]
}
curl -X GET "http://localhost:8000/api/v1/products" \
  -H "Content-Type: application/json" \
  -d '{
    "relations": ["category"],
    "oper": {
      "category": {
        "and": ["name|=|Electronics"]
      }
    }
  }'
This returns only products whose category name is “Electronics”.

Update a Product

curl -X PUT http://localhost:8000/api/v1/products/1 \
  -H "Content-Type: application/json" \
  -d '{
    "price": 119.99,
    "stock": 45
  }'

Bulk Update Multiple Products

curl -X POST http://localhost:8000/api/v1/products/update-multiple \
  -H "Content-Type: application/json" \
  -d '{
    "product": [
      {"id": 1, "stock": 40},
      {"id": 2, "stock": 30},
      {"id": 3, "stock": 25}
    ]
  }'

Delete a Product

curl -X DELETE http://localhost:8000/api/v1/products/1

What You Get Out of the Box

With these four files, you now have:

Complete CRUD

Create, read, update, and delete operations with proper HTTP methods

Dynamic Filtering

Complex queries with operators and powerful filtering capabilities

Relation Loading

Eager load relations with field selection

Pagination

Both offset-based and cursor-based pagination

Sorting

Order by any field in ascending or descending order

Bulk Operations

Update multiple records in a single request

Security

Relation allowlists, operator allowlists, query depth limits

Error Handling

Automatic database error parsing with user-friendly messages

Advanced Query Examples

Complex Filtering with Nested Logic

{
  "oper": {
    "and": [
      "stock|>|0",
      {
        "or": [
          "price|<|50",
          "on_sale|=|true"
        ]
      }
    ]
  }
}
Translation: Get products that are in stock AND (price < 50 OR on_sale = true)

Multiple Relations with Field Selection

GET /api/v1/products?relations=["category:id,name","reviews:id,rating,comment,user"]
Loads category with only id and name, plus reviews with id, rating, comment, and the full user relation.

Nested Relation Loading

GET /api/v1/products?relations=["category.parent:id,name","reviews.user:id,name"]
Loads category → parent and reviews → user in a single query.

Filtering on Nested Relations

{
  "oper": {
    "category.parent": {
      "and": ["name|=|Technology"]
    }
  }
}
Translation: Get products where the parent category’s name is “Technology”.

Cursor Pagination for Infinite Scroll

{
  "pagination": {
    "infinity": true,
    "pageSize": 20,
    "cursor": "eyJpZCI6MTAsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0"
  }
}
Perfect for mobile apps with infinite scrolling.

Next Steps

Now that you have a working API, explore these advanced features:

Configuration Guide

Learn about environment variables, caching, and configuration options

Basic Usage

Detailed guide to all query parameters and filtering options

Advanced Usage

Hierarchical data, role-based fields, exports, and custom logic

API Reference

Complete reference for all methods, parameters, and configurations

Common Next Tasks

Create a FormRequest class extending BaseFormRequest:
namespace App\Http\Requests;

use Ronu\RestGenericClass\Core\Requests\BaseFormRequest;

class ProductRequest extends BaseFormRequest
{
    public function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'price' => 'required|numeric|min:0',
            'stock' => 'required|integer|min:0',
            'category_id' => 'required|exists:categories,id',
        ];
    }
}
Update your controller to use it:
public function store(ProductRequest $request): array
{
    return parent::store($request);
}
Add to your .env:
REST_CACHE_ENABLED=true
REST_CACHE_STORE=redis
REST_CACHE_TTL=60
REST_CACHE_TTL_LIST=60
REST_CACHE_TTL_ONE=30
Cache is automatically applied to read operations and invalidated on writes.
In your model:
protected array $fieldsByRole = [
    'admin' => ['featured', 'promoted', 'verified'],
];
Now only users with the ‘admin’ role can modify these fields.
For tree structures (like categories):
class Category extends BaseModel
{
    const HIERARCHY_FIELD_ID = 'parent_id';
    protected $fillable = ['name', 'parent_id'];
}
Query the hierarchy:
{
  "hierarchy": {
    "filter_mode": "with_descendants",
    "max_depth": 3
  }
}
Congratulations! You’ve built a production-ready REST API with advanced filtering, relations, and pagination in just a few minutes.

Troubleshooting

Make sure the relation is listed in your model’s RELATIONS constant:
const RELATIONS = ['category', 'reviews'];
Check your operator syntax. Format must be: field|operator|valueCorrect: "price|>=|100"
Incorrect: "price >= 100"
The package automatically includes foreign keys. Don’t worry if you specify category:id,name - the category_id FK is added automatically.
Ensure caching is enabled in config and your cache driver is properly configured:
php artisan config:clear
php artisan cache:clear

Get Help

If you run into issues:
  1. Check the Troubleshooting Guide for common problems
  2. Review the FAQ for frequently asked questions
  3. Open an issue on GitHub
  4. Read the full Documentation for detailed information

Ready to dive deeper?

Explore the complete feature set in the Basic Usage guide

Build docs developers (and LLMs) love