Introduction

Resource controllers map the seven standard CRUD actions to predictable routes with a single line. This lesson covers Route::resource, partial resources, nested resources, shallow nesting, and API resource variants.

Key Concepts

  • Route::resource: Registers seven RESTful routes for index/create/store/show/edit/update/destroy.
  • Route::apiResource: Same without create and edit — ideal for APIs that do not serve HTML forms.
  • only() / except(): Partial resource registration.
  • Nested resources: posts.comments generates /posts/{post}/comments/... routes.
  • Shallow nesting: ->shallow() flattens child routes that do not need the parent ID.

Real World Context

Every Laravel CRUD screen is built on resource controllers. Following the convention means a new developer reading Route::resource('posts', PostController::class) already knows every URL, every method name, and every route name.

Deep Dive

Resource controllers map the typical CRUD operations to controller methods with a single line of route definition. This follows RESTful conventions.

Creating a Resource Controller

bash
php artisan make:controller PostController --resource

This generates a controller with all CRUD methods:

php
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     */
    public function show(Post $post)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(Post $post)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, Post $post)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(Post $post)
    {
        //
    }
}

Registering Resource Routes

One line creates all 7 routes:

php
Route::resource('posts', PostController::class);

This creates:

VerbURIActionRoute Name
GET/postsindexposts.index
GET/posts/createcreateposts.create
POST/postsstoreposts.store
GET/posts/{post}showposts.show
GET/posts/{post}/editeditposts.edit
PUT/PATCH/posts/{post}updateposts.update
DELETE/posts/{post}destroyposts.destroy

Partial Resources

Only include certain methods:

php
// Only these methods
Route::resource('posts', PostController::class)->only([
    'index', 'show'
]);

// All except these
Route::resource('posts', PostController::class)->except([
    'create', 'store', 'edit', 'update', 'destroy'
]);

API Resource Controllers

For APIs, you don't need create and edit (those return forms):

bash
php artisan make:controller API/PostController --api

Register API routes:

php
// Only 5 routes (no create/edit)
Route::apiResource('posts', PostController::class);

// Multiple API resources
Route::apiResources([
    'posts' => PostController::class,
    'comments' => CommentController::class,
]);

Nested Resources

For parent-child relationships:

php
// posts/1/comments, posts/1/comments/5
Route::resource('posts.comments', CommentController::class);

Controller receives both models:

php
class CommentController extends Controller
{
    public function show(Post $post, Comment $comment)
    {
        // Both $post and $comment are injected
    }
}
Shallow Nesting

Use parent ID only when necessary:

php
Route::resource('posts.comments', CommentController::class)->shallow();

This creates:

  • /posts/{post}/comments (index, create, store)
  • /comments/{comment} (show, edit, update, destroy)

Naming Resource Routes

Customize route names:

php
Route::resource('posts', PostController::class)->names([
    'create' => 'posts.build',
    'store' => 'posts.save',
]);

// Or prefix all names
Route::resource('posts', PostController::class)->names('admin.posts');
// admin.posts.index, admin.posts.create, etc.

Custom Route Parameters

Change the parameter name:

php
Route::resource('users', UserController::class)->parameters([
    'users' => 'admin_user'
]);
// /users/{admin_user}

Implementing a Complete Resource Controller

php
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class PostController extends Controller
{
    public function index(): View
    {
        $posts = Post::latest()->paginate(10);

        return view('posts.index', ['posts' => $posts]);
    }

    public function create(): View
    {
        return view('posts.create');
    }

    public function store(StorePostRequest $request): RedirectResponse
    {
        $post = Post::create($request->validated());

        return redirect()->route('posts.show', $post)
            ->with('success', 'Post created successfully!');
    }

    public function show(Post $post): View
    {
        return view('posts.show', ['post' => $post]);
    }

    public function edit(Post $post): View
    {
        return view('posts.edit', ['post' => $post]);
    }

    public function update(UpdatePostRequest $request, Post $post): RedirectResponse
    {
        $post->update($request->validated());

        return redirect()->route('posts.show', $post)
            ->with('success', 'Post updated successfully!');
    }

    public function destroy(Post $post): RedirectResponse
    {
        $post->delete();

        return redirect()->route('posts.index')
            ->with('success', 'Post deleted successfully!');
    }
}

Common Pitfalls

  1. Using Route::resource for an API — You get create and edit routes serving nonexistent HTML forms. Use Route::apiResource.
  2. Deeply nested resources — posts.comments.replies generates ugly URLs. Flatten with ->shallow().

Best Practices

  1. Stick to the resource convention — It makes your app predictable and tools like route:list more useful.
  2. Use --model=Post --requests — Generates a controller prewired for route model binding and form requests.

Summary

  • Route::resource maps CRUD to seven routes in one call.
  • Route::apiResource is the five-route stateless variant.
  • only() and except() register a subset.
  • Nested resources share URL segments with their parents.
  • ->shallow() flattens deep nesting.
✓ Completed