Refactoring routes & controllers on the nested resource in Laravel

A nested resource is a resource with another sub-resource (eg: one-to-many relationship). For instance, a podcast may have multiple episodes. The nested resource of podcast and episodes can be defined as below in Laravel:

use App\Http\Controllers\PodcastEpisodesController;

Route::resource('podcast.episodes', PodcastEpisodesController::class);

The URIs defined in route will be like this:

/podcast/{podcast}/episodes/{episode}

A similar example in official Laravel 8.x documentation here: https://laravel.com/docs/8.x/controllers#restful-nested-resources

Here are 4 tips shared by Adam Wathan on strategies to split large controllers into multiple small controllers.

Tip 1: Give nested resources a dedicated controller

Instead of creating a custom methods in PodcastController or EpisodeController, we can create a dedicated controller which is PodcastEpisodesController. In this controller, we can place index method to view all episodes under a specific podcast and place create & store methods to view create episode page and store the new episode under a podcast.

Route::get('/podcasts/{id}/episodes',     'PodcastEpisodesController@index');
Route::post('/podcasts/{id}/episodes',    'PodcastEpisodesController@store');
Route::get('/podcasts/{id}/episodes/new', 'PodcastEpisodesController@create');

Tip 2: Treat properties edited independently as separate resources

Resources exposed through your controllers and endpoints don’t have to map one-to-one with your models or database tables.

If a property of a model is being updated separately, we can introduce another controller specifically for this property. In the example given by Adam, he created PodcastCoverImageController and utilize the update method.

Tip 3: Treat pivot models as their own resource

Sometimes, a pivot model can be treated as an entirely new model. In the example shown by Adam, a podcast can be subscribed by multiple users and a user can subscribe to many podcasts. Instead of having podcast_user pivot table, he introduce a new model which is subscription model to replace the pivot table. With that, he can treat subscription as their own resource and use store and destroy methods to add and remove subscription.

- Route::post('/podcasts/{id}/subscribe', 'PodcastsController@subscribe');
+ Route::post('/subscriptions',           'SubscriptionsController@store');
- Route::post('/podcasts/{id}/unsubscribe', 'PodcastsController@subscribe');
+ Route::delete('/subscriptions/{id}',      'SubscriptionsController@destroy');

Tip 4: Think of different states as different resources

Even for different states such as publish or unpublish (published_at column in table), we can think of it as different resources. We can introduce a new controller called PublishedPodcastsController. In this controller, we utilize store method to update the published_at column and destroy method to remove the value in published_at column.

- Route::post('/podcasts/{id}/publish', 'PodcastsController@publish');
+ Route::post('/published-podcasts',    'PublishedPodcastsController@store');
- Route::post('/podcasts/{id}/unpublish',   'PodcastsController@unpublish');
+ Route::delete('/published-podcasts/{id}', 'PublishedPodcastsController@destroy');