Routa makes working with Laravel resource routes easy by adding dynamic URL accessors to your Eloquent models. It supports standard resource URLs (index, show, create, update edit, destroy), collection operations, nested route parameters, signed URLs, Blade directives, Artisan generators, and JSON export for frontend consumption.
composer require dgtlss/routaphp artisan vendor:publish --tag=routa-configThis will create a config/routa.php file where you can customize Routa's behavior.
Let's say you have a Product model with resource routes:
// app/Models/Product.php
namespace App\Models;
use Dgtlss\Routa\Traits\ResourceRoutes;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use ResourceRoutes;
protected $guarded = [];
public static function getResourceRouteBase(): string
{
return 'products'; // This should match your route name
}
}In your routes/web.php or routes/api.php:
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::resource('products', ProductController::class);Now you can access all resource URLs directly from your model instances:
$product = Product::find(1);
// All available URLs
echo $product->index_url; // https://your-app.com/products
echo $product->create_url; // https://your-app.com/products/create
echo $product->show_url; // https://your-app.com/products/1
echo $product->edit_url; // https://your-app.com/products/1/edit
echo $product->update_url; // https://your-app.com/products/1
echo $product->destroy_url; // https://your-app.com/products/1Routa automatically generates these URL accessors for your models:
| Accessor | Description | Example Usage |
|---|---|---|
show_url |
View a single resource | $product->show_url |
edit_url |
Edit form for a resource | $product->edit_url |
update_url |
Update a resource (PUT/PATCH) | $product->update_url |
destroy_url |
Delete a resource | $product->destroy_url |
index_url |
List all resources | $product->index_url |
create_url |
Create form for new resource | $product->create_url |
Work with multiple models and include route URLs for each item:
| Method | Description | Example Usage |
|---|---|---|
routeMap() |
Get route maps for all models in collection | $products->routeMap() |
withRoutes() |
Append route URLs to each model | $products->withRoutes() |
withRoutes() |
Add routes to individual model | $product->withRoutes() |
toArrayWithRoutes() |
Get model array with routes included | $product->toArrayWithRoutes() |
Generate secure, signed URLs for public access or temporary links:
// Individual signed URLs
echo $product->signed_show_url; // Signed version of show_url
echo $product->signed_edit_url; // Signed version of edit_url
echo $product->signed_update_url; // Signed version of update_url
echo $product->signed_destroy_url; // Signed version of destroy_url
// Get all signed URLs as an array
$signedRoutes = $product->signedRouteMap();Enable Signed URLs Globally:
Set signed_urls => true in your config/routa.php file, and all URLs will automatically be signed.
Enable Signed URLs per Model:
class Product extends Model
{
use ResourceRoutes;
protected static bool $useSignedUrls = true;
// ... rest of the model
}Perfect for nested resources like users/{user}/products/{product}:
// In your routes file
Route::resource('users.products', ProductController::class);// In your Product model
class Product extends Model
{
use ResourceRoutes;
protected static array $resourceRouteParams = [
'user' => 'user_id', // 'user' is the route parameter, 'user_id' is the model attribute
];
public static function getResourceRouteBase(): string
{
return 'users.products';
}
}Now when you access URLs, Routa automatically includes the user parameter:
$product = Product::find(1);
$product->user_id = 5; // The user this product belongs to
echo $product->show_url; // https://your-app.com/users/5/products/1
echo $product->edit_url; // https://your-app.com/users/5/products/1/editUse routes directly in your Blade templates:
<!-- Basic usage -->
<a href="@route($product, 'show')">View Product</a>
<a href="@route($product, 'edit')">Edit Product</a>
<!-- Default action is 'show' if not specified -->
<a href="@route($product)">View Product</a>
<!-- Works with any action -->
<a href="@route($product, 'destroy')" method="POST">Delete</a>php artisan routa:model ProductThis creates a new model file with the ResourceRoutes trait already set up:
// app/Models/Product.php
namespace App\Models;
use Dgtlss\Routa\Traits\ResourceRoutes;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use ResourceRoutes;
protected $guarded = [];
public static function getResourceRouteBase(): string
{
return 'products';
}
}Perfect for passing URLs to your frontend JavaScript:
# Basic export
php artisan routa:export "App\Models\Product" --id=1
# Pretty-printed JSON
php artisan routa:export "App\Models\Product" --id=1 --prettyOutput:
{
"index": "https://your-app.com/products",
"create": "https://your-app.com/products/create",
"show": "https://your-app.com/products/1",
"edit": "https://your-app.com/products/1/edit",
"update": "https://your-app.com/products/1",
"destroy": "https://your-app.com/products/1"
}Get all URLs as an array - perfect for admin panels or bulk operations:
$product = Product::find(1);
// Get all standard URLs
$routes = $product->routeMap();
/*
[
'index' => 'https://your-app.com/products',
'create' => 'https://your-app.com/products/create',
'show' => 'https://your-app.com/products/1',
'edit' => 'https://your-app.com/products/1/edit',
'update' => 'https://your-app.com/products/1',
'destroy' => 'https://your-app.com/products/1'
]
*/
// Get all signed URLs
$signedRoutes = $product->signedRouteMap();
/*
[
'index' => null,
'create' => null,
'show' => 'https://your-app.com/products/1?signature=abc123...',
'edit' => 'https://your-app.com/products/1/edit?signature=abc123...',
'update' => 'https://your-app.com/products/1?signature=abc123...',
'destroy' => 'https://your-app.com/products/1?signature=abc123...'
]
*/Work with multiple models and include route URLs for each item - perfect for API responses:
// Get all products with their route URLs
$products = Product::all();
// Option 1: Get array of route maps for each product
$routeMaps = $products->routeMap();
/*
[
['show' => 'https://your-app.com/products/1', 'edit' => 'https://your-app.com/products/1/edit', ...],
['show' => 'https://your-app.com/products/2', 'edit' => 'https://your-app.com/products/2/edit', ...],
['show' => 'https://your-app.com/products/3', 'edit' => 'https://your-app.com/products/3/edit', ...]
]
*/
// Option 2: Append routes to each model (Recommended for APIs)
$productsWithRoutes = $products->withRoutes();
/*
[
{
"id": 1,
"name": "Product 1",
"price": 29.99,
"routes": {
"index": "https://your-app.com/products",
"create": "https://your-app.com/products/create",
"show": "https://your-app.com/products/1",
"edit": "https://your-app.com/products/1/edit",
"update": "https://your-app.com/products/1",
"destroy": "https://your-app.com/products/1"
}
},
{
"id": 2,
"name": "Product 2",
"price": 39.99,
"routes": {
"index": "https://your-app.com/products",
"create": "https://your-app.com/products/create",
"show": "https://your-app.com/products/2",
"edit": "https://your-app.com/products/2/edit",
"update": "https://your-app.com/products/2",
"destroy": "https://your-app.com/products/2"
}
}
]
*/Individual Model Methods:
$product = Product::find(1);
// Add routes to the model
$productWithRoutes = $product->withRoutes();
echo $productWithRoutes->routes['show']; // https://your-app.com/products/1
// Get model as array with routes included
$arrayWithRoutes = $product->toArrayWithRoutes();
// Returns: ['id' => 1, 'name' => 'Product', 'routes' => [...], ...]Publish the config file to customize Routa's behavior:
php artisan vendor:publish --tag=routa-configreturn [
/*
|--------------------------------------------------------------------------
| Route Prefix
|--------------------------------------------------------------------------
|
| This value will be prepended to all route names unless overridden
| on the model with `protected static $resourceRoutePrefix`.
| Example: 'admin' would make routes like 'admin.products.show'
|
*/
'route_prefix' => null,
/*
|--------------------------------------------------------------------------
| Verify Routes Exist
|--------------------------------------------------------------------------
|
| If enabled, Routa will check if the named route exists using
| Route::has(). If it doesn't exist, the URL will return null.
| Set to false if you want to generate URLs even when routes don't exist.
|
*/
'verify_routes_exist' => true,
/*
|--------------------------------------------------------------------------
| Fallback Prefix for Resource Route Base
|--------------------------------------------------------------------------
|
| When a model doesn't define `$resourceRouteBase`, this prefix will
| be used for auto-generated base route names like 'app.products'.
|
*/
'base_fallback_prefix' => 'app',
/*
|--------------------------------------------------------------------------
| Use Signed URLs
|--------------------------------------------------------------------------
|
| If true, Routa will generate signed URLs where applicable.
| This can be overridden per model using $useSignedUrls property.
|
*/
'signed_urls' => false,
];// config/routa.php
return [
'route_prefix' => 'admin',
];
// Your model
class Product extends Model
{
use ResourceRoutes;
// No need to specify prefix here, it will use the config
public static function getResourceRouteBase(): string
{
return 'products';
}
}
// Generated route names will be: admin.products.index, admin.products.show, etc.// config/routa.php
return [
'route_prefix' => 'api.v1',
];
// Generated route names will be: api.v1.products.index, api.v1.products.show, etc.Routa includes a helpful test assertion to verify all routes are working:
// tests/Feature/ProductTest.php
public function test_product_routes_are_valid()
{
$product = Product::factory()->create();
// This will assert that all URLs in routeMap() are valid and non-null
$this->assertRoutaRoutesValid($product);
}# Run Routa's test suite
composer test
# Or using Laravel's test runner
php artisan testPerfect for building APIs that include route URLs for frontend consumption:
// app/Http/Controllers/ProductController.php
class ProductController extends Controller
{
public function index()
{
$products = Product::all();
// Return products with route URLs included
return response()->json($products->withRoutes());
}
public function show(Product $product)
{
// Return single product with routes
return response()->json($product->withRoutes());
}
}API Response Example:
{
"data": [
{
"id": 1,
"name": "Laptop",
"price": 999.99,
"routes": {
"show": "https://api.yourapp.com/products/1",
"edit": "https://api.yourapp.com/products/1/edit",
"update": "https://api.yourapp.com/products/1",
"destroy": "https://api.yourapp.com/products/1"
}
}
]
}You can customize the route base per model:
class Product extends Model
{
use ResourceRoutes;
public static function getResourceRouteBase(): string
{
return 'catalog.items'; // Custom route base
}
}
// This will generate routes like: catalog.items.index, catalog.items.show, etc.Override global settings per model:
class Product extends Model
{
use ResourceRoutes;
// Override global route prefix
protected static string $resourceRoutePrefix = 'admin';
// Override route verification
protected static bool $verifyRoutesExist = false;
// Enable signed URLs just for this model
protected static bool $useSignedUrls = true;
public static function getResourceRouteBase(): string
{
return 'products';
}
}Handle deeply nested routes:
// routes/web.php
Route::resource('companies.departments.teams', TeamController::class);
// app/Models/Team.php
class Team extends Model
{
use ResourceRoutes;
protected static array $resourceRouteParams = [
'company' => 'company_id',
'department' => 'department_id',
];
public static function getResourceRouteBase(): string
{
return 'companies.departments.teams';
}
}
// Usage
$team = Team::find(1);
$team->company_id = 5;
$team->department_id = 10;
echo $team->show_url; // /companies/5/departments/10/teams/1If your URLs are returning null, check:
- Route Names Match: Ensure your route names match your
getResourceRouteBase()return value - Route Verification: If
verify_routes_existis true, the route must exist - Model Attributes: For nested routes, ensure the required attributes (like
user_id) are set
// Debug route names
$routeName = $product->getFullRouteName('show');
dd($routeName, Route::has($routeName));Use Laravel's route:list command to see your actual route names:
php artisan route:list --name=productEnsure you've defined the $resourceRouteParams correctly:
// Route: users/{user}/products/{product}
// Model needs:
protected static array $resourceRouteParams = [
'user' => 'user_id', // route_parameter => model_attribute
];-
routa:testgenerator for test scaffolding - Multiple-model export:
php artisan routa:export-all - Optional middleware bindings (e.g. signed + throttled)
- Support for custom route parameters
- Integration with Laravel's route caching
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
# Clone the repository
git clone https://github.com/dgtlss/routa.git
cd routa
# Install dependencies
composer install
# Run tests
composer testThe MIT License (MIT). Please see License File for more information.
If you encounter any issues or have questions, please:
- Check the troubleshooting section
- Search existing GitHub Issues
- Create a new issue if needed
Happy coding with Routa! ๐