DEV Community

Cover image for Mastering Scoped Resource Routes in Laravel: A Practical Guide
Asfia Aiman
Asfia Aiman

Posted on

Mastering Scoped Resource Routes in Laravel: A Practical Guide

Laravel’s scoped implicit model binding is a game-changer for developers working with nested resources. It ensures that child models resolved through nested routes are automatically validated to belong to their parent models. In this blog, we’ll explore how to use the scoped method with a fresh example involving Blog and Post models, discuss when to use it, and explain why it’s a great choice for your Laravel applications. We’ll also include practical code examples to bring the concept to life.

Understanding Scoped Implicit Model Binding

Scoped implicit model binding simplifies handling nested resources by ensuring the child model (e.g., a post) belongs to the parent model (e.g., a blog). This feature eliminates the need for manual validation in your controllers, making your code cleaner and more secure. For example, when accessing a URL like /blogs/{blog}/posts/{post}, Laravel ensures the resolved Post belongs to the specified Blog.

When to Use Scoped Resource Routes

You should consider using scoped resource routes when:

  • Working with Nested Resources: Your application has parent-child relationships, such as blogs and posts, users and profiles, or categories and products.
  • Enforcing Data Integrity: You need to ensure that child resources are only accessible in the context of their parent to prevent unauthorized access.
  • Using Custom Keys: You want to bind child resources using fields other than the default id, such as a slug for SEO-friendly URLs.
  • Simplifying Controller Logic: You want to avoid writing repetitive relationship checks in your controllers.
  • Building RESTful APIs or Web Routes: Scoped routes work seamlessly for both web and API routes, ensuring consistent behavior.

Why Scoped Resource Routes Are a Good Idea

Using scoped resource routes offers several benefits:

  1. Enhanced Security: Automatically validates that the child resource belongs to the parent, preventing users from accessing unrelated resources.
  2. Cleaner Code: Eliminates boilerplate code for relationship checks, making controllers more concise and maintainable.
  3. SEO-Friendly URLs: Supports custom keys like slug, enabling readable and search-engine-optimized URLs (e.g., /blogs/tech-blog/posts/laravel-tips).
  4. Consistency: Leverages Laravel’s conventions to guess relationship names, reducing configuration overhead.
  5. Error Handling: Automatically returns a 404 error if the child resource doesn’t belong to the parent, saving you from manual error handling.

Example: Scoped Routes with Blogs and Posts

Let’s walk through a complete example using Blog and Post models to demonstrate scoped resource routes.

1. Define the Models

Assume you have a Blog model with a one-to-many relationship to Post:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Blog extends Model
{
    protected $fillable = ['title', 'slug'];

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}
Enter fullscreen mode Exit fullscreen mode

And a Post model:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = ['title', 'slug', 'content'];

    public function blog()
    {
        return $this->belongsTo(Blog::class);
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Create the Controller

Create a BlogPostController to handle post-related operations:

namespace App\Http\Controllers;

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

class BlogPostController extends Controller
{
    public function show(Blog $blog, Post $post)
    {
        return view('posts.show', compact('blog', 'post'));
    }

    public function store(Request $request, Blog $blog)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
        ]);

        $post = $blog->posts()->create([
            'title' => $validated['title'],
            'slug' => \Str::slug($validated['title']),
            'content' => $validated['content'],
        ]);

        return redirect()->route('blogs.posts.show', [$blog, $post]);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Define the Routes

In your routes/web.php file, define the scoped nested resource:

use App\Http\Controllers\BlogPostController;

Route::resource('blogs.posts', BlogPostController::class)->scoped([
    'post' => 'slug',
]);
Enter fullscreen mode Exit fullscreen mode

This configuration registers routes like:

  • GET /blogs/{blog}/posts/{post:slug} for displaying a post.
  • POST /blogs/{blog}/posts for creating a new post.

4. Create a View

Create a Blade view (resources/views/posts/show.blade.php) to display the post:

<h1>Blog: {{ $blog->title }}</h1>
<h2>Post: {{ $post->title }}</h2>
<p>{{ $post->content }}</p>
<a href="{{ route('blogs.posts.index', $blog) }}">Back to Posts</a>
Enter fullscreen mode Exit fullscreen mode

5. Testing the Route

With this setup, a URL like /blogs/tech-blog/posts/laravel-tips will:

  • Resolve the Blog with the slug tech-blog.
  • Resolve the Post with the slug laravel-tips, ensuring it belongs to the specified blog.
  • Display the post details in the view.

If the post doesn’t belong to the blog, Laravel throws a 404 error automatically.

Advanced Example: Custom Relationship Name

If your relationship name doesn’t match the plural of the route parameter (e.g., Blog has an articles relationship instead of posts), you can specify it explicitly:

Route::resource('blogs.articles', BlogPostController::class)->scoped([
    'article' => 'slug',
    'relationship' => 'articles',
]);
Enter fullscreen mode Exit fullscreen mode

Here, Laravel uses the articles relationship on the Blog model to scope the query.

Practical Use Case: Why It Matters

Imagine you’re building a blogging platform where users can access posts only within the context of a specific blog. Without scoped binding, you’d need to manually verify the relationship in your controller:

public function show($blogId, $postSlug)
{
    $blog = Blog::findOrFail($blogId);
    $post = Post::where('slug', $postSlug)->where('blog_id', $blogId)->firstOrFail();
    return view('posts.show', compact('blog', 'post'));
}
Enter fullscreen mode Exit fullscreen mode

With scoped implicit model binding, Laravel handles this for you, reducing the code to:

public function show(Blog $blog, Post $post)
{
    return view('posts.show', compact('blog', 'post'));
}
Enter fullscreen mode Exit fullscreen mode

This not only saves time but also reduces the risk of errors and improves code readability.

Conclusion

Scoped resource routes in Laravel are a powerful tool for building robust, secure, and maintainable applications with nested resources. By using the scoped method, you can enforce parent-child relationships, support custom keys like slugs, and simplify your controller logic. Whether you’re building a blog, an e-commerce platform, or any application with hierarchical data, scoped routes help you write cleaner code and deliver a better user experience.

Start using scoped resource routes in your Laravel projects to take advantage of their security, simplicity, and flexibility!

Top comments (0)