Slugs for URLs tutorial

Mar 8, 2016 laravel slugs php url
WhatsApp
For those wondering how one can change the default 'id' endpoint URL format to a slug look no further. What this post will be going over is removing the 'id' in the URL with a user friendly URL (SEO friendly) from scratch but I will be skipping over the blog post creation process. With that said I will assume you have a view and form ready to submit a blog post. You'll either have to input information into the database yourself or setup a view with a form and submit the post.

By default Laravel will serve you URLs based by id. If we look inside the routes.php file under the app folder you should come across something similar to:

Route::get('user/{id}', 'PagesController@userIndex');


You can also check the route list if you open up the terminal and type php artisan route:list. Remember that you can type php artisan to bring up artisan commands you can use to remind yourself about the commands. This will print out a table for your entire routes. Below is an example of the whizbangapps website route list, you can ignore the middleware column as we won't be covering that in this article, focus your attention ont he URI column.

php artisan route:list
+--------+-----------+-------------------------+---------------+-----------------------------------------------------------------+------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+-------------------------+---------------+-----------------------------------------------------------------+------------+
| | GET|HEAD | / | | App\Http\Controllers\PagesController@getIndexView | web |
| | GET|HEAD | blog | | App\Http\Controllers\PagesController@getBlogView | web |
| | GET|HEAD | blog/{slug} | | App\Http\Controllers\PagesController@getBlogPostView | web |
| | GET|HEAD | contact | | App\Http\Controllers\PagesController@getContactView | web |
| | GET|HEAD | home | | App\Http\Controllers\HomeController@index | web,admin |
| | POST | login | | App\Http\Controllers\Auth\AuthController@login | web,guest |
| | GET|HEAD | login | | App\Http\Controllers\Auth\AuthController@showLoginForm | web,guest |
| | GET|HEAD | logout | | App\Http\Controllers\Auth\AuthController@logout | web |
| | POST | password/email | | App\Http\Controllers\Auth\PasswordController@sendResetLinkEmail | web,guest |
| | POST | password/reset | | App\Http\Controllers\Auth\PasswordController@reset | web,guest |
| | GET|HEAD | password/reset/{token?} | | App\Http\Controllers\Auth\PasswordController@showResetForm | web,guest |
| | POST | posts | posts.store | App\Http\Controllers\PostController@store | web,admin |
| | GET|HEAD | posts | posts.index | App\Http\Controllers\PostController@index | web,admin |
| | GET|HEAD | posts/create | posts.create | App\Http\Controllers\PostController@create | web,admin |
| | DELETE | posts/{posts} | posts.destroy | App\Http\Controllers\PostController@destroy | web,admin |
| | GET|HEAD | posts/{posts} | posts.show | App\Http\Controllers\PostController@show | web,admin |
| | PUT|PATCH | posts/{posts} | posts.update | App\Http\Controllers\PostController@update | web,admin |
| | GET|HEAD | posts/{posts}/edit | posts.edit | App\Http\Controllers\PostController@edit | web,admin |
| | GET|HEAD | projects | | App\Http\Controllers\PagesController@getProjectsView | web |
| | GET|HEAD | register | | App\Http\Controllers\Auth\AuthController@showRegistrationForm | web,guest |
| | POST | register | | App\Http\Controllers\Auth\AuthController@register | web,guest |
+--------+-----------+-------------------------+---------------+-----------------------------------------------------------------+------------+


You can also have Laravel create the CRUD (Create, Read, Update, Delete) routes for you with the resource function (example below) which I have also done and is shown above in the route list. You can associate create with posts.create, read with posts.show, update with posts.update and delete for posts.destroy in the URI column.

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


Before you read on, make sure you have an understanding about Routes, Controllers and the Eloquent model.

Let's Begin


Routes


Okay so let's make URLs that like your-domain.com/blog/1 look user friendly by applying changes to the route. Let's pretend we're making a new awesome blogging website. Keep in mind we're using the GET method on all these routes as we want to retrieve what is at the route.

First we need to create the route for users to access the blog where we'll 'get' or view the list of posts.
Route::get('blog/' ['uses => 'PagesController@showPosts']);


We'll also need another route to 'get' or view a single post. Later we'll substitute the slug for the title of the post.
Route::get('blog/{slug}' ['uses => 'PagesController@showSinglePost']);


PagesController is an existing controller used for all the pages on our website.

Database/Migrations


Open up the terminal and we'll need to add a slug to our posts table. Head to the root of your project and type the following

php artisan make:migration update_posts_table_with_slug


public function up()
{
Schema::table('posts', function(Blueprint $table) {
$table->string('slug')->unqiue();
});
}


Next run the command
php artisan migrate

and this will update your table with the new 'slug' column!

Read on if you want to create a table from scratch otherwise you can skip to the Controller section:
php artisan make:migration create_posts_table

Artisan will create a migration in your Database > Migrations folder. It'll have the date prefixed to the name you have chosen in our case it's create_post_table. Open it up and you should have a class with two functions up() and down(). Up to create your table and it's attributes and down to drop tables.

We'll need to create the columns for our table which are the features our post will have. Such as a title, body, date and time, tags, category etc whatever you want to add to this table you'll add it here. For the purpose of this post we'll make it simple and create the title, body, date and slug.

public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('body');
$table->string('slug');
$table->timestamps();
});
}


What you see above is our columns in the posts table. For those that don't know, timestamps() creates two date columns, one column for created_at and the other updated_at. The rest is pretty self-explanatory but as to why we are using text for the body and not string is due to the size of the data-type. I believe string is a VARCHAR but only holds 255 characters. Or more column types you'll want to check the Laravel Migration docs.

Once we're done here we'll migrate the table with
php artisan migrate


And you should get feedback command line saying it was successful!

Controller


Next open your Controller folder in your favourite text editor (I'm using Sublime). We should already have a PostController, as I said at the beginning of the tutorial I won't be going over how to create a post and submit. When a user submits a post via your form this is the stage where we'll grab the title and convert it to a slug. So let's head over to the PostsController.php file. Locate the store function and we'll add our slug there in a single line.

$post->public function store(Request $request)
{
....
$post->slug = str_slug($request->title, '-');
$post->save;
//return the post data
}

The function str_slug is a helper function provided by Laravel which takes two arguments. The text you want to change and the delimiter you wish to use. In our case, $request->title and '-'.

Now we've saved it to the database, we need to get the slug from the database. We'll be using an existing controller that controls all our pages, so head to the PagesController.php file. We've stated in our route at the beginning of the tutorial that it uses PagesController we'll also need to create the related functions showPosts and showSinglePost.

class PagesController extends Controller {
...
public function showPosts() {
//return an array of posts
}
public function showSinglePost() {
...
}


The way this works is that because we've saved the slug into the database, in the view showPosts we'll have retrieve the URLs contained with slugs e.g. www.example.com/blog/your-slug. When a user clicks on this URL, we'll enter the showSinglePost function. Remembering the route we created earlier
Route::get('blog/{slug}' ['uses => 'PagesController@showSinglePost']);


If the URL follows the route above we'll enter the showSinglePost function. Now that we understand, we can begin to fill in the showSinglePost function

public function showSinglePost($slug) {
//find slug and return post
$post = Post::where('slug', '=', $slug)->firstOrFail();
// return var_dump($post);
return view('pages.blogPost')->with('post', $post);
}


If we were clicking on the link www.example.com/blog/your-slug. "your-slug" will be passed into the function as $slug. Next we check our database with the where function and find the first appearance of the match and assign it to $post. Finally we return the $post to our view.

And with that we have created our slugs!

TL;DR


Create slug in your route
Route::get('blog/{slug}' ['uses => 'PagesController@showSinglePost']);

Match slug in your database and return the post
public function showSinglePost($slug) {
//find slug and return post
$post = Post::where('slug', '=', $slug)->firstOrFail();
// return var_dump($post);
return view('pages.blogPost')->with('post', $post);
}


Enjoy!

If you found the following info helpful, I'm happy to accept any donations of the following cryptocurrencies.

  • Bitcoin - 17DTiPExzP9StqveW428acEyB4mVMfKbiK
  • Ethereum - 0x87B8307FD20dc90cc05c94905Ec593134D32B6FF
  • Litecoin - LZMiz5U5sVq9doMLYE3gfLJrxCQDKuyCmU
  • Neo - AXv71WB38ajc1KUUEnxQKhynLLPc4BapVb