Route Model Binding in Laravel cover image

Route Model Binding in Laravel

Oliver Sarfas • April 12, 2020

programming laravel

Note: All the content in this article is current and working for Laravel 7.x. If you are using an earlier version such as 5.x or 6.x, not all of these methods will work

One of the greatest benefits of using Laravel is the plethora of "built-in" functions it comes with. One of the most used, and easiest to use, is the Routing.

Creating routes for your application is as simple as adding a line of code into your routes/web.php file.

However, what if you want something a little smarter? Let's say you have some Posts and you want to create URLs for these automatically? What if we want to use their slug property for SEO?

Let's go through this, and how simple it is to implement.

Setup 🛠

For this example we have a model, App\Post

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

We're assuming the model has timestamps and the standard laravel unsigned big integer ID field as well.

Registering some routes 🛣

The default routes/web.php files looks like this;

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

We're going to edit this, and end up with the following - we'll go through this line by line.

<?php

// List all posts
$router->get('posts', fn() => response()->data(['posts' => Post::all()]));

// Show a specific post
$router->get('posts/{post}', fn(App\Post $post) => response()->data(['post' => $post]));

What does this mean?! 🤔

Line 4 of the above example contains the route registration for the URL /posts. This will return all our Posts to the browser.

Line 7 is where things get clever. We're using a technique called Route Model Binding.

The clever bit... 💡

The {post} part of the URL is a parameter as we've written it in curly braces. This will get passed into our closure.

As we've type-hinted the App\Post, this will automatically resolve to a Model instance of App\Post.

As it stands, we can now go to URLS such as /posts/1, /posts/2, etc etc. Provided that a Post with that ID exists.

Changing from ID to a custom field

Common / Legacy Method

We can alter our App\Post model to have the following function;

<?php

public function getRouteKeyName()
{
  return 'slug'; 
}

This will tell the route model binding middleware to check the slug of the Post, instead of the default id field.

"New" method

I'm not sure when Laravel introduced this, probably 7.x, but could be a late 6.x addition - so don't hold me to that, but you can now do the Route Model Binding Key in the route declaration itself!

So our routes/web.php file will now look like this;

<?php

// List all posts
$router->get('posts', fn() => response()->data(['posts' => Post::all()]));

// Show a specific post
$router->get('posts/{post:slug}', fn(App\Post $post) => response()->data(['post' => $post]));

The addition of the :slug will tell the middleware to route to the slug instead of id on the model for us. Keeping our Model clean and simple.

Final Thoughts

Routing in most frameworks is pretty easy nowadays, however I've yet to find a solution as clean as Laravel's.

The simplicity of Route Model Binding is exquisite, and the new method of binding to a different data column makes that even easier to do!

Which of the two methods do you prefer?

Questions? Want to talk? Here are all my social channels