Guides

Routes

Give a layer its own web, API, console and broadcast routes.

withRouting() mounts a layer's route files the way Application::configure()->withRouting() does in bootstrap/app.php. Call it from the layer provider's boot() method:

functional/orders/src/Providers/OrdersServiceProvider.php
public function boot(): void
{
    $this->withRouting(
        web: __DIR__ . '/../../routes/web.php',
        api: __DIR__ . '/../../routes/api.php',
        commands: __DIR__ . '/../../routes/console.php',
        channels: __DIR__ . '/../../routes/channels.php',
        health: '/orders/up',
    );
}
ArgumentBehaviour
webLoaded in the web middleware group. A path or an array of paths.
apiLoaded in the api middleware group, under the apiPrefix prefix. A path or an array of paths.
commandsRequired when the application runs in the console.
channelsRequired on every request.
healthRegisters a GET route at this URI that returns {"status":"ok"}.
apiPrefixPrefix of the api routes. Defaults to api.

Every argument is optional, and missing files are skipped. The provider generated by osdd:layer calls withRouting() with the four standard files, so a layer gets its routes as soon as you create them.

Route files

The routes generator of osdd:layer creates the four files when you create the layer:

php artisan osdd:layer functional/orders --generators=provider,routes

For an existing layer, create the files by hand. osdd:layer refuses a layer that already exists, and with --force it rewrites the provider and the route files you already have.

functional/orders/routes/web.php
use Functional\Orders\Http\Controllers\OrderController;
use Illuminate\Support\Facades\Route;

Route::resource('orders', OrderController::class);
functional/orders/routes/api.php
use Functional\Orders\Http\Controllers\Api\OrderController;
use Illuminate\Support\Facades\Route;

Route::get('orders', [OrderController::class, 'index']); // GET /api/orders

Do not add an api prefix inside routes/api.php: withRouting() already adds it.

Console routes and schedules

A layer's routes/console.php works like the application's one. Define closure commands and schedules in it:

functional/orders/routes/console.php
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;

Artisan::command('orders:report', function () {
    $this->info('Orders report sent.');
});

Schedule::command('orders:prune-abandoned')->daily();

php artisan schedule:list shows the schedules of every layer.

Broadcast channels

functional/orders/routes/channels.php
use Functional\Orders\Models\Order;
use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('orders.{order}', function ($user, Order $order) {
    return $user->id === $order->user_id;
});

Route caching

When routes are cached (php artisan route:cache or php artisan optimize), the web, api and health routes of every layer come from the route cache: withRouting() does not load them again. The commands and channels files are still loaded, as Laravel's own withRouting() does, so the closure commands, schedules and broadcast channels of each layer keep working.

Run php artisan route:cache again after changing a layer's web.php or api.php.

Custom routes

For anything beyond these conventions, such as a domain, a name prefix or a custom middleware group, use the Route facade in boot():

use Illuminate\Support\Facades\Route;

public function boot(): void
{
    Route::middleware(['web', 'auth'])
        ->prefix('admin/orders')
        ->name('admin.orders.')
        ->group(__DIR__ . '/../../routes/admin.php');
}

Wrap these calls in if (! $this->app->routesAreCached()) to skip them once routes are cached.