A clean, scalable Api CRUD package for the Service-based architecture for Laravel with advanced filtering, eager-loading relationships, and automatic resource resolution
rizkussef/laravel-crud-api is a Laravel package for a clean, scalable api crud package for the service-based architecture for laravel with advanced filtering, eager-loading relationships, and automatic resource resolution.
It currently has 0 GitHub stars and 14 downloads on Packagist (latest version v1.5.2).
Install it with composer require rizkussef/laravel-crud-api.
Discover more Laravel packages by rizkussef
or browse all Laravel packages to compare alternatives.
Last updated
A clean, scalable Api CRUD architecture for Laravel, built around a reusable Base Service and Base Controller with automatic:
Designed to eliminate repetitive CRUD logic while keeping your application clean and maintainable. Pass filters and relationships directly from your frontend to dynamically build optimized queries with full control over data retrieval.
Install via Composer:
composer require rizkussef/laravel-crud-api
If needed, manually register the provider in:
config/app.php
Rizkussef\LaravelCrudApi\ApiCrudServiceProvider::class,
php artisan vendor:publish --provider="Rizkussef\LaravelCrudApi\ApiCrudServiceProvider" --tag=config
This will publish:
config/api-crud.php
Example:
return [
'paginate' => 15,
];
App
βββ Models
β βββ User.php
βββ Services
β βββ UserService.php
βββ Http
β βββ Controllers
β β βββ UserController.php
β βββ Requests
β β βββ UserRequest.php
β β βββ UserUpdateRequest.php
β βββ Resources
β βββ UserResource.php
The absolute fastest way to get started is by using the built-in Artisan generator. This powerful command scaffolds a fully functional CRUD API endpoint in seconds!
php artisan api-crud:make {Model}
php artisan api-crud:make Article
What happens behind the scenes? The command automatically generates all layers required for an API CRUD operation:
| Layer | Generated File | Description |
| :--- | :--- | :--- |
| Model | App\Models\Article.php | The Eloquent model (along with its database migration). |
| Resource | App\Http\Resources\ArticleResource.php | Automatically serializes and formats your JSON response. |
| Requests | App\Http\Requests\ArticleRequest.phpArticleUpdateRequest.php | Dedicated Form validation classes (Auto-validated by Controller). |
| Service | App\Services\ArticleService.php | Core business logic layer (Extends ApiCrudService). |
| Controller| App\Http\Controllers\ArticleController.php | Handles routing and formatting (Extends ApiCrudController). |
| Option | Description | Example |
| :--- | :--- | :--- |
| --no-migration | Tells the generator to skip database migration file creation. | php artisan api-crud:make User --no-migration |
Once generated, just register your new endpoint in routes/api.php and you're fully operational!
Route::apiResource('articles', ArticleController::class);
namespace App\Services;
use Rizkussef\LaravelCrudApi\Services\ApiCrudService;
class UserService extends ApiCrudService
{
// Add custom business logic here if needed
}
The architecture heavily relies on naming conventions to automatically resolve dependencies:
UserService β App\Models\User
UserService β App\Http\Resources\UserResource
UserController β App\Http\Requests\UserRequest (Used for store)
UserController β App\Http\Requests\UserUpdateRequest (Used for update)
No need to manually inject or define the Model, Resource, or Form Requests. ApiCrudController automatically validates inbound requests using the resolved Form Request before passing data to the Service.
namespace App\Http\Controllers;
use App\Services\UserService;
use Rizkussef\LaravelCrudApi\Http\Controllers\ApiCrudController;
class UserController extends ApiCrudController {
public function __construct(UserService $service)
{
parent::__construct($service);
}
}
Just Inject the specific service for this controller.
The package includes powerful filtering and eager-loading capabilities. Pass filters and relationships from the frontend to dynamically build queries. They can be passed either as array parameters or as JSON-encoded strings.
// Equal / Greater Than / Less Than
['age' => ['operator' => '=', 'value' => 25]]
['age' => ['operator' => '>', 'value' => 18]]
['age' => ['operator' => '>=', 'value' => 18]]
['age' => ['operator' => '<', 'value' => 65]]
['age' => ['operator' => '<=', 'value' => 65]]
['status' => ['operator' => '!=', 'value' => 'deleted']]
// LIKE search (wildcard)
['name' => ['operator' => 'like', 'value' => 'John']]
// Starts with
['email' => ['operator' => 'starts_with', 'value' => 'admin']]
// Ends with
['email' => ['operator' => 'ends_with', 'value' => '.com']]
// Simple value (auto LIKE for strings)
['name' => 'John'] // Searches LIKE "%John%"
// Multiple values
['status' => ['operator' => 'in', 'value' => ['active', 'pending']]]
// Exclude values
['status' => ['operator' => 'not_in', 'value' => ['deleted', 'banned']]]
// Range
['price' => ['operator' => 'between', 'value' => [100, 500]]]
['price' => ['operator' => 'not_between', 'value' => [100, 500]]]
['deleted_at' => ['operator' => 'null']] // IS NULL
['verified_at' => ['operator' => '!null']] // IS NOT NULL
['verified_at' => ['operator' => 'not_null']]
['published_at' => ['operator' => 'exists', 'value' => true]] // IS NOT NULL
['published_at' => ['operator' => 'exists', 'value' => false]] // IS NULL
['created_at' => ['operator' => 'date', 'value' => '2024-02-23']]
['created_at' => ['operator' => 'year', 'value' => 2024]]
['created_at' => ['operator' => 'month', 'value' => 2]]
['created_at' => ['operator' => 'day', 'value' => 23]]
import axios from 'axios';
const fetchUsers = async () => {
const { data } = await axios.get('/api/users/Paginate', {
params: {
filters: {
status: 'active',
age: { operator: '>', value: 18 }
},
relationships: ['profile', 'comments'],
per_page: 15
}
});
console.log(data);
};
namespace App\Services;
use App\Services\UserService;
class ReportService
{
public function __construct(private UserService $userService) {}
/**
* Generate active users report
*/
public function getActiveUsersReport()
{
$filters = [
'status' => 'active',
'created_at' => [
'operator' => 'date',
'value' => now()->subMonth()->format('Y-m-d')
]
];
$relationships = ['profile', 'department'];
return $this->userService->index($filters, $relationships);
}
/**
* Get premium users with pagination
*/
public function getPremiumUsers($page = 1)
{
$filters = [
'subscription_type' => 'premium',
'last_payment_date' => [
'operator' => '!=null'
]
];
$relationships = ['subscription', 'profile'];
return $this->userService->getPaginated(
perPage: 50,
filters: $filters,
relationships: $relationships
);
}
/**
* Search users by multiple criteria
*/
public function searchUsers($name, $department, $minAge)
{
$filters = [
'name' => $name,
'department_id' => $department,
'age' => [
'operator' => '>=',
'value' => $minAge
]
];
return $this->userService->index($filters, ['profile', 'department']);
}
}
The BaseCrudController provides:
index() β List recordsgetPaginated($perPage) - Paginate list of recordsshow($id) β Show single recordstore(Request $request) β Automatically resolves and validates the incoming Request using the associated Form Request, then creates recordupdate(Request $request, $id) β Automatically resolves and validates using the associated Update Form Request, then updates recorddestroy($id) β Delete recordController handles validation & response formatting. All business logic is handled inside ApiCrudService.
return new UserResource($user);
return UserResource::collection($users);
Automatically handled by the base service.
// No UserResource exists
return $user; // returns full user model data
If the corresponding Resource class does not exist, the base controller will return the raw model data instead of a resource.
{
"data": [
{
"id": 1,
"name": "John Doe"
}
],
"meta": {
"current_page": 1,
"per_page": 15
}
}
β Use the Artisan Generator Command to create all layers automatically.
β Keep business logic inside Model-specific services.
β Keep controllers thin.
β Extend ApiCrudService instead of modifying it.
β Use Resources for API formatting.
β Use filters and relationships to avoid N+1 query problems.
namespace App\Services;
use Rizkussef\LaravelCrudApi\Services\ApiCrudService;
class UserService extends ApiCrudService
{
/**
* Activate user
*/
public function activate($id)
{
$user = $this->model->findOrFail($id);
$user->update(['active' => true]);
return $this->applyResource($user);
}
/**
* Get active users in a department
*/
public function getActiveDepartmentUsers($departmentId, $perPage = 15)
{
$filters = [
'active' => true,
'department_id' => $departmentId
];
$relationships = ['department', 'profile'];
return $this->getPaginated($perPage, $filters, $relationships);
}
}
namespace App\Services;
use Rizkussef\LaravelCrudApi\Services\ApiCrudService;
class OrderService extends ApiCrudService
{
/**
* Get high-value orders in date range with customer details
*/
public function getHighValueOrders($minAmount, $startDate, $endDate, $perPage = 20)
{
$filters = [
'total_amount' => [
'operator' => '>=',
'value' => $minAmount
],
'created_at' => [
'operator' => 'between',
'value' => [$startDate, $endDate]
],
'status' => [
'operator' => 'in',
'value' => ['completed', 'shipped']
]
];
$relationships = ['customer', 'items.product', 'payment'];
return $this->getPaginated($perPage, $filters, $relationships);
}
/**
* Search orders by customer name and status
*/
public function searchOrders($customerName, $statuses)
{
$filters = [
'customer_name' => [
'operator' => 'like',
'value' => $customerName
],
'status' => [
'operator' => 'in',
'value' => $statuses
]
];
return $this->index($filters, ['customer', 'items']);
}
}
namespace App\Services;
use Rizkussef\LaravelCrudApi\Services\ApiCrudService;
class ArticleService extends ApiCrudService
{
/**
* Get published articles with authors and comments
*/
public function getPublishedArticles($perPage = 15)
{
$filters = [
'status' => 'published',
'published_at' => [
'operator' => '!null' // IS NOT NULL
]
];
$relationships = ['author', 'comments.author', 'tags'];
return $this->getPaginated($perPage, $filters, $relationships);
}
/**
* Get articles pending review (no reviewer assigned)
*/
public function getPendingReview()
{
$filters = [
'status' => 'draft',
'reviewer_id' => [
'operator' => 'null' // IS NULL
]
];
return $this->index($filters, ['author']);
}
}
ApiCrudService is bound in the container via the Service Provider, so it can be injected anywhere.
You can extend:
And you can use:
Route::apiResource('users', UserController::class);
Developed by Rizk Ussef
A fullstack developer passionate about creating clean, maintainable architecture patterns and reusable solutions for building scalable applications across frontend and backend ecosystems.
Contributions are welcome! If you find bugs or have feature suggestions, please open an issue or submit a pull request.
MIT License - see LICENSE file for details.