Skip to content

CRUD Controller

Laraventus includes a ready-to-use generic controller called ModelController that allows you to quickly create full CRUD endpoints for any Eloquent model with minimal code. This controller automatically handles listing, creation, updating, and deletion of records, all fully compatible with the Aventus $type response system and AventusJs integration.

ModelController is an abstract class that defines all the common CRUD operations you need for a model:

MethodHTTP VerbDescription
index()GETLists all resources
show($id)GETReturns a single resource
store()POSTCreates a new resource
storeMany()POSTCreates multiple resources
update($id)PUT/PATCHUpdates a resource
updateMany()PUT/PATCHUpdates multiple resources
destroy($id)DELETEDeletes a resource
destroyMany()DELETEDeletes multiple resources by IDs

Each method automatically returns typed resources, using the $type metadata expected by AventusJs.

<?php
namespace App\Http\Controllers;
use App\Http\Requests\UserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use Aventus\Laraventus\Controllers\ModelController;
/**
* @extends ModelController<User, UserRequest, UserResource>
*/
class UserController extends ModelController
{
public function defineModel(): string
{
return User::class;
}
public function defineRequest(): string
{
return UserRequest::class;
}
public function defineResource(): string
{
return UserResource::class;
}
}

This single controller gives you a complete CRUD API, including batch operations.

Instead of manually registering each route, Laraventus provides a helper method that generates all CRUD endpoints at once, including the Many operations.

To use it, import the Laraventus Route class then, register your routes:

<?php
use App\Http\Controllers\UserController;
use Aventus\Laraventus\Routes\Route;
Route::resourceWithMany('user', UserController::class);

This automatically creates the following endpoints:

HTTP VerbURIController Method
GET/userindex
GET/user/{id}show
POST/userstore
POST/user/manystoreMany
PUT/user/{id}update
PUT/user/manyupdateMany
DELETE/user/{id}destroy
DELETE/user/manydestroyMany

Each route returns a Laraventus-formatted JSON response containing $type, errors, and result.

{
"result": [
{
"$type": "App.Http.Resources.UserResource",
"name": "Alice",
"email": "alice@example.com"
},
{
"$type": "App.Http.Resources.UserResource",
"name": "Bob",
"email": "bob@example.com"
}
],
"errors": [],
"$type": "Aventus.Laraventus.Helpers.LaravelResult"
}
{
"result": {
"$type": "App.Http.Resources.UserResource",
"name": "Charlie",
"email": "charlie@example.com"
},
"errors": [],
"$type": "Aventus.Laraventus.Helpers.LaravelResult"
}

One of the biggest advantages of using Laraventus together with AventusJs is the ability to connect your backend controllers directly to the frontend, without manually writing HTTP requests or managing REST endpoints.

When you use ModelController on the backend, AventusJs can automatically generate the corresponding .lib.avt classes, giving you type-safe and reactive access to your Laravel data, right from the frontend.

import { UserController } from "../generated/app/Http/Controllers/UserController.lib.avt";
import { UserResource } from "../generated/app/Http/Resources/UserResource.lib.avt";
export class UserRAM extends AventusPhp.RamHttp<UserResource> implements Aventus.IRam {
/**
* @inheritdoc
*/
public override defineRoutes(): AventusPhp.RamQuery<UserResource> {
return new UserController();
}
}

With this code, your Ram is now connected to your backend

Laraventus allows you to add custom permission checks or access control directly at the route level, thanks to its powerful Attribute-based middleware system.

Once the AventusAttributesMiddleware has been added to the web middleware group, any controller method can declare an Attribute class that extends Aventus\Laraventus\Attributes\Middleware.

You can easily restrict access to specific endpoints by applying a custom Attribute. For example, Deny:

<?php
namespace App\Http\Controllers;
use App\Http\Middlewares\Deny;
use App\Http\Requests\UserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use Aventus\Laraventus\Controllers\ModelController;
use Aventus\Laraventus\Attributes\NoExport;
/**
* @extends ModelController<User, UserRequest, UserResource>
*/
class UserController extends ModelController
{
/**
* @return class-string<User>
*/
public function defineModel(): string
{
return User::class;
}
/**
* @return class-string<UserRequest>
*/
public function defineRequest(): string
{
return UserRequest::class;
}
/**
* @return class-string<UserResource>
*/
public function defineResource(): string
{
return UserResource::class;
}
#[Deny] // use a custom attribute to implement your permission logic
#[NoExport] // use no export because already define inside the parent class
public function index(): array
{
return parent::index();
}
}

The #[Deny] attribute is automatically detected by the AventusAttributesMiddleware.

When the route is called, the system executes the handle() method defined in your custom middleware before running the controller method.

Your middleware can prevent the controller logic to be executed.

In addition to the standard Model, Request, and Resource types, ModelController supports an optional fourth type : the Detailed Resource. This allows you to provide a richer representation of your model when fetching a single record, without overloading the data returned by list endpoints.

When listing entities (via index()), you usually want a lightweight representation for performance reasons. For example, listing users with only their id, name, and email. However, when fetching a single resource (via show($id)), you may want to include additional related data, computed attributes, or more complete metadata.

The Detailed Resource type allows you to separate these two representations cleanly, while keeping the same controller logic.

<?php
namespace App\Http\Controllers;
use App\Http\Requests\UserRequest;
use App\Http\Resources\UserResource;
use App\Http\Resources\UserResourceDetails;
use App\Models\User;
use Aventus\Laraventus\Controllers\ModelController;
/**
* @extends ModelController<User, UserRequest, UserResource, UserResourceDetails>
*/
class UserController extends ModelController
{
public function defineModel(): string
{
return User::class;
}
public function defineRequest(): string
{
return UserRequest::class;
}
public function defineResource(): string
{
return UserResource::class;
}
public function defineResourceDetails(): string
{
return UserResourceDetails::class;
}
}

When defined, ModelController automatically uses the detailed resource for:

  • show
  • update
  • store

Meanwhile, the index continue to use the standard Resource class.

When using AventusJs, this fourth type provides seamless frontend integration. If your controller defines a detailed resource, the corresponding RamHttp class automatically knows that:

  • Items retrieved via getAll use the standard Resource
  • When a single item is accessed or expanded, the RAM automatically upgrades it to the Detailed Resource, fetching additional information from the backend if it’s not already available

This behavior allows your frontend to stay reactive and efficient: users first see a lightweight list, and additional data loads transparently when an item is viewed in detail.

While ModelController provides ready-to-use CRUD behavior, it is also designed to be fully customizable. Every CRUD operation is split into two layers:

  • A public method (e.g. store, update, destroy) that handles validation, transactions, and resource conversion.
  • A protected method (ending with Action, e.g. storeAction, updateAction, destroyAction) that contains the actual business logic.

By overriding these *Action methods in your custom controller, you can inject your own logic without losing any of the built-in Laraventus behavior such as resource formatting, automatic transactions, or AventusJs integration.

MethodCalled ByPurpose
indexAction()index()Define how items are listed
showAction($id)show()Define how a single item is loaded
showManyAction(array $ids)showMany()Define how multiple items are loaded
storeAction($item)store() / storeMany()Define how new items are persisted
updateAction($item)update() / updateMany()Define how existing items are updated
destroyAction($id)destroy()Define how a single item is deleted
destroyManyAction(array $ids)destroyMany()Define how multiple items are deleted

Each of these methods is protected, so they will not be exported to the AventusJs frontend. That means you don’t need to add the #[NoExport] attribute, your backend logic remains private by default.

<?php
namespace App\Http\Controllers;
use App\Http\Requests\UserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use Aventus\Laraventus\Controllers\ModelController;
/**
* @extends ModelController<User, UserRequest, UserResource>
*/
class UserController extends ModelController
{
public function defineModel(): string
{
return User::class;
}
public function defineRequest(): string
{
return UserRequest::class;
}
public function defineResource(): string
{
return UserResource::class;
}
/**
* Customize how a new user is stored
*
* @param User $item
*/
protected function storeAction($item): void
{
// Example: hash password before saving
$item->password = bcrypt($item->password);
// Example: set default role
$item->role = 'user';
$item->save();
}
}

This override replaces the default storeAction logic with your own, while still benefiting from:

  • Automatic request validation (UserRequest)
  • Safe database transaction handling
  • Standardized response wrapping (UserResource)

All public CRUD methods in ModelController (store, update, destroy, etc.) are wrapped in database transactions. This means that if an exception occurs inside your overridden *Action method, the entire operation is automatically rolled back, ensuring your database stays consistent.

protected function updateAction($item): void
{
// Your custom logic
$item->save();
// If something goes wrong here...
throw new \Exception('Unexpected error');
// The transaction will be rolled back automatically
}

This makes ModelController both powerful and flexible, letting you adapt it to your project’s specific needs without rewriting its core logic.

To save time and maintain consistency when creating new Laraventus CRUD controller, you can use prebuilt templates available through the Aventus VS Code extension.

  1. Open the Aventus Shop
  2. Search for "Laraventus.CRUD"
  3. Click Download to add it to your template list.

This template automatically generates all the necessary files for a complete Laraventus CRUD controller including the Controller, Request, Resource, ResourceDetails - following the same structure as the examples above.

Once the template is installed, you can assign it to a Quick Action for faster access:

  1. Press Ctrl + K, Ctrl + Shift + V to open the Aventus Quick Action Manager.
  2. Check the box next to "Laraventus.CRUD".
  3. Validate your choice. The template is now available as a quick command.

When working in your Laravel project, you can now quickly generate a new HTTP function using the shortcut: Ctrl + K, Ctrl + V

This will:

  • Ask for the name of the CRUD (for example, User).
  • Prompt you to select if you need a Resource with details.
  • Automatically create all selected files under app/Http/Controllers/… using the proper Laraventus structure.

Within seconds, you’ll have a fully functional controller setup ready to use, following the Laraventus conventions and compatible with AventusJs.