Simplifying Route Structures with Laravel's Shallow Nesting Resources

Simplifying Route Structures with Laravel's Shallow Nesting Resources

When building RESTful APIs or web applications with Laravel, you often deal with nested resources. While full nesting can lead to verbose and complex routes, Laravel offers a elegant solution: shallow nesting. This feature allows you to create more intuitive and cleaner route structures. Let's dive into how you can leverage shallow nesting in your Laravel applications.

Understanding Nested Resources

Before we explore shallow nesting, let's recall how nested resources typically look:

Route::resource('posts.comments', CommentController::class);

This generates routes like:

  • GET /posts/{post}/comments
  • POST /posts/{post}/comments
  • GET /posts/{post}/comments/{comment}
  • PUT /posts/{post}/comments/{comment}
  • DELETE /posts/{post}/comments/{comment}

While this is logically structured, it can lead to overly long URLs, especially for deeply nested resources.

Introducing Shallow Nesting

Shallow nesting provides a way to simplify these routes. Here's how you implement it:

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

This generates the following routes:

  • GET /posts/{post}/comments
  • POST /posts/{post}/comments
  • GET /comments/{comment}
  • PUT /comments/{comment}
  • DELETE /comments/{comment}

Notice how the routes for individual comments no longer include the post ID in the URL.

Benefits of Shallow Nesting

  1. Shorter URLs: Especially beneficial for deeply nested resources.
  2. Improved Readability: Routes become more intuitive and easier to understand.
  3. Reduced Complexity: Simplifies route handling in controllers.
  4. Better Resource Independence: Individual resource actions don't unnecessarily depend on parent resources.

Implementing Shallow Nesting

In Routes

// routes/web.php or routes/api.php

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

In Controllers

Your controller methods will now reflect this simplified structure:

class CommentController extends Controller
{
    public function index($postId)
    {
        // List comments for a specific post
    }

    public function store(Request $request, $postId)
    {
        // Create a new comment for a specific post
    }

    public function show($id)
    {
        // Show a specific comment (no need for $postId)
    }

    public function update(Request $request, $id)
    {
        // Update a specific comment
    }

    public function destroy($id)
    {
        // Delete a specific comment
    }
}

Real-World Example: Blog with Posts and Comments

Let's consider a more comprehensive example of a blog system with posts and comments:

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

// PostController.php
class PostController extends Controller
{
    public function show($id)
    {
        $post = Post::findOrFail($id);
        return view('posts.show', compact('post'));
    }
}

// CommentController.php
class CommentController extends Controller
{
    public function store(Request $request, $postId)
    {
        $post = Post::findOrFail($postId);
        $comment = $post->comments()->create($request->validated());
        return redirect()->route('posts.show', $post);
    }

    public function update(Request $request, $id)
    {
        $comment = Comment::findOrFail($id);
        $comment->update($request->validated());
        return redirect()->route('posts.show', $comment->post);
    }
}

In this example, creating a comment is still associated with a specific post, but updating a comment doesn't require the post ID in the route.

Handling Relationships

When using shallow nesting, you might need to adjust how you handle relationships in your controllers:

public function update(Request $request, $id)
{
    $comment = Comment::findOrFail($id);
    
    // If you need to check the associated post
    $post = $comment->post;
    
    // Perform authorization
    $this->authorize('update', [$comment, $post]);
    
    $comment->update($request->validated());
    
    return redirect()->route('posts.show', $post);
}

Testing Shallow Nested Routes

When writing tests for shallow nested routes, make sure to use the correct URL structure:

public function test_can_create_comment()
{
    $post = Post::factory()->create();
    
    $response = $this->post("/posts/{$post->id}/comments", [
        'content' => 'A new comment',
    ]);
    
    $response->assertRedirect();
    $this->assertDatabaseHas('comments', ['content' => 'A new comment']);
}

public function test_can_update_comment()
{
    $comment = Comment::factory()->create();
    
    $response = $this->put("/comments/{$comment->id}", [
        'content' => 'Updated comment',
    ]);
    
    $response->assertRedirect();
    $this->assertDatabaseHas('comments', ['id' => $comment->id, 'content' => 'Updated comment']);
}

Shallow nesting in Laravel provides a powerful way to simplify your route structure, especially when dealing with nested resources. By reducing URL complexity and improving route clarity, it can lead to more maintainable and intuitive APIs. Remember to consider your application's specific needs when deciding between full nesting and shallow nesting, and don't hesitate to mix both approaches where appropriate.

If this guide was helpful to you, subscribe to my daily newsletter and give me a follow on X/Twitter. It helps a lot!

Subscribe to Harris Raftopoulos

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe