Skip to content

Routes

The fastest way to register every CRUD route is the Route::fastApiResource macro the package adds to the router — it registers the full set in one line.

php
use App\Http\Controllers\PostController;

Route::fastApiResource('posts', PostController::class);

Restrict the actions, and customise the route parameter name or route-name prefix:

php
// Only a subset
Route::fastApiResource('posts', PostController::class, ['only' => ['index', 'show']]);

// Everything except some actions
Route::fastApiResource('posts', PostController::class, ['except' => ['delete', 'restoreAll']]);

// Custom {id} parameter name and route-name prefix
Route::fastApiResource('posts', PostController::class, [
    'parameter' => 'post',
    'names'     => 'admin.posts',
]);

Generated routes

MethodURIActionRoute nameDescription
GET/postsindexposts.indexPaginated list
POST/postsstoreposts.storeCreate resource
DELETE/postsdeleteposts.deleteBulk delete via delete_rows
POST/posts/restorerestoreAllposts.restoreAllRestore all soft-deleted
PATCH/posts/{id}/status/{column}updateColumnposts.updateColumnUpdate an allowlisted column
PATCH/posts/{id}/statuschangeStatusposts.changeStatusToggle a boolean column
PATCH/posts/{id}/restorerestoreposts.restoreRestore one soft-deleted record
DELETE/posts/{id}/forcepermanentDeleteposts.permanentDeletePermanently delete
GET/posts/{id}showposts.showSingle resource
PUT / PATCH/posts/{id}updateposts.updateUpdate resource
DELETE/posts/{id}destroyposts.destroySoft (or force) delete

Collection routes are registered before the {id} routes so a bare {id} segment never shadows a static sibling such as /posts/restore.

Registering routes manually

Prefer to wire routes yourself? Register the same set explicitly — keep the collection routes above the {id} routes, and match the verbs/URIs so permissionMiddleware() and your clients line up with the macro:

php
use App\Http\Controllers\PostController;

Route::get('posts', [PostController::class, 'index'])->name('posts.index');
Route::post('posts', [PostController::class, 'store'])->name('posts.store');
Route::delete('posts', [PostController::class, 'delete'])->name('posts.delete');
Route::post('posts/restore', [PostController::class, 'restoreAll'])->name('posts.restoreAll');
Route::patch('posts/{id}/status/{column}', [PostController::class, 'updateColumn'])->name('posts.updateColumn');
Route::patch('posts/{id}/status', [PostController::class, 'changeStatus'])->name('posts.changeStatus');
Route::patch('posts/{id}/restore', [PostController::class, 'restore'])->name('posts.restore');
Route::delete('posts/{id}/force', [PostController::class, 'permanentDelete'])->name('posts.permanentDelete');
Route::get('posts/{id}', [PostController::class, 'show'])->name('posts.show');
Route::match(['put', 'patch'], 'posts/{id}', [PostController::class, 'update'])->name('posts.update');
Route::delete('posts/{id}', [PostController::class, 'destroy'])->name('posts.destroy');

Web controllers

fastApiResource also works with a BaseWebController. It registers the same actions, but web controllers additionally define create and edit form endpoints, which the macro does not register — add those yourself (or use Route::resource for the standard CRUD verbs and fastApiResource with only for the extras):

MethodURIActionDescription
GET/posts/createcreateShow create form
GET/posts/{id}/editeditShow edit form

Request Bodies

store / update

Send validated fields as JSON (API) or form data (Web):

json
{
  "name": "New Post",
  "desc": "Description",
  "status": 1,
  "active": 1
}

Only fields listed in the model's $fillable array are persisted (others are silently stripped). Models using $guarded fall back to the actual table columns.

delete (bulk)

json
{
  "delete_rows": [1, 2, 3]
}

Each ID is validated to exist in the model's table. The field name (delete_rows) and the maximum number of IDs are configurable via fast-api.bulk.

changeStatus

No body needed. Toggles the target column (default status) between 0 and 1.

updateColumn

Send the column value in the request body:

json
{
  "status": 1
}

The {column} segment is checked against the controller's $updatableColumns allowlist (default ['status']) — a column that is not allowlisted returns 403, even if it is fillable. Add columns to $updatableColumns to expose them.

Released under the MIT License.