Laravel Tutorials - How to Create Slugs in Laravel

How to Create Slugs in Laravel

2020-03-27 Lukas Markevičius
How to Create Slugs in Laravel featured image
Today we are going to learn how to create slugs in Laravel. You are going to learn:

Why use Slugs

If you are familiar with Laravel framework you probably know how to make a basic model with CRUD. To view a single object from a database you usually call route with a controller function show. For Laravel to know which object to show you usually have to pass its id through route. For example, if you have a Post model and if you want to view a single post, you should call route like this {{ route('post.index', $post->id) }} and the URL would be something like this: https://example.com/post/2. This is the basic way to find a post from a database and return it to the user. However, it's not the prettiest method. What I mean is for the user it would be much nicer to see a URL like this: https://example.com/post/my-first-post. That part after post/ is called slug. It can identify the object you are trying to see just like the id could. This method also would benefit you if you are looking to improve your SEO and rank higher on Google. When search engines are indexing your pages they are also analyzing your URLs which help them to identify what this page is all about. So, today we are going to learn how to create slugs in Laravel.

How to Create Slugs

Let's just say that you already have a basic model and CRUD set up. If you don't know how to do it you can read my other tutorial on How to create step by step Laravel project or you can just clone the repository and checkout to the starting point. To do so in your terminal run:
$ git clone git@github.com:LukasMarkevicius/laravel-tutorial.git

$ cd laravel-tutorial

$ git checkout create-project -b <your-branch-name>
For this example, we will use a Post model which have id, title, description and timestamps columns. What we will need next is to add an additional column named slug. We can do that by creating a new migration in your terminal:
$ php artisan make:migration add_slug_column_to_posts --table=posts

Now we can find a new migration file in database/migrations and create a new column:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddSlugColumnToPosts extends Migration
{
    /**
    * Run the migrations.
    *
    * @return void
    */
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->string('slug')->unique()->after('title');
        });
    }

    /**
    * Reverse the migrations.
    *
    * @return void
    */
    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropColumn('slug');
        });
    }
}
After we save file we can then migrate it to the database by running a command php artisan migrate in terminal. If you are getting any errors it is probably because you have already some post data in a database which gives you an error of a duplicate entry. If that is the case you can delete all post from the database and migrate then. Or if you don't know how to do that you can run this command in your terminal:
$ php artisan migrate:refresh

Which basically will rollback all of your migrations and then migrate everything back for you. Or in other words it will recreate the database.

Now after you have successfully migrated and added new column we can now add a new input field into our resources/views/post/create.blade.php file:
...
<form method="POST" action="{{ route('post.store') }}">
    @csrf

    <div class="form-group">
        <label for="title">Title</label>
        <input type="text" name="title" class="form-control" value="{{ old('title') }}" placeholder="Post title">
    </div>

    <div class="form-group">
        <label for="slug">Slug</label>
        <input type="text" name="slug" class="form-control" value="{{ old('slug') }}" placeholder="post-slug">
    </div>

    <div class="form-group">
        <label for="description">Description</label>
        <textarea name="description" rows="8" cols="80" class="form-control">{{ old('description') }}</textarea>
    </div>

    <button type="submit" class="btn btn-primary">Create Post</button>
</form>
...

Now we need to update store function which in my example can be found in app/Http/Controllers/PostController.php:

public function store(Request $request)
{
    $validatedData = $this->validate($request, [
        'title'         => 'required|min:3|max:255',
        'slug'          => 'required|min:3|max:255|unique:posts',
        'description'   => 'required|min:3'
    ]);

    $validatedData['slug'] = Str::slug($validatedData['slug'], '-');

    Post::create($validatedData);

    return redirect()->route('post.index');
}

We added a slug field to validate which also checks if a slug is unique or not. We used a method Str::slug()  to format string to slug. Don't forget to include use Illuminate\Support\Str;  at the top of the controller file. It basically changes any whitespace in to "-". Now we can try to create a new post. If everything ran smoothly you should now be able to create a post with a slug.

How to show Objects with the Slugs

Now we need to change routes for show function to find a post with a slug. We start by changing the URL inside resources/views/post/show.blade.php file which points to post.show route:

...
<div class="card-body">
    <p>{{ substr($post->description, 0, 100) }}</p>
    <a href="{{ route('post.show', $post->slug) }}" class="btn btn-primary btn-block">Read More</a>
</div>
...

Now if you have followed my previous tutorial you might remember that we created routes with Route::resource() method which creates a collection of routes for you:

+--------+-----------+------------------+--------------+---------------------------------------------+--------------+
| Domain | Method    | URI              | Name         | Action                                      | Middleware   |
+--------+-----------+------------------+--------------+---------------------------------------------+--------------+
|        | GET|HEAD  | api/user         |              | Closure                                     | api,auth:api |
|        | GET|HEAD  | post             | post.index   | App\Http\Controllers\PostController@index   | web          |
|        | POST      | post             | post.store   | App\Http\Controllers\PostController@store   | web          |
|        | GET|HEAD  | post/create      | post.create  | App\Http\Controllers\PostController@create  | web          |
|        | GET|HEAD  | post/{post}      | post.show    | App\Http\Controllers\PostController@show    | web          |
|        | PUT|PATCH | post/{post}      | post.update  | App\Http\Controllers\PostController@update  | web          |
|        | DELETE    | post/{post}      | post.destroy | App\Http\Controllers\PostController@destroy | web          |
|        | GET|HEAD  | post/{post}/edit | post.edit    | App\Http\Controllers\PostController@edit    | web          |
+--------+-----------+------------------+--------------+---------------------------------------------+--------------+

You can see that post.show, post.update, post.destroy and post.edit routes accept not just specified post id or slug but an entire post instance which is actually one of the Laravel features where Laravel will automatically inject the model instance that has an ID matching the corresponding value from the request. That's why in our controllers methods we can take post instance like this:

public function show(Post $post)
{
    return view('post.show')->withPost($post);
}

Instead of this:

public function show($id)
{
    $post = Post::find($id);

    return view('post.show')->withPost($post);
}

So by default it takes an ID, to take a slug we need to modify our app/Post.php file:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

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

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

Also note that I added 'slug' inside protected $fillable because we will need it later to mass-assign while creating a post record.

Now if we would click on a post it will redirect us to the post.show view with our newly created slug!

How to edit Slugs

If you want to be able to edit that slug we can change things just like before. Now we will change the URL of the Edit Post and Delete buttons, so inside resources/views/post/show.blade.php file:

...
<a href="{{ route('post.edit', $post->slug) }}" class="btn btn-primary">Edit Post</a>
<form action="{{ route('post.destroy', $post->slug) }}" method="post">
    @csrf
    @method('DELETE')
    <button type="submit" class="btn btn-danger mt-3">Delete</button>
</form>
...

Now we can change update() method in the PostController.php file:

...
public function update(Request $request, Post $post)
{
      $validatedData = $this->validate($request, [
        'title'         => 'required|min:3|max:255',
        'slug'          => 'required|min:3|max:255|unique:posts,id,' . $post->slug,
        'description'   => 'required|min:3'
      ]);

      $validatedData['slug'] = Str::slug($validatedData['slug'], '-');

      $post->update($validatedData);

      return redirect()->route('post.show', $post);
}
...

You can notice that we added new parameters to the validation unique rule. It is because if we wouldn't change our slug it would throw an error because we submitted the same slug. So we add additional parameters which check if the slug is unique except for the submitted slug itself if that makes any sense to you. Now, all we have to do is to add additional input field in the resources/views/post/edit.blade.php file:

...
<form method="POST" action="{{ route('post.update', $post->slug) }}">
    @csrf
    @method('PUT')

    <div class="form-group">
        <label for="title">Title</label>
        <input type="text" name="title" class="form-control" value="{{ $post->title }}">
    </div>

    <div class="form-group">
        <label for="slug">Slug</label>
        <input type="text" name="slug" class="form-control" value="{{ $post->slug }}" placeholder="post-slug">
    </div>

    <div class="form-group">
        <label for="description">Description</label>
        <textarea name="description" rows="8" cols="80" class="form-control">{{ $post->description }}</textarea>
    </div>

    <button type="submit" class="btn btn-primary">Update Post</button>
</form>
...

And that's it! You now have learned how to create slugs in Laravel. If you wish to get the source code you can get it from here. You can checkout to create-slugs tag to get to the end of this tutorial. You can do so by running command in your terminal:

$ git checkout create-slugs -b <your-branch-name>

If you have any questions or suggestions feel free to leave a comment.

How to Create Slugs in Laravel pinterest image

Related Tutorials

How to Generate a Simple XML Sitemap using Laravel

Posted 2020-03-08 Lukas Markevičius

Today we are going to learn how to generate a simple XML sitemap using Laravel. Mainly you can create it either manually or create some sort of automated solution. To create it manually you can make y...

How to create Step by Step Laravel Project

Posted 2020-03-08 Lukas Markevičius

If you are a beginner and want to learn how to start developing Laravel applications you came to the right place because today we are going to learn step-by-step how to create your first Laravel 5.6 p...

Back