Laresponse
Laravel trait untuk standardized JSON responses dan DTO untuk parsing query params dari frontend
Versi: 0.1.5 Changelog
TL;DR
Laresponse menyediakan dua hal: JsonResponseTrait untuk standardize format JSON response dari backend, dan ContainerQuery DTO untuk parsing query params yang dikirim oleh frontend (khususnya dari utils-data-container). Keduanya dirancang bekerja bersama sehingga backend dan frontend berbicara dalam kontrak yang sama.
Namespace:
use Bpmlib\Laresponse\Traits\JsonResponseTrait;
use Bpmlib\Laresponse\DTO\ContainerQuery;
use Bpmlib\Laresponse\DTO\BaseContainerFilter; // extend untuk typed filter per endpointResponse Methods (Quick Reference):
returnAdaptive()— ⭐ DIREKOMENDASIKAN untuk list/collection (auto-detect pagination)returnJson()— ⭐ DIREKOMENDASIKAN untuk single data atau error responsesreturnPaginateJson()— Explicit untuk LengthAware paginationreturnCursorPaginateJson()— Explicit untuk Cursor paginationreturnSimplePaginateJson()— Explicit untuk Simple pagination
Installation & Setup
Requirements
PHP Version
- Minimum: PHP 8.1
- Recommended: PHP 8.3+
Composer Dependencies
{
"illuminate/support": "^10.0|^11.0|^12.0|^13.0",
"illuminate/http": "^10.0|^11.0|^12.0|^13.0",
"illuminate/pagination": "^10.0|^11.0|^12.0|^13.0",
"illuminate/database": "^10.0|^11.0|^12.0|^13.0"
}Framework Requirements
- Laravel: 10.0+, 11.0+, 12.0+, atau 13.0+
Composer Install
composer require bpmlib/laravel-laresponseAuto-Discovery
Tidak ada service provider atau config file. Trait dan class siap digunakan setelah installation.
Quick Start
Response JSON Standar
<?php
namespace App\Http\Controllers;
use Bpmlib\Laresponse\Traits\JsonResponseTrait;
use App\Models\Product;
class ProductController extends Controller
{
use JsonResponseTrait;
public function index()
{
return $this->returnAdaptive(Product::paginate(15));
}
public function show($id)
{
$product = Product::find($id);
if (!$product) {
return $this->returnJson([], 404);
}
return $this->returnJson($product);
}
}ContainerQuery + Response
Parsing query params dari utils-data-container lalu return response yang sesuai:
use Bpmlib\Laresponse\DTO\ContainerQuery;
public function index(Request $request)
{
$q = new ContainerQuery($request);
$products = Product::query()
->when($q->hasSort(), fn($b) => $b->orderBy($q->sortBy, $q->sortDir))
->when($q->hasSearch(), fn($b) => $b->where('name', 'like', "%{$q->search}%"))
->paginate($q->effectivePerPage());
return $this->returnAdaptive($products);
}Core Concepts
Standardized Response Structure
Semua response mengikuti struktur yang sama sehingga frontend selalu bisa expect field yang konsisten.
Base structure:
[
'message' => string, // Human-readable, otomatis dari HTTP code jika kosong
'content' => mixed, // Data utama
'success' => bool, // true jika HTTP 2xx
]Dengan pagination:
[
// + base fields
'max_page' => ?int,
'current_page' => ?int,
'per_page' => ?int,
'total' => ?int,
'has_more' => ?bool,
'next_cursor' => ?string,
'previous_cursor' => ?string,
]Field pagination yang tidak relevan diisi null — semua mode mengembalikan key yang sama. Ini memudahkan frontend mendeteksi mode paginasi dari response shape.
Default messages per HTTP code:
| Code | Message |
|---|---|
| 200 | Sukses |
| 201 | Sukses dibuat |
| 400 | Pastikan format request yang Anda kirimkan sesuai |
| 401 | Anda belum login |
| 403 | Anda tidak mempunyai akses untuk ini |
| 404 | Data yang anda cari tidak ada |
| 422 | Form yang anda kirimkan ada yang tidak valid |
| 500 | Terjadi kesalahan di server |
| 503 | Server sedang sibuk |
Code di luar tabel menggunakan fallback 'Hai :D'.
ContainerQuery & URL Params
ContainerQuery mem-parse query params yang dihasilkan oleh getterUrlStringAttribute() / getterObjectAttribute() dari utils-data-container. Kontrak params-nya:
perPage=10
page=2
cursor=abc123
useCursor=1
sortBy=name
sortDir=asc
filter[status]=active
filter[tags][]=php
filter[tags][]=js
q=keyword ← nama param search bisa dikustomisasi (default: 'q')Filter diparse Laravel secara otomatis menjadi nested array, lalu dibersihkan dari nilai kosong (null, string kosong, array kosong) — sama persis dengan deepCleanFilter di sisi JS.
paginationMode() mengembalikan:
'cursor'— jikacursoratauuseCursor=1ada'numeric'— jikapageada (berlaku untukpaginate()maupunsimplePaginate(), backend yang memilih)'none'— tidak ada params paginasi
Semua nama param di atas bisa dikustomisasi via constructor jika nama param dari frontend berbeda dari kontrak utils-data-container. Lihat selengkapnya
Append Mechanism
Parameter $appends memungkinkan merge data tambahan ke response tanpa bisa menimpa key bawaan.
return $this->returnAdaptive($products, appends: [
'meta' => ['api_version' => '2.0'],
'message' => 'Ini akan diabaikan', // key protected, tidak bisa di-override
]);Key yang dilindungi: message, content, success, validation, dan semua key pagination.
Data Mapping Pattern
Mapper dipanggil per item — untuk transformasi presentation layer sebelum data dikirim ke frontend.
return $this->returnAdaptive($users, mapper: fn($u) => [
'id' => $u->id,
'name' => $u->name,
'avatar' => $u->profile?->avatar_url ?? '/default.png',
]);Berlaku untuk array, Collection, dan semua tipe Paginator.
API Reference
JsonResponseTrait
Namespace: Bpmlib\Laresponse\Traits\JsonResponseTrait
Contains:
- returnAdaptive()
- returnJson()
- returnPaginateJson()
- returnCursorPaginateJson()
- returnSimplePaginateJson()
returnAdaptive()
Auto-detect tipe data dan return response yang sesuai. Direkomendasikan untuk semua list/collection responses.
Signature:
protected function returnAdaptive(
Collection|LengthAwarePaginator|CursorPaginator|Paginator|array $content,
string $message = '',
int $code = 200,
?callable $mapper = null,
array $appends = [],
array $returnHeaders = [],
int $returnOptions = JSON_THROW_ON_ERROR
): JsonResponseParameters
| Name | Type | Default | Description |
|---|---|---|---|
$content | Collection|LengthAwarePaginator|CursorPaginator|Paginator|array | - | Data yang akan di-return |
$message | string | '' | Custom message; kosong = default per HTTP code |
$code | int | 200 | HTTP status code |
$mapper | ?callable | null | Transform callback Lihat selengkapnya |
$appends | array | [] | Data tambahan Lihat selengkapnya |
$returnHeaders | array | [] | Custom HTTP headers |
$returnOptions | int | JSON_THROW_ON_ERROR | JSON encoding options |
Returns: JsonResponse
$mapper
Callback untuk transform setiap item sebelum dikirim ke response.
Signature:
callable(mixed $item): mixedContoh:
$this->returnAdaptive($users, mapper: fn($u) => [
'id' => $u->id,
'name' => $u->name,
]);Use Case: Transformasi presentation layer — hide sensitive fields, compute derived properties, format tanggal.
$appends
Array data tambahan yang di-merge ke response. Built-in keys tidak bisa di-override.
Contoh:
$this->returnAdaptive($products, appends: [
'meta' => ['version' => '2.0'],
'filters' => $request->only(['category']),
]);returnJson()
Base method untuk single data, errors, atau validation errors.
Signature:
protected function returnJson(
mixed $content = [],
int $code = 200,
string $message = '',
array|MessageBag $validationErrors = [],
?callable $mapper = null,
array $appends = [],
array $returnHeaders = [],
int $returnOptions = JSON_THROW_ON_ERROR
): JsonResponseParameters
| Name | Type | Default | Description |
|---|---|---|---|
$content | mixed | [] | Data utama |
$code | int | 200 | HTTP status code |
$message | string | '' | Custom message |
$validationErrors | array|MessageBag | [] | Validation errors Lihat selengkapnya |
$mapper | ?callable | null | Transform callback — sama dengan returnAdaptive |
$appends | array | [] | Data tambahan — sama dengan returnAdaptive |
$returnHeaders | array | [] | Custom HTTP headers |
$returnOptions | int | JSON_THROW_ON_ERROR | JSON encoding options |
Returns: JsonResponse
$validationErrors
Validation errors dari Laravel Validator. Hanya muncul di response jika $code == 422.
Accepted formats:
// MessageBag (dari Validator)
$this->returnJson([], 422, validationErrors: $validator->errors());
// Array manual
$this->returnJson([], 422, validationErrors: [
'email' => ['Email sudah digunakan'],
]);returnPaginateJson()
Explicit method untuk LengthAwarePaginator (->paginate()).
Signature:
protected function returnPaginateJson(
LengthAwarePaginator $content,
int $code = 200,
string $message = 'Success',
?callable $mapper = null,
array $appends = [],
array $returnHeaders = [],
int $returnOptions = JSON_THROW_ON_ERROR
): JsonResponseParameters
| Name | Type | Default | Description |
|---|---|---|---|
$content | LengthAwarePaginator | - | Data hasil ->paginate() |
$code | int | 200 | HTTP status code |
$message | string | 'Success' | Custom message |
$mapper | ?callable | null | Sama dengan returnAdaptive |
$appends | array | [] | Sama dengan returnAdaptive |
$returnHeaders | array | [] | Custom HTTP headers |
$returnOptions | int | JSON_THROW_ON_ERROR | JSON encoding options |
Returns: JsonResponse — max_page, current_page, total, has_more terisi; cursor fields null.
returnCursorPaginateJson()
Explicit method untuk CursorPaginator (->cursorPaginate()).
Signature:
protected function returnCursorPaginateJson(
CursorPaginator $content,
int $code = 200,
string $message = 'Success',
?callable $mapper = null,
array $appends = [],
array $returnHeaders = [],
int $returnOptions = JSON_THROW_ON_ERROR
): JsonResponseParameters
| Name | Type | Default | Description |
|---|---|---|---|
$content | CursorPaginator | - | Data hasil ->cursorPaginate() |
$code | int | 200 | HTTP status code |
$message | string | 'Success' | Custom message |
$mapper | ?callable | null | Sama dengan returnAdaptive |
$appends | array | [] | Sama dengan returnAdaptive |
$returnHeaders | array | [] | Custom HTTP headers |
$returnOptions | int | JSON_THROW_ON_ERROR | JSON encoding options |
Returns: JsonResponse — next_cursor, previous_cursor, has_more, per_page terisi; numeric fields null.
returnSimplePaginateJson()
Explicit method untuk Paginator (->simplePaginate()).
Signature:
protected function returnSimplePaginateJson(
Paginator $content,
int $code = 200,
string $message = 'Success',
?callable $mapper = null,
array $appends = [],
array $returnHeaders = [],
int $returnOptions = JSON_THROW_ON_ERROR
): JsonResponseParameters
| Name | Type | Default | Description |
|---|---|---|---|
$content | Paginator | - | Data hasil ->simplePaginate() |
$code | int | 200 | HTTP status code |
$message | string | 'Success' | Custom message |
$mapper | ?callable | null | Sama dengan returnAdaptive |
$appends | array | [] | Sama dengan returnAdaptive |
$returnHeaders | array | [] | Custom HTTP headers |
$returnOptions | int | JSON_THROW_ON_ERROR | JSON encoding options |
Returns: JsonResponse — current_page, has_more, per_page terisi; total, max_page, cursor fields null.
ContainerQuery
Namespace: Bpmlib\Laresponse\DTO\ContainerQuery
Class untuk mem-parsing query params standar yang dikirim oleh utils-data-container. Semua properties bersifat readonly — nilai ditetapkan di constructor dan tidak bisa diubah.
Contains:
- Properties
- fromRequest()
- paginationMode()
- isNumericPagination()
- isCursorPagination()
- isNotPaginated()
- hasSort() / hasSearch() / hasCursor() / hasFilter()
- effectivePerPage()
- filterValue()
- resolveFilter()
- defaultParamKeys()
Constructor
NOTE
Parameter kedua diperbarui di v0.1.5 menjadi string|array. Penggunaan string tetap bekerja seperti sebelumnya.
public function __construct(Request $request, string|array $param = 'q')Parameters
| Name | Type | Default | Description |
|---|---|---|---|
$request | Request | - | Incoming HTTP request |
$param | string|array | 'q' | String: set nama param search. Array: override nama param. Lihat selengkapnya |
$param
String shorthand mengubah hanya nama param search. Array mengoverride key manapun — key yang tidak didefinisikan tetap menggunakan default kontrak utils-data-container. Dot-notation didukung untuk param nested.
Default keys:
| Field | Default param |
|---|---|
perPage | perPage |
page | page |
cursor | cursor |
useCursor | useCursor |
sortBy | sortBy |
sortDir | sortDir |
filter | filter |
search | q |
Contoh:
new ContainerQuery($request, 'keyword'); // string — search saja
new ContainerQuery($request, ['sortBy' => 'orderBy', 'search' => 'keyword']); // array — override beberapa key
new ContainerQuery($request, ['sortDir' => 'order.direction']); // dot-notation didukungProperties
| Property | Type | Description |
|---|---|---|
$perPage | ?int | Jumlah item per halaman; null jika tidak dikirim |
$page | ?int | Nomor halaman (numeric pagination); null jika tidak dikirim |
$cursor | ?string | Cursor value; null jika tidak dikirim |
$useCursor | bool | true jika frontend set useCursor=1 |
$sortBy | ?string | Kolom untuk sort; null jika tidak dikirim |
$sortDir | string | Arah sort, selalu 'asc' atau 'desc' |
$filter | array | Cleaned filter dari filter[*] params |
$search | ?string | Search term; null jika tidak dikirim atau kosong |
$searchParam | string | Nama param yang digunakan untuk search; derived dari $param |
fromRequest()
Static factory — alternatif untuk constructor.
public static function fromRequest(Request $request, string|array $param = 'q'): staticpaginationMode()
public function paginationMode(): string // 'numeric'|'cursor'|'none'Mendeteksi mode paginasi dari params yang dikirim. 'numeric' berarti ada page — backend yang memilih antara paginate() (LengthAware) atau simplePaginate().
isNumericPagination()
public function isNumericPagination(): booltrue jika paginationMode() === 'numeric'.
isCursorPagination()
public function isCursorPagination(): booltrue jika paginationMode() === 'cursor'.
isNotPaginated()
public function isNotPaginated(): booltrue jika tidak ada params paginasi — gunakan ->get().
hasSort() / hasSearch() / hasCursor() / hasFilter()
public function hasSort(): bool // $sortBy tidak null
public function hasSearch(): bool // $search tidak null
public function hasCursor(): bool // $cursor tidak null
public function hasFilter(): bool // $filter tidak kosongeffectivePerPage()
public function effectivePerPage(int $default = 15): intMengembalikan $perPage jika ada, atau $default jika tidak.
| Name | Type | Default | Description |
|---|---|---|---|
$default | int | 15 | Fallback per-page jika tidak dikirim frontend |
filterValue()
Mengakses nilai filter dengan dot-notation. Jika $strict = true, $default diabaikan — method akan throw jika key tidak ada.
public function filterValue(string $key, mixed $default = null, bool $strict = false): mixedParameters
| Name | Type | Default | Description |
|---|---|---|---|
$key | string | - | Dot-notation path ke filter (e.g. 'user.role') |
$default | mixed | null | Nilai jika key tidak ada (diabaikan saat $strict = true) |
$strict | bool | false | Jika true, throw OutOfBoundsException saat key tidak ada |
Throws: OutOfBoundsException jika key tidak ada dan $strict = true
Contoh:
$q->filterValue('status'); // null jika tidak ada
$q->filterValue('status', 'active'); // 'active' sebagai fallback
$q->filterValue('user.role'); // nested: filter[user][role]
$q->filterValue('status', strict: true); // throw jika tidak adaresolveFilter()
NEW v0.1.5
Hydrate $this->filter ke typed filter DTO. IDE menginfer return type dari class yang dipassing via @template.
public function resolveFilter(string $filterClass): BaseContainerFilterParameters
| Name | Type | Default | Description |
|---|---|---|---|
$filterClass | class-string<BaseContainerFilter> | - | Subclass BaseContainerFilter yang akan di-hydrate |
Returns: Instance $filterClass dengan properties terisi dari $this->filter.
defaultParamKeys()
NEW v0.1.5
Protected method yang mengembalikan default mapping nama field DTO ke nama query param. Override di subclass untuk mengubah default secara global tanpa harus pass $param setiap instantiasi.
protected function defaultParamKeys(): arrayReturns: array<string, string>
Contoh override:
class LegacyProductQuery extends ContainerQuery
{
protected function defaultParamKeys(): array
{
return array_merge(parent::defaultParamKeys(), [
'sortBy' => 'orderBy',
'sortDir' => 'orderDirection',
]);
}
}BaseContainerFilter
NEW v0.1.5
Namespace: Bpmlib\Laresponse\DTO\BaseContainerFilter
Abstract class untuk typed filter DTO per endpoint. Extend dan implementasikan mapFilterFields() untuk mendefinisikan mapping dari raw filter array ke typed properties.
Contains:
mapFilterFields()
abstract public static function mapFilterFields(array $data): array;Menerima cleaned filter array dari ContainerQuery::$filter dan mengembalikan named array yang di-spread ke constructor subclass.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
$data | array | - | Cleaned filter array dari ContainerQuery::$filter |
Returns: Named array untuk new static(...mapFilterFields($data)).
Contoh implementasi:
readonly class ProductFilter extends BaseContainerFilter
{
public function __construct(
public ?string $status = null,
public ?string $categoryId = null,
) {}
public static function mapFilterFields(array $data): array
{
return [
'status' => $data['status'] ?? null,
'categoryId' => $data['category_id'] ?? null, // snake_case → camelCase
];
}
}Examples
Contains:
- 1. Single Data & Error Responses
- 2. List dengan Pagination (Adaptive)
- 3. Explicit Pagination Methods
- 4. ContainerQuery — Parsing & Response
- 5. ContainerQuery — Nested Filter (Ad-hoc)
- 6. Data Mapper & Appends
- 7. Validation Errors
- 8. Typed Filter dengan BaseContainerFilter ✨ NEW
- 9. Custom Param Keys ✨ NEW
1. Single Data & Error Responses
use Bpmlib\Laresponse\Traits\JsonResponseTrait;
class UserController extends Controller
{
use JsonResponseTrait;
public function show($id)
{
$user = User::find($id);
if (!$user) {
return $this->returnJson([], 404);
}
return $this->returnJson($user);
}
public function destroy($id)
{
$product = Product::find($id);
if (!$product) {
return $this->returnJson([], 404);
}
if (!auth()->user()->can('delete', $product)) {
return $this->returnJson([], 403);
}
$product->delete();
return $this->returnJson([], 200, 'Product berhasil dihapus');
}
}2. List dengan Pagination (Adaptive)
returnAdaptive() menangani semua tipe — array, Collection, dan semua tipe Paginator:
public function index()
{
// LengthAware
return $this->returnAdaptive(Product::paginate(15));
// Cursor
return $this->returnAdaptive(Post::cursorPaginate(20));
// Simple
return $this->returnAdaptive(Product::simplePaginate(15));
// Collection / array
return $this->returnAdaptive(Product::all());
}Atau jika tipe bisa berubah secara dinamis:
public function index(Request $request)
{
$results = $request->boolean('all')
? Product::all()
: Product::paginate(15);
return $this->returnAdaptive($results);
}3. Explicit Pagination Methods
Gunakan explicit methods jika API contract sudah fix atau ingin type safety:
// Admin dashboard: butuh total & max pages
public function adminIndex()
{
return $this->returnPaginateJson(Product::paginate(15));
}
// Infinite scroll feed
public function feed()
{
return $this->returnCursorPaginateJson(Post::latest()->cursorPaginate(20));
}
// Mobile listing: tidak butuh total count
public function mobileIndex()
{
return $this->returnSimplePaginateJson(Product::simplePaginate(15));
}4. ContainerQuery — Parsing & Response
Parsing semua query params dari utils-data-container sekaligus:
use Bpmlib\Laresponse\DTO\ContainerQuery;
public function index(Request $request)
{
$q = new ContainerQuery($request);
$query = Product::query()
->when($q->hasSort(), fn($b) => $b->orderBy($q->sortBy, $q->sortDir))
->when($q->hasSearch(), fn($b) => $b->where('name', 'like', "%{$q->search}%"));
$results = match ($q->paginationMode()) {
'cursor' => $query->cursorPaginate($q->effectivePerPage()),
'numeric' => $query->paginate($q->effectivePerPage()),
default => $query->get(),
};
return $this->returnAdaptive($results);
}5. ContainerQuery — Nested Filter (Ad-hoc)
filterValue() cocok untuk akses cepat atau filter nested dengan dot-notation, tanpa perlu mendefinisikan filter class:
public function index(Request $request)
{
$q = new ContainerQuery($request);
$products = Product::query()
->when($q->filterValue('status'), fn($b, $v) => $b->where('status', $v))
->when($q->filterValue('user.role'), fn($b, $v) => $b->where('role', $v)) // nested: filter[user][role]
->paginate($q->effectivePerPage());
return $this->returnAdaptive($products);
}Untuk filter per endpoint yang typed dan IDE-autocomplete, lihat Example 8.
6. Data Mapper & Appends
Transform data per item dan tambahkan metadata ke response:
public function index(Request $request)
{
$q = new ContainerQuery($request);
$products = Product::with('category')->paginate($q->effectivePerPage());
return $this->returnAdaptive(
content: $products,
mapper: fn($p) => [
'id' => $p->id,
'name' => $p->name,
'price' => $p->price,
'category' => $p->category->name,
'available' => $p->stock > 0,
],
appends: [
'meta' => ['timestamp' => now()->toIso8601String()],
]
);
}7. Validation Errors
Field validation hanya muncul jika $code == 422:
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|min:3',
'email' => 'required|email|unique:users',
]);
if ($validator->fails()) {
return $this->returnJson(
content: [],
code: 422,
validationErrors: $validator->errors()
);
}
$user = User::create($request->validated());
return $this->returnJson($user, 201);
}Output (422):
{
"message": "Form yang anda kirimkan ada yang tidak valid",
"content": [],
"success": false,
"validation": {
"email": ["The email has already been taken."]
}
}8. Typed Filter dengan BaseContainerFilter
Typed filter per endpoint — akses via properties, IDE-autocomplete, tidak ada magic strings:
use Bpmlib\Laresponse\DTO\BaseContainerFilter;
readonly class ProductFilter extends BaseContainerFilter
{
public function __construct(
public ?string $status = null,
public ?string $categoryId = null,
public array $tags = [],
) {}
public static function mapFilterFields(array $data): array
{
return [
'status' => $data['status'] ?? null,
'categoryId' => $data['category_id'] ?? null,
'tags' => $data['tags'] ?? [],
];
}
}public function index(Request $request)
{
$q = new ContainerQuery($request);
$filter = $q->resolveFilter(ProductFilter::class);
$products = Product::query()
->when($filter->status, fn($b, $v) => $b->where('status', $v))
->when($filter->categoryId, fn($b, $v) => $b->where('category_id', $v))
->when($filter->tags, fn($b, $v) => $b->whereIn('tag', $v))
->when($q->hasSort(), fn($b) => $b->orderBy($q->sortBy, $q->sortDir))
->paginate($q->effectivePerPage());
return $this->returnAdaptive($products);
}9. Custom Param Keys
Ketika nama param dari frontend tidak sesuai kontrak default utils-data-container:
// Per-request: override inline
$q = new ContainerQuery($request, ['sortBy' => 'orderBy', 'sortDir' => 'orderDirection', 'search' => 'keyword']);// Per-class: subclass dengan override permanen — tidak perlu pass param setiap kali
class LegacyProductQuery extends ContainerQuery
{
protected function defaultParamKeys(): array
{
return array_merge(parent::defaultParamKeys(), [
'sortBy' => 'orderBy',
'sortDir' => 'orderDirection',
'search' => 'keyword',
]);
}
}
$q = new LegacyProductQuery($request);Laravel Integration
Penggunaan di Controllers
Pattern yang disarankan — pasang di base controller agar semua controllers punya akses:
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Routing\Controller as BaseController;
use Bpmlib\Laresponse\Traits\JsonResponseTrait;
abstract class Controller extends BaseController
{
use AuthorizesRequests;
use JsonResponseTrait;
}ContainerQuery cukup di-instantiate di dalam method yang membutuhkan:
public function index(Request $request)
{
$q = new ContainerQuery($request);
// ...
}