quonain/smart-response
SmartResponse is a Laravel package that returns JSON API responses or Blade/Inertia views from the same controller method, auto-detecting request type (Accept header, /api routes, bearer tokens). Includes pagination, response shortcuts, macros, caching, and meta enrichment.
SmartResponse is a production-ready Laravel package that returns API JSON or Blade / Inertia web views from the same controller method — with automatic request-type detection.
| Category | Capabilities |
|---|---|
| Detection | Accept header, expectsJson(), /api/* routes, Bearer tokens (Sanctum / Passport) |
| API | Standard JSON envelope, optional XML, validation errors, exception handler |
| Web | Blade views, redirects, session flash, optional toast |
| Pagination | Length-aware, simple, cursor paginators + API Resources |
| DX | Trait, Facade, global helpers, response()->smart* macros |
| HTTP shortcuts | created, noContent, notFound, unauthorized, forbidden |
| Meta | Auto timestamp, request_id, optional api_version on API responses |
| Extras | Inertia.js, Livewire, i18n, caching, logging, events, OpenAPI examples |
| Framework | Laravel 10 · 11 · 12 · 13 · PHP 8.2+ |
^8.2^10.0 · ^11.0 · ^12.0 · ^13.0composer require quonain/smart-response
Or pin the latest 1.x release:
composer require quonain/smart-response:^1.1
Laravel auto-discovers the service provider — no manual registration.
# Configuration
php artisan vendor:publish --tag=smart-response-config
# Translations
php artisan vendor:publish --tag=smart-response-lang
Packagist: After a new release, open your package page and click Update if Composer does not see the latest tag yet. Enable the GitHub hook under package settings for automatic sync.
use Illuminate\Http\Request;
use Quonain\SmartResponse\Traits\HasSmartResponse;
class UserController extends Controller
{
use HasSmartResponse;
public function index(Request $request)
{
$users = User::paginate(15);
return $this->smartResponse(
request: $request,
data: UserResource::collection($users),
view: 'users.index',
message: 'users.fetched', // translation key (optional)
);
}
public function store(Request $request)
{
$user = User::create($request->validated());
return $this->smartCreated($user, 'users.created');
}
}
| Request type | Result |
|---|---|
API (Accept: application/json, /api/*, Bearer token, …) |
Standard JSON |
Web (text/html, normal browser) |
Blade view users.index with $data, $message, … |
{
"success": true,
"message": "Users fetched successfully",
"data": [],
"meta": {
"timestamp": "2026-05-21T12:00:00+00:00",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"current_page": 1,
"per_page": 15,
"total": 100
},
"errors": null
}
SmartResponse treats a request as API when any of these match (configurable in config/smart-response.php):
Accept contains application/json or application/vnd.api+jsonAccept contains application/xml or text/xmlapi/* or configured prefixes (api by default)expectsJson() is true (AJAX, etc.)Authorization: Bearer … is present (detection.bearer_as_api — ideal for Sanctum / Passport SPA or mobile apps)Otherwise the request is handled as Web (view or redirect).
smartResponse()return $this->smartResponse(
request: $request,
data: $data,
view: 'users.index',
viewData: ['title' => 'Users'],
message: 'Success',
success: true,
errors: null,
meta: ['custom' => 'value'],
status: 200,
route: 'users.index', // web redirect
routeParameters: [],
format: null, // auto: json | xml
flash: true,
toast: false,
cacheKey: null,
cacheTtl: null,
headers: ['X-Custom' => '1'],
inertiaComponent: 'Users/Index',
useInertia: false,
useLivewire: false,
);
| Trait method | Facade / Manager | Status | Use case |
|---|---|---|---|
smartSuccess() |
SmartResponse::success() |
200 | OK with data |
smartCreated() |
SmartResponse::created() |
201 | Resource created |
smartNoContent() |
SmartResponse::noContent() |
204 | Delete / empty OK |
smartError() |
SmartResponse::error() |
4xx/5xx | Generic error |
smartNotFound() |
SmartResponse::notFound() |
404 | Missing resource |
smartUnauthorized() |
SmartResponse::unauthorized() |
401 | Not logged in |
smartForbidden() |
SmartResponse::forbidden() |
403 | No permission |
smartValidationError() |
SmartResponse::validationError() |
422 | Form / API validation |
return $this->smartSuccess($users, 'Users loaded');
return $this->smartCreated($user, 'users.created');
return $this->smartNoContent();
return $this->smartNotFound('error.not_found');
return $this->smartUnauthorized();
return $this->smartForbidden('error.forbidden');
return $this->smartValidationError($validator->errors());
use Quonain\SmartResponse\Facades\SmartResponse;
SmartResponse::success($data, 'Done');
SmartResponse::created($data, 'Created');
SmartResponse::notFound('Not found');
// Global helpers (no trait required)
smart_response(request: $request, data: $users, view: 'users.index');
smart_created($user, 'users.created');
smart_not_found('error.not_found');
smart_rate_limit_response(retryAfter: 60);
response()->smart($data, 'OK');
response()->smartSuccess($data, 'Saved');
response()->smartError('Failed', ['code' => 'X'], 400);
response()->smartCreated($data, 'Created');
response()->smartNotFound('Not found');
Length-aware — pass a paginator; meta keys are merged automatically:
return $this->smartResponse(
request: $request,
data: User::paginate(20),
view: 'users.index',
);
Cursor — works with cursorPaginate():
return $this->smartResponse(
request: $request,
data: User::orderBy('id')->cursorPaginate(15),
);
Meta includes: per_page, path, next_cursor, prev_cursor, has_more.
// API Resource collection
return $this->smartResponse(
request: $request,
data: UserResource::collection($users),
view: 'users.index',
);
// Force or auto-detect XML
return $this->smartResponse(request: $request, data: $users, format: 'xml');
Enabled by default (meta.enabled in config). Every API response can include:
| Meta key | Source |
|---|---|
timestamp |
Current time (ISO 8601) |
request_id |
X-Request-Id header or auto UUID |
api_version |
X-API-Version header or meta.api_version config |
// config/smart-response.php
'meta' => [
'enabled' => true,
'include_timestamp' => true,
'include_request_id' => true,
'request_id_header' => 'X-Request-Id',
'include_api_version' => true,
'api_version' => '1.0',
],
Returns a standard error JSON with Retry-After header:
// Uses config defaults (429 + retry_after_seconds)
return smart_rate_limit_response();
// Custom message and seconds
return smart_rate_limit_response('Slow down', 120);
Enable in config, then cache GET API responses:
return $this->smartResponse(
request: $request,
data: $expensiveData,
cacheKey: 'users.index',
cacheTtl: 120,
);
Without cacheKey, a hash of the full URL + Accept header is used.
return $this->smartResponse(
request: $request,
message: 'User created',
route: 'users.index',
toast: true,
status: 201,
);
Register in bootstrap/app.php (Laravel 11+):
use Quonain\SmartResponse\Exceptions\Handler\SmartResponseExceptionHandler;
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (Throwable $e, $request) {
return app(SmartResponseExceptionHandler::class)->render($request, $e);
});
})
API requests receive the same JSON envelope; web requests fall through to Laravel’s default handling.
Publish config/smart-response.php and adjust:
| Key | Description |
|---|---|
api.* |
JSON keys: success, message, data, meta, errors |
detection.* |
JSON/XML accepts, route prefixes, bearer_as_api |
meta.* |
Timestamp, request ID, API version injection |
default_format |
json or xml |
status_codes.* |
Defaults for 200, 201, 204, 401, 403, 404, 422, 429, 500 |
web.* |
Flash / toast session keys, default redirect route |
inertia.enabled |
Inertia.js adapter |
livewire.enabled |
Livewire hooks |
locale.enabled |
Translate message keys via lang files |
cache.enabled |
Cache GET API responses |
logging.enabled |
Log each response |
events.enabled |
SmartResponsePreparing / SmartResponsePrepared |
rate_limit.* |
429 message and retry_after_seconds |
graphql.enabled |
GraphQL response Accept detection |
return $this->smartResponse(message: 'users.fetched');
// → lang/vendor/smart-response/en/messages.php
Built-in keys include: users.fetched, users.created, error.not_found, error.unauthorized, error.forbidden, error.rate_limit, and more.
// config/smart-response.php
'inertia' => ['enabled' => true],
return $this->smartResponse(
request: $request,
data: $users,
inertiaComponent: 'Users/Index',
useInertia: true,
);
Alias: smart.response (enabled by default)
Route::middleware('smart.response')->group(function () {
// ...
});
use Quonain\SmartResponse\Events\SmartResponsePreparing;
use Quonain\SmartResponse\Events\SmartResponsePrepared;
Event::listen(SmartResponsePreparing::class, fn ($e) => /* mutate payload */);
Event::listen(SmartResponsePrepared::class, fn ($e) => /* inspect response */);
use Quonain\SmartResponse\Support\OpenApiExample;
OpenApiExample::successExample();
OpenApiExample::errorExample();
composer install
composer test
smart-response/
├── config/smart-response.php
├── lang/en/messages.php
├── src/
│ ├── Contracts/
│ ├── Detectors/ # API vs Web detection
│ ├── DTO/
│ ├── Formatters/ # JSON, XML
│ ├── Builders/
│ ├── Services/
│ ├── Traits/ # HasSmartResponse
│ ├── Facades/
│ ├── Events/
│ ├── Exceptions/
│ ├── Http/Middleware/
│ ├── Macros/
│ └── Support/ # MetaEnricher, Pagination, i18n, …
├── tests/
├── examples/
└── README.md
See CHANGELOG.md for version history (1.1.0 — HTTP shortcuts, meta enrichment, cursor pagination, Bearer detection).
See CONTRIBUTING.md.
MIT © Quonain Ejaz. See LICENSE.
How can I help you explore Laravel packages today?