PHPackages                             laraditz/crudless - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. laraditz/crudless

ActiveLibrary[HTTP &amp; Networking](/categories/http)

laraditz/crudless
=================

Minimal base API controller for Laravel - full CRUD with a single property declaration.

1.2.3(2mo ago)010MITPHPPHP ^8.1

Since Apr 9Pushed 2mo agoCompare

[ Source](https://github.com/laraditz/crudless)[ Packagist](https://packagist.org/packages/laraditz/crudless)[ RSS](/packages/laraditz-crudless/feed)WikiDiscussions main Synced 2w ago

READMEChangelogDependencies (8)Versions (10)Used By (0)

Laravel Crudless
================

[](#laravel-crudless)

[![Latest Version on Packagist](https://camo.githubusercontent.com/140d9450859816705e7d0ddf3bd229aba6b35c7263ef8d66690e77af6b27ed0c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6c6172616469747a2f637275646c6573732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/laraditz/crudless)[![Total Downloads](https://camo.githubusercontent.com/5564f540a6039c57ffe1bad367c1c91e4b7a88c87d61f50e3a353181561604ba/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6c6172616469747a2f637275646c6573732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/laraditz/crudless)[![License](https://camo.githubusercontent.com/36caaf5f6a2dabf80c9d326d86e00154c6e7b76aacdb93873d1eb53b6badb71d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6c6172616469747a2f637275646c6573733f7374796c653d666c61742d737175617265)](./LICENSE.md)

The fastest way to build a Laravel API. Get full CRUD, auth (register, login, logout), or both working in minutes - use only what you need, no boilerplate, no repetition.

Requirements
------------

[](#requirements)

- PHP ^8.1
- Laravel 10, 11, 12, or 13

Installation
------------

[](#installation)

```
composer require laraditz/crudless
```

Quick Start
-----------

[](#quick-start)

Use whichever pieces you need - CRUD, auth, or both.

**1. CRUD - extend `BaseApiController`:**

```
use Laraditz\Crudless\BaseApiController;

class UserController extends BaseApiController {}
```

```
Route::apiResource('users', UserController::class);
```

`index`, `show`, `store`, `update`, `destroy` are live. The model is resolved automatically from the controller name (`UserController` → `App\Models\User`).

**2. Auth - register routes in `routes/api.php`:**

```
use Laraditz\Crudless\Facades\Crudless;

Crudless::authRoutes();
```

That's it. You now have:

MethodURINameDescription`POST``/auth/register``auth.register`Create account`POST``/auth/login``auth.login`Get Sanctum token`POST``/auth/logout``auth.logout`Revoke token---

Configuration Reference
-----------------------

[](#configuration-reference)

Both `BaseApiController` and `BaseAuthController` are optional - use only what you need. Every property on each is optional too.

### Model

[](#model)

```
protected ?string $model = null;
```

The Eloquent model this controller manages. Optional - when not declared, the model is guessed from the controller class name:

ControllerResolved model`UserController``App\Models\User` → `App\User``PostController``App\Models\Post` → `App\Post`The first class that exists wins. Set `$model` explicitly when the model lives outside these namespaces:

```
protected string $model = \Domain\Blog\Models\Post::class;
```

---

### API Resource

[](#api-resource)

```
protected ?string $resource = null;
```

When set, all responses are wrapped through this resource class.

```
class UserController extends BaseApiController
{
    protected string $model     = User::class;
    protected ?string $resource = UserResource::class;
}
```

Applies to `index`, `show`, `store`, and `update` responses.

---

### Eager Loading

[](#eager-loading)

```
protected array $with     = [];   // used by index()
protected array $withShow = [];   // used by show() - falls back to $with if empty
```

```
class PostController extends BaseApiController
{
    protected string $model    = Post::class;
    protected array $with      = ['author', 'category'];
    protected array $withShow  = ['author', 'category', 'tags', 'comments'];
}
```

`$withShow` lets `show()` load heavier relationships without affecting the list response.

---

### Authorization

[](#authorization)

```
protected array $authorizedMethods = ['index', 'show', 'store', 'update', 'destroy'];
```

Lists which methods run a policy check. Laravel resolves the correct policy from the model automatically.

```
// Only write operations require authorization
class PostController extends BaseApiController
{
    protected string $model            = Post::class;
    protected array $authorizedMethods = ['store', 'update', 'destroy'];
}

// No authorization at all
class PublicPostController extends BaseApiController
{
    protected string $model            = Post::class;
    protected array $authorizedMethods = [];
}
```

Default is all five methods - secure by default.

---

### Pagination

[](#pagination)

```
protected ?int $perPage    = null;   // null = no pagination
protected int $maxPerPage  = 100;    // cap on client-requested page size
```

```
class PostController extends BaseApiController
{
    protected string $model  = Post::class;
    protected ?int $perPage  = 15;
    protected int $maxPerPage = 50;
}
```

Client can override page size via `?per_page=25`. The `$maxPerPage` cap prevents abuse.

When paginated, `index()` returns `response()->api()->collection()` which includes `meta` and `links` automatically.

When not paginated, returns the full collection via `response()->api()->data()->success()`.

---

### Validation

[](#validation)

Three levels available - use whichever fits:

**Level 1 - Form Request (recommended for complex rules):**

```
protected ?string $storeRequest  = null;
protected ?string $updateRequest = null;
```

```
class PostController extends BaseApiController
{
    protected string $model          = Post::class;
    protected ?string $storeRequest  = StorePostRequest::class;
    protected ?string $updateRequest = UpdatePostRequest::class;
}
```

**Level 2 - Ad-hoc rules (for simple cases):**

```
class TagController extends BaseApiController
{
    protected string $model = Tag::class;

    protected function storeRules(): array
    {
        return ['name' => 'required|string|max:50|unique:tags'];
    }

    protected function updateRules(): array
    {
        return ['name' => 'required|string|max:50|unique:tags,name,' . request()->route('tag')];
    }
}
```

**Level 3 - No validation:**

Declare neither. Falls back to `$request->all()`.

**Priority:** `$storeRequest` → `storeRules()` → `$request->all()`

---

### Custom Query

[](#custom-query)

Override `query()` to chain additional constraints on `index()`:

```
class PostController extends BaseApiController
{
    protected string $model = Post::class;

    protected function query(): Builder
    {
        return $this->resolveModel()::query()
            ->with($this->with)
            ->where('published', true)
            ->latest();
    }
}
```

The base `query()` is simply `$this->resolveModel()::query()->with($this->with)` - zero cost when not overridden.

---

### Filtering

[](#filtering)

Integrates with [`laraditz/model-filter`](https://github.com/laraditz/model-filter) to enable dynamic query filtering on `index()` via request parameters.

**Step 1** — add the `Filterable` trait to your model and declare filterable fields:

```
use Laraditz\ModelFilter\Filterable;

class Post extends Model
{
    use Filterable;

    protected array $filterable = ['title', 'status', 'author.name'];
}
```

**Step 2** — filtering is applied automatically on `index()` with no extra configuration needed:

```
class PostController extends BaseApiController
{
    protected string $model = Post::class;
}
```

Clients can now filter via query parameters:

```
GET /posts?filters[status]=published
GET /posts?filters[title]=Laravel

```

For custom filter logic, generate a filter class and point `$filter` to it:

```
php artisan make:filter PostFilter
```

```
class PostController extends BaseApiController
{
    protected string $model  = Post::class;
    protected ?string $filter = PostFilter::class;
}
```

---

### Lifecycle Hooks

[](#lifecycle-hooks)

Override any hook to add behaviour without replacing the entire method.

**Before hooks** run before the main action. Throw an exception (e.g. `abort(403)`) to stop execution.

**After hooks** receive the result and must return it - return a modified value to change what is sent in the response.

HookSignatureWhen it runs`beforeIndex``(): void`after auth, before query`afterIndex``(mixed $data): mixed`after query, before response`beforeShow``(mixed $record): void`after auth, before response`afterShow``(mixed $record): mixed`after `beforeShow`, before response`beforeStore``(array $data): void`after auth + validation, before `create``afterStore``(mixed $record): mixed`after `create`, before response`beforeUpdate``(mixed $record, array $data): void`after auth + validation, before `update``afterUpdate``(mixed $record): mixed`after `update`, before response`beforeDestroy``(mixed $record): void`after auth, before `delete``afterDestroy``(): void`after `delete`, before response```
class OrderController extends BaseApiController
{
    protected string $model = Order::class;

    // Send a notification after an order is created
    protected function afterStore(mixed $record): mixed
    {
        $record->notify(new OrderCreatedNotification());

        return $record;
    }

    // Prevent deletion of completed orders
    protected function beforeDestroy(mixed $record): void
    {
        abort_if($record->status === 'completed', 403, 'Completed orders cannot be deleted.');
    }
}
```

---

Response Map
------------

[](#response-map)

All responses go through [`raditzfarhan/laravel-api-response`](https://github.com/raditzfarhan/laravel-api-response) via the `response()->api()` macro.

MethodResponse`index` (paginated)`response()->api()->collection($data)` - includes `meta` and `links``index` (full list)`response()->api()->data($data)->success()``show``response()->api()->data($record)->success()``store``response()->api()->created($record)` - HTTP 201`update``response()->api()->data($record)->success()``destroy``response()->api()->httpCode(204)->success()`---

Full Example
------------

[](#full-example)

A fully configured controller:

```
class PostController extends BaseApiController
{
    protected string $model            = Post::class;
    protected ?string $resource        = PostResource::class;

    protected array $with              = ['author', 'category'];
    protected array $withShow          = ['author', 'category', 'tags', 'comments'];

    protected array $authorizedMethods = ['store', 'update', 'destroy'];

    protected ?int $perPage            = 15;
    protected int $maxPerPage          = 50;

    protected ?string $storeRequest    = StorePostRequest::class;
    protected ?string $updateRequest   = UpdatePostRequest::class;

    protected ?string $filter          = PostFilter::class;

    protected function query(): Builder
    {
        return $this->resolveModel()::query()
            ->with($this->with)
            ->where('published', true)
            ->latest();
    }
}
```

A minimal one - model resolved automatically from the class name:

```
class TagController extends BaseApiController {}
```

---

Extending Individual Methods
----------------------------

[](#extending-individual-methods)

For most customisation, prefer lifecycle hooks - they keep the method intact and compose cleanly. When you need full control, any CRUD method can be overridden:

```
class OrderController extends BaseApiController
{
    protected string $model = Order::class;

    public function store(Request $request)
    {
        $this->authorizeAction('create', $this->resolveModel());

        $data   = $this->resolveStoreData($request);
        $record = $this->resolveModel()::create($data);

        // full custom flow when hooks are not enough
        event(new OrderPlaced($record));

        return response()->api()->created($this->transform($record));
    }
}
```

Internal helpers `authorizeAction()`, `resolveStoreData()`, `resolveUpdateData()`, `resolveModel()`, and `transform()` are all `protected` and available in child classes.

---

### Auth

[](#auth)

`BaseAuthController` provides register, login, and logout via Laravel Sanctum. Skip this entirely if you have your own auth solution.

**Step 1** - ensure `HasApiTokens` is on your User model:

```
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;
}
```

**Step 2** - register routes in `routes/api.php`:

```
use Laraditz\Crudless\Facades\Crudless;

Crudless::authRoutes();
```

This registers:

MethodURINameAuth`POST``/auth/register``auth.register`No`POST``/auth/login``auth.login`No`POST``/auth/logout``auth.logout`Bearer token**Custom prefix or controller:**

```
// Custom prefix only
Crudless::authRoutes('admin');

// Custom prefix + custom controller
Crudless::authRoutes('vendor', VendorAuthController::class);
```

**Apply middleware to all auth routes:**

```
// Single middleware
Crudless::authRoutes(middleware: ['throttle:60,1']);

// Multiple middleware
Crudless::authRoutes(middleware: ['throttle:60,1', 'verified']);

// Combined with other parameters
Crudless::authRoutes('admin', AdminAuthController::class, middleware: ['throttle:10,1']);
```

**Disable specific routes with `$except`:**

```
// Register and login only — no register route
Crudless::authRoutes(except: ['register']);

// Login only
Crudless::authRoutes(except: ['register', 'logout']);

// Works with custom prefix and controller too
Crudless::authRoutes('admin', AdminAuthController::class, except: ['register']);
```

Available values: `'register'`, `'login'`, `'logout'`.

**Configuration properties:**

```
protected string  $userModel       = \App\Models\User::class;
protected ?string $registerRequest = null;
protected ?string $loginRequest    = null;
```

**Overridable rule methods:**

```
class AdminAuthController extends BaseAuthController
{
    protected string $userModel = Admin::class;

    protected function registerRules(): array
    {
        return [
            ...parent::registerRules(),
            'department' => 'required|string',
        ];
    }
}
```

**Lifecycle hooks:**

HookSignatureWhen it runs`beforeRegister``(array $data): void`after validation, before creation`afterRegister``(mixed $user): mixed`after creation, before response`beforeLogin``(array $data): void`after validation, before credential check`afterLogin``(mixed $user, string $token): mixed`after token issued - return value is the response body`beforeLogout``(mixed $user): void`before token revocation`afterLogout``(): void`after revocation, before response```
class AdminAuthController extends BaseAuthController
{
    protected function afterLogin(mixed $user, string $token): mixed
    {
        return ['token' => $token, 'role' => $user->role];
    }

    protected function beforeRegister(array $data): void
    {
        abort_if(!str_ends_with($data['email'], '@company.com'), 403, 'Company email required.');
    }
}
```

---

Dependencies
------------

[](#dependencies)

- [`raditzfarhan/laravel-api-response`](https://github.com/raditzfarhan/laravel-api-response) - all responses are dispatched through its `response()->api()` macro
- [`laraditz/model-filter`](https://github.com/laraditz/model-filter) - powers the `$filter` / `Filterable` integration for `index()` query filtering
- [`laravel/sanctum`](https://github.com/laravel/sanctum) - token issuance and revocation for auth endpoints

---

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information about recent changes.

Security
--------

[](#security)

If you discover any security vulnerabilities, please email  instead of using the issue tracker. All security vulnerabilities will be promptly addressed.

Credits
-------

[](#credits)

- [Raditz Farhan](https://github.com/raditzfarhan) - Creator and maintainer
- [All Contributors](../../contributors) - Thank you for your contributions!

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance86

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

8

Last Release

73d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1203676?v=4)[Raditz Farhan](/maintainers/raditzfarhan)[@raditzfarhan](https://github.com/raditzfarhan)

---

Top Contributors

[![raditzfarhan](https://avatars.githubusercontent.com/u/1203676?v=4)](https://github.com/raditzfarhan "raditzfarhan (34 commits)")

---

Tags

apilaravelrestcrudbasecontroller

### Embed Badge

![Health badge](/badges/laraditz-crudless/health.svg)

```
[![Health](https://phpackages.com/badges/laraditz-crudless/health.svg)](https://phpackages.com/packages/laraditz-crudless)
```

###  Alternatives

[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9742.3M121](/packages/roots-acorn)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[api-platform/laravel

API Platform support for Laravel

59156.3k11](/packages/api-platform-laravel)[hasinhayder/tyro

Tyro - The ultimate Authentication, Authorization, and Role &amp; Privilege Management solution for Laravel 12 &amp; 13

6753.6k5](/packages/hasinhayder-tyro)[bjerke/laravel-bread

A boilerplate package for BREAD operations through REST API in Laravel

115.2k](/packages/bjerke-laravel-bread)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1232.2k16](/packages/fleetbase-core-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
