In the previous article, we built the foundation for what I called Laravel Blade Partial API Pattern. The idea was simple—to make every Blade partial directly accessible through an API-style URL using HTMX, without writing individual controller methods or route definitions.
That worked fine until we hit one problem: data.
The route knew how to locate and render a partial, but it didn’t know what data that partial needed. Some partials required a single record (like a product card), others needed multiple records (like a product list), and a few needed data built from complex DTO classes. This article is about that missing piece—how to fetch and pass the correct data automatically.
The Problem Recap
The old global route could do this much:
/products/partials/product-card/2
→ find
resources/views/products/partials/product-card.blade.php
→ load view
→ done
That’s it.
It didn’t care whether that partial needed one product, a list of products, or a composed object with multiple models. We’re now going to fix that.
The Rules (The Contract Between View and Data)
To make the system predictable and maintainable, there need to be rules. After experimenting, I came up with six clear steps that define how a partial route resolves data.
- Find the correct partial to load
Every request like
should map directly to/products/partials/product-card/2.resources/views/products/partials/product-card.blade.php - Find the resource from the first segment
The first segment
is always the model reference. It’s converted to singular formproductsto locate the model:product.App\Models\Product - Decide if one or more records are required
This comes from the filename prefix.
Starts with
→ multiple records Starts withproducts-→ single recordproduct- - Extract the DTO or ViewModel annotation
At the top of each partial, I can define what data structure it expects:
This tells the route what class to instantiate and which columns the AQC should select./** @var \App\ViewModels\ProductCardDTO $model */. - Fetch the data using AQC Once the DTO is known, the system calls the AQC Design Pattern class to select the appropriate columns and fetch records from the corresponding model.
- Return the rendered HTML Finally, the resolved data is passed to the partial and returned as raw HTML — ideal for HTMX swaps or inline updates.
Example in Action
Let’s take these partials:
/resources/views/products/partials/products-list.blade.php // multiple
/resources/views/products/partials/product-card.blade.php // single
/resources/views/products/partials/product-row.blade.php // single
/resources/views/products/partials/product-quick-view.blade.php // single
And these URLs:
/products/partials/products-list
/products/partials/product-card/2
/products/partials/product-row/2
/products/partials/product-quick-view/2
Here’s the global route refactored.
use App\Helpers\PartialApiResolver;
Route::match(['get', 'post'], '{path}', function(string $path, Request $request){
return App\Helpers\PartialApiResolver::handle($path, $request);
})->where('path', '.*');
And here's the helper class with all 6 steps.
<?php
namespace App\Helpers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class PartialApiResolver
{
public static function handle(string $path, Request $request)
{
$path = trim($path, '/');
if ($path === '') abort(404);
$segments = explode('/', $path);
$id = self::extractId($segments);
$viewPath = self::viewPath($segments);
$resourceName = self::detectResource($segments);
$dtoClass = self::extractDto($segments);
$data = self::fetchData($resourceName, $id, $dtoClass, $request);
return view($viewPath, $data);
}
private static function extractId(array &$segments): ?int
{
$maybeLast = end($segments);
if (is_numeric($maybeLast)) {
array_pop($segments);
return (int) $maybeLast;
}
return null;
}
private static function viewPath(array $segments): string
{
return implode('.', $segments);
}
private static function detectResource(array $segments): ?string
{
$resourceSegment = $segments[0] ?? null;
return $resourceSegment ? Str::singular($resourceSegment) : null;
}
private static function extractDto(array $segments): ?string
{
$bladeFile = resource_path('views/' . implode('/', $segments) . '.blade.php');
if (!file_exists($bladeFile)) return null;
$contents = file_get_contents($bladeFile);
if (preg_match('/@var\s+([A-Za-z0-9_\\\\<>]+)\s+\$[A-Za-z0-9_]+/m', $contents, $m)) {
return trim($m[1]);
}
return null;
}
private static function fetchData(?string $resource, ?int $id, ?string $dto, Request $request): array
{
if (!$resource) return [];
$aqcNamespace = "App\\AQC\\" . ucfirst($resource);
$columns = $dto && method_exists($dto, 'columns') ? $dto::columns() : '*';
try {
if ($id) {
$aqcClass = "{$aqcNamespace}\\Get" . ucfirst($resource);
if (!class_exists($aqcClass)) abort(404, "AQC class [$aqcClass] not found.");
$item = $aqcClass::handle($id, $request->all(), $columns);
return [$resource => $item ?: new ("App\\Models\\" . ucfirst($resource))];
} else {
$aqcClass = "{$aqcNamespace}\\Get" . Str::plural($resource);
if (!class_exists($aqcClass)) abort(404, "AQC class [$aqcClass] not found.");
$items = $aqcClass::handle($request->all(), $columns);
return [Str::plural($resource) => $items];
}
} catch (\Throwable $e) {
return $id
? [$resource => new ("App\\Models\\" . ucfirst($resource))]
: [Str::plural($resource) => collect()];
}
}
}
Implementation Notes
Here's what we have done so far.
- Detect plural or singular pattern to decide between collection or single model.
- Check for numeric ID in the URL (if found, load a single record).
- Parse the partial file to extract the DTO or ViewModel class name from annotations.
- Pass model and DTO into your AQC pipeline to build queries dynamically.
- Graceful fallback: if a record is missing, provide a new model instance instead of throwing an error.
This way, even if you hit a partial like /product/partials/product-card/9999, it still renders without breaking the flow.
Why This Works So Well
Because it combines the predictability of Laravel’s Blade conventions with the flexibility of APIs. HTMX requests behave like AJAX calls but stay fully server-driven. Your AQC pattern handles the data efficiently. And your Blade partials now serve as self-contained, declarative view components that know what data they need.
You don’t have to touch controllers, define routes, or mix JSON and HTML responses. The system just knows what to do.
Final Thoughts
This pattern now feels complete. The first part gave partials their own routes. This part gave them data.
What you have now is a unified layer where every Blade partial can act like an API endpoint — with model discovery, DTO integration, and automatic data fetching.
This is what I have experienced recently. What is your take on this approach? Let's hear your ideas and thoughts.