Laravel 7 tutorial with Blog example

In this article we will make a Laravel blog application with the following features:

  1. Anyone can log in/register
  2. Users can be 'admin', 'author', or 'subscriber'.
  3. Authors can write/update/delete their own posts.
  4. Admin has full access to the website and can read/ write/ update/ delete any of the posts.
  5. Anyone can read these posts
  6. Users can comment on the posts (only after login)

Demo:

The demo of the application is hosted on Heroku. View Demo. By default, users are authors (so that you can see the complete demo). Your email is required to avoid spam users. Your email will neither be shared with a third party nor be used by us for sending emails. (Although if you do want to receive relevant tutorial emails from us, you can subscribe!)

Single page of demo blogging website

Source code:

The source code of this application is on Github. Code on Github. This code is open source and you can use and modify it for your projects.

Pre-requirements:

Knowledge:

This series is written with an assumption that readers are newbies to the Laravel framework but have some experience with PHP. If you don't know PHP then use my Step by step PHP tutorials series. For Laravel tutorials read Laravel Tutorials.

Softwares:

Installed PHP, Mysql, Laravel

Install Laravel:

  1. curl -sS https://getcomposer.org/installer | php
  2. sudo mv composer.phar /usr/local/bin/composer
  3. composer global require laravel/installer
  4. laravel new blog

Now change directory (in terminal) to inside blog directory and use command.

  1. composer require laravel/helpers

We will use Str::slug helper for creating slug from post title. For starting Laravel app you need to use this command

  1. php artisan serve

Content:

1. Setup database

  • Connect with MySQL database
  • Create posts and comments tables

2. Create Models

  • Create Post Model
  • Create Comment Model
  • Update User model

3. Controllers

  • PostController
  • CommentController

4. Define Web Routes

5. Build front end

  • Customize app.blade.php
  • make home view
  • Create posts
  • Show posts
  • Edit posts

6. Add TinyMCE and Make user profile

  • Add TinyMCE to posts
  • Make profile backend
  • View for profile

7. Add user profile

1. Setup Database

We will use migrations and schema builder for generating tables.

First config database by changing .env file. .env file is a hidden file in the root folder of the application (herein blog folder).

  1. DB_HOST=localhost
  2. DB_DATABASE=your_database_name
  3. DB_USERNAME=database_username
  4. DB_PASSWORD=database_password

Set your database in blog >> config >> database.php by changing value of default:

  1. <?php
  2. return [
  3. ...
  4. ...
  5. // set database
  6. 'default' => 'mysql',
  7. ...
  8. ...

For more configurations read Database configuration in Laravel.

Now make migrations, using commands:

  1. php artisan make:migration posts
  2. php artisan make:migration comments

You will get two more files in blog >> database >> migrations with postfix posts.php and comments.php. Note that there were two files already there. The first file with name 2014_10_12_000000_create_users_table.php is a generating table for users in the database. Another file with name 2019_08_19_000000_create_failed_jobs_table.php generates a table for logs.

Create table for posts:

Open file that is created by migration with suffix posts.php in migrations folder and paste this code. This code is creating a table named posts in the database.

  1. <?php
  2. use Illuminate\Database\Migrations\Migration;
  3. use Illuminate\Database\Schema\Blueprint;
  4. use Illuminate\Support\Facades\Schema;
  5. class Posts extends Migration
  6. {
  7. /**
  8. * Run the migrations.
  9. *
  10. * @return void
  11. */
  12. public function up()
  13. {
  14. // blog table
  15. Schema::create('posts', function (Blueprint $table) {
  16. $table->id();
  17. $table->unsignedBigInteger('author_id');
  18. $table->foreign('author_id')
  19. ->references('id')->on('users')
  20. ->onDelete('cascade');
  21. $table->string('title')->unique();
  22. $table->text('body');
  23. $table->string('slug')->unique();
  24. $table->boolean('active');
  25. $table->timestamps();
  26. });
  27. }
  28. /**
  29. * Reverse the migrations.
  30. *
  31. * @return void
  32. */
  33. public function down()
  34. {
  35. // drop blog table
  36. Schema::drop('posts');
  37. }
  38. }

Migration is a Laravel core class that is used for database version control. Migration class provides two functions up() and down(). up() function executes when migration runs. down() function undo the up() function and runs when we rollback last migration or reset the database.

Schema is another Laravel class that creates and modifies database tables.

  1. <?php
  2. Schema::create('posts', function (Blueprint $table) {
  3. $table->id();
  4. $table->unsignedBigInteger('author_id');
  5. $table->foreign('author_id')
  6. ->references('id')->on('users')
  7. ->onDelete('cascade');
  8. $table->string('title')->unique();
  9. $table->text('body');
  10. $table->string('slug')->unique();
  11. $table->boolean('active');
  12. $table->timestamps();
  13. });

In this code, we are calling create() function which takes two arguments. First is the database table name on which we are operating and another is the closure which is creating columns in this table. $table is an instance to this table. Laravel provides built-in functions for column types in databases. For example, $table->id(); is creating a column with name 'id' and which is a primary key (automatic increases).

For detailed knowledge read migrations and schema builder.

Create table for comments:

Similarly, paste this code in the file with suffix commets.php in the migrations folder.

  1. <?php
  2. use Illuminate\Database\Migrations\Migration;
  3. use Illuminate\Database\Schema\Blueprint;
  4. use Illuminate\Support\Facades\Schema;
  5. class Comments extends Migration
  6. {
  7. /**
  8. * Run the migrations.
  9. *
  10. * @return void
  11. */
  12. public function up()
  13. {
  14. //id, on_blog, from_user, body, at_time
  15. Schema::create('comments', function (Blueprint $table) {
  16. $table->id();
  17. $table->unsignedBigInteger('on_post');
  18. $table->unsignedBigInteger('from_user');
  19. $table->foreign('on_post')
  20. ->references('id')->on('posts')
  21. ->onDelete('cascade');
  22. $table->foreign('from_user')
  23. ->references('id')->on('users')
  24. ->onDelete('cascade');
  25. $table->text('body');
  26. $table->timestamps();
  27. });
  28. }
  29. /**
  30. * Reverse the migrations.
  31. *
  32. * @return void
  33. */
  34. public function down()
  35. {
  36. // drop comment
  37. Schema::drop('comments');
  38. }
  39. }

User table:

Migration for the user table is already written in 2014_10_12_000000_create_users_table.php file. But we also want to add roles to the user. So just add one line in this file:

  1. <?php
  2. $table->string('password', 60);
  3. // add this one line
  4. $table->enum('role',['admin','author','subscriber'])->default('author');
  5. $table->rememberToken();

Migrate to database:

Generate tables in the database by using this command:

  1. php artisan migrate

Note: If you get error this step, it is because you have not install PHP-MySQL. Check this StackOverflow answer.

This command will generate all tables in the database. Till now we have set up tables in the database for our application. After migration try this link http://localhost:8000/auth/login

2. Create models

1) Posts Model:

Now create models for our tables so that we can use eloquent ORM. Models interact with databases in a conventional manner. For details about eloquent ORM visit Eloquent ORM in Laravel 7. Create two files in the app folder with names Posts.php and Comments.php. In Posts.php write this code:

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. // Posts class instance will refer to posts table in database
  5. class Posts extends Model
  6. {
  7. //restricts columns from modifying
  8. protected $guarded = [];
  9. // posts has many comments
  10. // returns all comments on that post
  11. public function comments()
  12. {
  13. return $this->hasMany('App\Comments', 'on_post');
  14. }
  15. // returns the instance of the user who is author of that post
  16. public function author()
  17. {
  18. return $this->belongsTo('App\User', 'author_id');
  19. }
  20. }

Posts class is associated with the post table in the database. $guarded variable is used to prevent inserting/ updating some columns of the table. We want to use all columns so $guarded array is empty. comments() function is associating comments with posts via one-many relation. author() function is returning the author of the post. This association is many to one relation.

2) Comments Model:

Similarly, make a model for the comments table.

  1. <?php
  2. namespace App;
  3. use Illuminate\Database\Eloquent\Model;
  4. // Comment class instance will refer to comment table in database
  5. class Comments extends Model
  6. {
  7. //comments table in database
  8. protected $guarded = [];
  9. // user who has commented
  10. public function author()
  11. {
  12. return $this->belongsTo('App\User', 'from_user');
  13. }
  14. // returns post of any comment
  15. public function post()
  16. {
  17. return $this->belongsTo('App\Posts', 'on_post');
  18. }
  19. }

3) User Model

Modify the code of app/User.php and change it to:

  1. <?php
  2. namespace App;
  3. use Illuminate\Foundation\Auth\User as Authenticatable;
  4. use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
  5. use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
  6. class User extends Authenticatable implements AuthenticatableContract, CanResetPasswordContract
  7. {
  8. // use Authenticatable, CanResetPassword;
  9. /**
  10. * The database table used by the model.
  11. *
  12. * @var string
  13. */
  14. protected $table = 'users';
  15. /**
  16. * The attributes that are mass assignable.
  17. *
  18. * @var array
  19. */
  20. protected $fillable = ['name', 'email', 'password'];
  21. /**
  22. * The attributes excluded from the model's JSON form.
  23. *
  24. * @var array
  25. */
  26. protected $hidden = ['password', 'remember_token'];
  27. // user has many posts
  28. public function posts()
  29. {
  30. return $this->hasMany('App\Posts', 'author_id');
  31. }
  32. // user has many comments
  33. public function comments()
  34. {
  35. return $this->hasMany('App\Comments', 'from_user');
  36. }
  37. public function can_post()
  38. {
  39. $role = $this->role;
  40. if ($role == 'author' || $role == 'admin') {
  41. return true;
  42. }
  43. return false;
  44. }
  45. public function is_admin()
  46. {
  47. $role = $this->role;
  48. if ($role == 'admin') {
  49. return true;
  50. }
  51. return false;
  52. }
  53. }

Here we have added four more functions to the User class. posts() and comments() functions are associating users with posts and comments. can_post() is checking if a user can post an article or not. is_admin() function is checking if a role is admin or not.

3. Controllers

Now we will make controllers for our application. Use the following commands for generating controllers:

  1. php artisan make:controller UserController
  2. php artisan make:controller PostController
  3. php artisan make:controller CommentController

These commands will create three files in app/Http/Controllers folder. For more details about controllers read Basic Controllers in Laravel 7 and Advance Controllers in Laravel 7.

PostController:

We will send view files (HTML text) after operating requests. We will form these view files later. Open app/Http/Controllers/PostController.php file.

Show all posts:

On the home page, we want to show 5 posts with pagination for other posts. index() function of PostController class is handling root request (localhost:8000). So update index() function as:

  1. <?php
  2. public function index()
  3. {
  4. //fetch 5 posts from database which are active and latest
  5. $posts = Posts::where('active',1)->orderBy('created_at','desc')->paginate(5);
  6. //page heading
  7. $title = 'Latest Posts';
  8. //return home.blade.php template from resources/views folder
  9. return view('home')->withPosts($posts)->withTitle($title);
  10. }

Create new post:

For creating a new post, we have to send a form to the user request. First, we will check if the requested user has permissions for posting or not (only authors and admin can post) then we will return form for creating the post. Update create() function as:

  1. <?php
  2. public function create(Request $request)
  3. {
  4. //
  5. if ($request->user()->can_post()) {
  6. return view('posts.create');
  7. } else {
  8. return redirect('/')->withErrors('You have not sufficient permissions for writing post');
  9. }
  10. }

Till now we have not created a 'post.create' view. This view contains the html code of the form. We will create this view later.

Storing posts:

When the user will submit the form then we need to validate the form and then store it. For validation, we are using PostFormRequest. Create a file with name PostFormRequest.php in app/Http/Requests (you need to create this folder) and write down this code:

  1. <?php
  2. namespace App\Http\Requests;
  3. use Illuminate\Foundation\Http\FormRequest;
  4. class PostFormRequest extends FormRequest
  5. {
  6. /**
  7. * Determine if the user is authorized to make this request.
  8. *
  9. * @return bool
  10. */
  11. public function authorize()
  12. {
  13. if ($this->user()->can_post()) {
  14. return true;
  15. }
  16. return false;
  17. }
  18. /**
  19. * Get the validation rules that apply to the request.
  20. *
  21. * @return array
  22. */
  23. public function rules()
  24. {
  25. return [
  26. 'title' => 'required|unique:posts|max:255',
  27. 'title' => array('Regex:/^[A-Za-z0-9 ]+$/'),
  28. 'body' => 'required',
  29. ];
  30. }
  31. }

authorize() function checks if the user has the right to submit this form. If the user has the right for posting then received data will be validated. The rules of the validation are defined by rules() function. Multiple rules on the same data (like title) are separated by pipe (|).

We will use this validation in our PostController class. Update the store() function with this code.

  1. <?php
  2. public function store(PostFormRequest $request)
  3. {
  4. $post = new Posts();
  5. $post->title = $request->get('title');
  6. $post->body = $request->get('body');
  7. $post->slug = Str::slug($post->title);
  8. $duplicate = Posts::where('slug', $post->slug)->first();
  9. if ($duplicate) {
  10. return redirect('new-post')->withErrors('Title already exists.')->withInput();
  11. }
  12. $post->author_id = $request->user()->id;
  13. if ($request->has('save')) {
  14. $post->active = 0;
  15. $message = 'Post saved successfully';
  16. } else {
  17. $post->active = 1;
  18. $message = 'Post published successfully';
  19. }
  20. $post->save();
  21. return redirect('edit/' . $post->slug)->withMessage($message);
  22. }

Also, don't forget to include PostFormRequest in PostController.php. Update the top section of PostController.php:

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Support\Str;
  4. use Illuminate\Http\Request;
  5. use App\Posts;
  6. use App\Http\Controllers\Controller;
  7. use App\Http\Requests\PostFormRequest;
  8. class PostController extends Controller {
  9. //other code/ functions below

Show full single posts along with comments:

  1. <?php
  2. public function show($slug)
  3. {
  4. $post = Posts::where('slug',$slug)->first();
  5. if(!$post)
  6. {
  7. return redirect('/')->withErrors('requested page not found');
  8. }
  9. $comments = $post->comments;
  10. return view('posts.show')->withPost($post)->withComments($comments);
  11. }

This function is taking slug as argument and in line 3 the post is fetched from the database. If a post exits then we are fetching comments. This relation between Post model and comments is defined in comments() function in Post model.

Single page of demo blogging website

Edit Post:

Now we will send the form for editing a post:

  1. <?php
  2. public function edit(Request $request,$slug)
  3. {
  4. $post = Posts::where('slug',$slug)->first();
  5. if($post && ($request->user()->id == $post->author_id || $request->user()->is_admin()))
  6. return view('posts.edit')->with('post',$post);
  7. return redirect('/')->withErrors('you have not sufficient permissions');
  8. }

In line 3 we are fetching the post from the database and in line 4 we are checking that the user who is requesting is a valid user (admin or author of that post) or not.

Update post:

We will receive this edit form and will update the database.

  1. <?php
  2. public function update(Request $request)
  3. {
  4. //
  5. $post_id = $request->input('post_id');
  6. $post = Posts::find($post_id);
  7. if ($post && ($post->author_id == $request->user()->id || $request->user()->is_admin())) {
  8. $title = $request->input('title');
  9. $slug = Str::slug($title);
  10. $duplicate = Posts::where('slug', $slug)->first();
  11. if ($duplicate) {
  12. if ($duplicate->id != $post_id) {
  13. return redirect('edit/' . $post->slug)->withErrors('Title already exists.')->withInput();
  14. } else {
  15. $post->slug = $slug;
  16. }
  17. }
  18. $post->title = $title;
  19. $post->body = $request->input('body');
  20. if ($request->has('save')) {
  21. $post->active = 0;
  22. $message = 'Post saved successfully';
  23. $landing = 'edit/' . $post->slug;
  24. } else {
  25. $post->active = 1;
  26. $message = 'Post updated successfully';
  27. $landing = $post->slug;
  28. }
  29. $post->save();
  30. return redirect($landing)->withMessage($message);
  31. } else {
  32. return redirect('/')->withErrors('you have not sufficient permissions');
  33. }
  34. }

Delete Post:

Final function of PostController is destroy(). It deletes the post. Only the author and admin have the right to delete the post.

  1. <?php
  2. public function destroy(Request $request, $id)
  3. {
  4. //
  5. $post = Posts::find($id);
  6. if($post && ($post->author_id == $request->user()->id || $request->user()->is_admin()))
  7. {
  8. $post->delete();
  9. $data['message'] = 'Post deleted Successfully';
  10. }
  11. else
  12. {
  13. $data['errors'] = 'Invalid Operation. You have not sufficient permissions';
  14. }
  15. return redirect('/')->with($data);
  16. }

CommentController:

For simplicity, we are defining only one method for creating comments. For adding more features you can define more functions to CommentController just like we have added to PostController.

  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Comments;
  4. use App\Http\Controllers\Controller;
  5. use Illuminate\Http\Request;
  6. class CommentController extends Controller {
  7. public function store(Request $request)
  8. {
  9. //on_post, from_user, body
  10. $input['from_user'] = $request->user()->id;
  11. $input['on_post'] = $request->input('on_post');
  12. $input['body'] = $request->input('body');
  13. $slug = $request->input('slug');
  14. Comments::create( $input );
  15. return redirect($slug)->with('message', 'Comment published');
  16. }
  17. }

At this time we are not adding any code to UserController. We will use this controller for displaying posts and drafts ofindivisible authors and also for creating profiles for users.

4. Authentication and Routes

For using builtin authentication, we need to install laravel/ui and auth. This will be done by running these two commands in the terminal.

  1. composer require laravel/ui
  2. php artisan ui bootstrap --auth

These commands will generate view files for authentication.

Now we will make routes for our application. In routing, we map urls with controllers. It is done using Route class. Route class provides get(), post(), delete(), put(), all() and match() functions for different types of requests from users. Here we are using only get() and post() functions for GET and POST type requests only. For more details about routes read Basic Routing in Laravel 7 and Advance Routing in Laravel 7. Change app/routes/web.php with this code.

  1. <?php
  2. use Illuminate\Support\Facades\Route;
  3. /*
  4. |--------------------------------------------------------------------------
  5. | Web Routes
  6. |--------------------------------------------------------------------------
  7. |
  8. | Here is where you can register web routes for your application. These
  9. | routes are loaded by the RouteServiceProvider within a group which
  10. | contains the "web" middleware group. Now create something great!
  11. |
  12. */
  13. Route::get('/', 'PostController@index');
  14. Route::get('/home', ['as' => 'home', 'uses' => 'PostController@index']);
  15. //authentication
  16. // Route::resource('auth', 'Auth\AuthController');
  17. // Route::resource('password', 'Auth\PasswordController');
  18. Route::get('/logout', 'UserController@logout');
  19. Route::group(['prefix' => 'auth'], function () {
  20. Auth::routes();
  21. });
  22. // check for logged in user
  23. Route::middleware(['auth'])->group(function () {
  24. // show new post form
  25. Route::get('new-post', 'PostController@create');
  26. // save new post
  27. Route::post('new-post', 'PostController@store');
  28. // edit post form
  29. Route::get('edit/{slug}', 'PostController@edit');
  30. // update post
  31. Route::post('update', 'PostController@update');
  32. // delete post
  33. Route::get('delete/{id}', 'PostController@destroy');
  34. // display user's all posts
  35. Route::get('my-all-posts', 'UserController@user_posts_all');
  36. // display user's drafts
  37. Route::get('my-drafts', 'UserController@user_posts_draft');
  38. // add comment
  39. Route::post('comment/add', 'CommentController@store');
  40. // delete comment
  41. Route::post('comment/delete/{id}', 'CommentController@distroy');
  42. });
  43. //users profile
  44. Route::get('user/{id}', 'UserController@profile')->where('id', '[0-9]+');
  45. // display list of posts
  46. Route::get('user/{id}/posts', 'UserController@user_posts')->where('id', '[0-9]+');
  47. // display single post
  48. Route::get('/{slug}', ['as' => 'post', 'uses' => 'PostController@show'])->where('slug', '[A-Za-z0-9-_]+');
  • A get() or post() function takes two arguments. First is the url which are requested in browsers. Second is the function or function name (pointed towards a function defined in controller class).
  • Here we are using controllers. Controllers are defined in the folder app/Http/Controllers. We will make these controllers in next the section. For more detail on controllers read Basic controllers in laravel 7 and advance controllers in laravel 7.
  • PostController@index means index() function of PostController class. We will make a PostController class in next the section.
  • For authentication (in line 19), we are using the built-in authentication system of laravel. controller() function maps urls with directly function names like auth/login to login() function of AuthController class (defined in app/Http/Controllers/AuthController.php). For more on the authentication system read Authentication in laravel 7.
  • In line 25, we are using auth middleware for restricting urls for only logged-in users. Middleware provides a convenient mechanism for filtering HTTP requests entering your application. For more detail read Middlewares in Laravel 7.

5. Build front end

Before going further, copy Js and CSS folders from Github in your public folder. These are static bootstrap and TinyMCE files. We will need them for UI and TinyMCE editor.

It is time to make all view files for sending responses from controllers defined in the last part. For more information about views read Views in Laravel 7.

Modify app.blade.php

Modify the resources/views/layout/app.blade.php as follow:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <title>Blog Demo | Flowkl</title>
  8. <link href="{{ asset('/css/app.css') }}" rel="stylesheet">
  9. <!-- Fonts -->
  10. <link href='//fonts.googleapis.com/css?family=Roboto:400,300' rel='stylesheet' type='text/css'>
  11. <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
  12. <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
  13. <!--[if lt IE 9]>
  14. <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
  15. <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
  16. <![endif]-->
  17. </head>
  18. <body>
  19. <nav class="navbar navbar-default">
  20. <div class="container-fluid">
  21. <div class="navbar-header">
  22. <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
  23. <span class="sr-only">Toggle Navigation</span>
  24. <span class="icon-bar"></span>
  25. <span class="icon-bar"></span>
  26. <span class="icon-bar"></span>
  27. </button>
  28. <a class="navbar-brand" href="https://www.flowkl.com">Flowkl</a>
  29. </div>
  30. <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
  31. <ul class="nav navbar-nav">
  32. <li>
  33. <a href="{{ url('/') }}">Home</a>
  34. </li>
  35. </ul>
  36. <ul class="nav navbar-nav navbar-right">
  37. @if (Auth::guest())
  38. <li>
  39. <a href="{{ url('/auth/login') }}">Login</a>
  40. </li>
  41. <li>
  42. <a href="{{ url('/auth/register') }}">Register</a>
  43. </li>
  44. @else
  45. <li class="dropdown">
  46. <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ Auth::user()->name }} <span class="caret"></span></a>
  47. <ul class="dropdown-menu" role="menu">
  48. @if (Auth::user()->can_post())
  49. <li>
  50. <a href="{{ url('/new-post') }}">Add new post</a>
  51. </li>
  52. <li>
  53. <a href="{{ url('/user/'.Auth::id().'/posts') }}">My Posts</a>
  54. </li>
  55. @endif
  56. <li>
  57. <a href="{{ url('/user/'.Auth::id()) }}">My Profile</a>
  58. </li>
  59. <li>
  60. <a href="{{ url('/auth/logout') }}">Logout</a>
  61. </li>
  62. </ul>
  63. </li>
  64. @endif
  65. </ul>
  66. </div>
  67. </div>
  68. </nav>
  69. <div class="container">
  70. @if (Session::has('message'))
  71. <div class="flash alert-info">
  72. <p class="panel-body">
  73. {{ Session::get('message') }}
  74. </p>
  75. </div>
  76. @endif
  77. @if ($errors->any())
  78. <div class='flash alert-danger'>
  79. <ul class="panel-body">
  80. @foreach ( $errors->all() as $error )
  81. <li>
  82. {{ $error }}
  83. </li>
  84. @endforeach
  85. </ul>
  86. </div>
  87. @endif
  88. <div class="row">
  89. <div class="col-md-10 col-md-offset-1">
  90. <div class="panel panel-default">
  91. <div class="panel-heading">
  92. <h2>@yield('title')</h2>
  93. @yield('title-meta')
  94. </div>
  95. <div class="panel-body">
  96. @yield('content')
  97. </div>
  98. </div>
  99. </div>
  100. </div>
  101. <div class="row">
  102. <div class="col-md-10 col-md-offset-1">
  103. <p>Copyright © 2015 | <a href="https://www.flowkl.com">Flowkl</a></p>
  104. </div>
  105. </div>
  106. </div>
  107. <!-- Scripts -->
  108. <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  109. <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/js/bootstrap.min.js"></script>
  110. </body>
  111. </html>

Here we are adding links to the top menu. This file is the base file for all other view files. This file is using a blade template system. For more information read Blade Template in laravel 7.

Make home view

Home view is the view file (home.blade.php) which displays the list of posts with short descriptions, meta tags, and pagination (5 posts per page). This file extends app.blade.php. Change the code of home.blade.php as:

  1. @extends('layouts.app')
  2. @section('title')
  3. {{$title}}
  4. @endsection
  5. @section('content')
  6. @if ( !$posts->count() )
  7. There is no post till now. Login and write a new post now!!!
  8. @else
  9. <div class="">
  10. @foreach( $posts as $post )
  11. <div class="list-group">
  12. <div class="list-group-item">
  13. <h3><a href="{{ url('/'.$post->slug) }}">{{ $post->title }}</a>
  14. @if(!Auth::guest() && ($post->author_id == Auth::user()->id || Auth::user()->is_admin()))
  15. @if($post->active == '1')
  16. <button class="btn" style="float: right"><a href="{{ url('edit/'.$post->slug)}}">Edit Post</a></button>
  17. @else
  18. <button class="btn" style="float: right"><a href="{{ url('edit/'.$post->slug)}}">Edit Draft</a></button>
  19. @endif
  20. @endif
  21. </h3>
  22. <p>{{ $post->created_at->format('M d,Y \a\t h:i a') }} By <a href="{{ url('/user/'.$post->author_id)}}">{{ $post->author->name }}</a></p>
  23. </div>
  24. <div class="list-group-item">
  25. <article>
  26. {!! Str::limit($post->body, $limit = 1500, $end = '....... <a href='.url("/".$post->slug).'>Read More</a>') !!}
  27. </article>
  28. </div>
  29. </div>
  30. @endforeach
  31. {!! $posts->render() !!}
  32. </div>
  33. @endif
  34. @endsection
Home Page of blogging website

Create post:

Now make a new folder named posts inside the views folder and make a file named create.blade.php inside that folder. This file is rendered by the create() function inside the PostController class. This file contains a form for creating new posts. The code this file is:

  1. @extends('layouts.app')
  2. @section('title')
  3. Add New Post
  4. @endsection
  5. @section('content')
  6. <form action="/new-post" method="post">
  7. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  8. <div class="form-group">
  9. <input required="required" value="{{ old('title') }}" placeholder="Enter title here" type="text" name = "title"class="form-control" />
  10. </div>
  11. <div class="form-group">
  12. <textarea name='body'class="form-control">{{ old('body') }}</textarea>
  13. </div>
  14. <input type="submit" name='publish' class="btn btn-success" value = "Publish"/>
  15. <input type="submit" name='save' class="btn btn-default" value = "Save Draft" />
  16. </form>
  17. @endsection


csrf_token() is for cross-site security. The old() function returns old data when the form gets an error and new posts can not be added in the database.

NOTE: We will add a rich-text editor TinyMCE to create post and edit post forms in the next part.

Show Post:

Similarly, make a new file show.blade.php in the posts folder for displaying the whole post with comments on that post. This view file is rendered by show() function in PostController class. The code of this file is:

  1. @extends('layouts.app')
  2. @section('title')
  3. @if($post)
  4. {{ $post->title }}
  5. @if(!Auth::guest() && ($post->author_id == Auth::user()->id || Auth::user()->is_admin()))
  6. <button class="btn" style="float: right"><a href="{{ url('edit/'.$post->slug)}}">Edit Post</a></button>
  7. @endif
  8. @else
  9. Page does not exist
  10. @endif
  11. @endsection
  12. @section('title-meta')
  13. <p>{{ $post->created_at->format('M d,Y \a\t h:i a') }} By <a href="{{ url('/user/'.$post->author_id)}}">{{ $post->author->name }}</a></p>
  14. @endsection
  15. @section('content')
  16. @if($post)
  17. <div>
  18. {!! $post->body !!}
  19. </div>
  20. <div>
  21. <h2>Leave a comment</h2>
  22. </div>
  23. @if(Auth::guest())
  24. <p>Login to Comment</p>
  25. @else
  26. <div class="panel-body">
  27. <form method="post" action="/comment/add">
  28. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  29. <input type="hidden" name="on_post" value="{{ $post->id }}">
  30. <input type="hidden" name="slug" value="{{ $post->slug }}">
  31. <div class="form-group">
  32. <textarea required="required" placeholder="Enter comment here" name = "body" class="form-control"></textarea>
  33. </div>
  34. <input type="submit" name='post_comment' class="btn btn-success" value = "Post"/>
  35. </form>
  36. </div>
  37. @endif
  38. <div>
  39. @if($comments)
  40. <ul style="list-style: none; padding: 0">
  41. @foreach($comments as $comment)
  42. <li class="panel-body">
  43. <div class="list-group">
  44. <div class="list-group-item">
  45. <h3>{{ $comment->author->name }}</h3>
  46. <p>{{ $comment->created_at->format('M d,Y \a\t h:i a') }}</p>
  47. </div>
  48. <div class="list-group-item">
  49. <p>{{ $comment->body }}</p>
  50. </div>
  51. </div>
  52. </li>
  53. @endforeach
  54. </ul>
  55. @endif
  56. </div>
  57. @else
  58. 404 error
  59. @endif
  60. @endsection

$post and $comments variables are passed from show() function.

Single page of demo blogging website

Single Page of blogging post

Edit Post:

For editing posts, we are sending a form from edit() function in PostController class. This form is stored in posts/edit.blade.php. So make a new file edit.blade.php in resources/views/posts folder and write this code in that post:

  1. @extends('layouts.app')
  2. @section('title')
  3. Edit Post
  4. @endsection
  5. @section('content')
  6. <form method="post" action='{{ url("/update") }}'>
  7. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  8. <input type="hidden" name="post_id" value="{{ $post->id }}{{ old('post_id') }}">
  9. <div class="form-group">
  10. <input required="required" placeholder="Enter title here" type="text" name = "title" class="form-control" value="@if(!old('title')){{$post->title}}@endif{{ old('title') }}"/>
  11. </div>
  12. <div class="form-group">
  13. <textarea name='body'class="form-control">
  14. @if(!old('body'))
  15. {!! $post->body !!}
  16. @endif
  17. {!! old('body') !!}
  18. </textarea>
  19. </div>
  20. @if($post->active == '1')
  21. <input type="submit" name='publish' class="btn btn-success" value = "Update"/>
  22. @else
  23. <input type="submit" name='publish' class="btn btn-success" value = "Publish"/>
  24. @endif
  25. <input type="submit" name='save' class="btn btn-default" value = "Save As Draft" />
  26. <a href="{{ url('delete/'.$post->id.'?_token='.csrf_token()) }}" class="btn btn-danger">Delete</a>
  27. </form>
  28. @endsection

6. Add TinyMCE and Make user profile

This is the final part of this article. In this part we will first add TinyMCE to our application then we will add functions to UserController class for showing profile, posts, and drafts of a particular user and finally we will make a user profile front end.

Add TinyMCE to posts:

First, download TinyMCE from https://www.tiny.cloud/get-tiny/ and extract it in folder public/js. Now add it to your application. Change edit.blade.php as:

  1. @extends('layouts.app')
  2. @section('title')
  3. Edit Post
  4. @endsection
  5. @section('content')
  6. <script type="text/javascript" src="{{ asset('/js/tinymce/tinymce.min.js') }}"></script>
  7. <script type="text/javascript">
  8. tinymce.init({
  9. selector : "textarea",
  10. plugins : ["advlist autolink lists link image charmap print preview anchor", "searchreplace visualblocks code fullscreen", "insertdatetime media table contextmenu paste"],
  11. toolbar : "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
  12. });
  13. </script>
  14. <form method="post" action='{{ url("/update") }}'>
  15. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  16. <input type="hidden" name="post_id" value="{{ $post->id }}{{ old('post_id') }}">
  17. <div class="form-group">
  18. <input required="required" placeholder="Enter title here" type="text" name = "title" class="form-control" value="@if(!old('title')){{$post->title}}@endif{{ old('title') }}"/>
  19. </div>
  20. <div class="form-group">
  21. <textarea name='body'class="form-control">
  22. @if(!old('body'))
  23. {!! $post->body !!}
  24. @endif
  25. {!! old('body') !!}
  26. </textarea>
  27. </div>
  28. @if($post->active == '1')
  29. <input type="submit" name='publish' class="btn btn-success" value = "Update"/>
  30. @else
  31. <input type="submit" name='publish' class="btn btn-success" value = "Publish"/>
  32. @endif
  33. <input type="submit" name='save' class="btn btn-default" value = "Save As Draft" />
  34. <a href="{{ url('delete/'.$post->id.'?_token='.csrf_token()) }}" class="btn btn-danger">Delete</a>
  35. </form>
  36. @endsection

Here we are adding a TinyMCE javascript to Textarea tag for generating rich-text editor. Also change create.blade.php:

  1. @extends('layouts.app')
  2. @section('title')
  3. Add New Post
  4. @endsection
  5. @section('content')
  6. <script type="text/javascript" src="{{ asset('/js/tinymce/tinymce.min.js') }}"></script>
  7. <script type="text/javascript">
  8. tinymce.init({
  9. selector : "textarea",
  10. plugins : ["advlist autolink lists link image charmap print preview anchor", "searchreplace visualblocks code fullscreen", "insertdatetime media table contextmenu paste"],
  11. toolbar : "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
  12. });
  13. </script>
  14. <form action="/new-post" method="post">
  15. <input type="hidden" name="_token" value="{{ csrf_token() }}">
  16. <div class="form-group">
  17. <input required="required" value="{{ old('title') }}" placeholder="Enter title here" type="text" name = "title"class="form-control" />
  18. </div>
  19. <div class="form-group">
  20. <textarea name='body'class="form-control">{{ old('body') }}</textarea>
  21. </div>
  22. <input type="submit" name='publish' class="btn btn-success" value = "Publish"/>
  23. <input type="submit" name='save' class="btn btn-default" value = "Save Draft" />
  24. </form>
  25. @endsection
Edit post page with tinyMCE WYSIWYG editor

Edit post page with TinyMCE WYSIWYG editor

Update:

I am updating this post to add the uploading image option to TinyMCE.

Upload Image:

First, download the project from https://github.com/vikdiesel/justboil.me. Now extract the uploaded archive and rename the extracted folder to jbimages. Move this folder in the directory blog/public/js/tinymce/plugins. Now create one more folder with name images in blog/public directory. This is the default destination where images will be stored. If you want to change the default destination then config it in the config.php file in jbimages folder (which is moved to plugins directory). Note that in this configuration path is relative to the domain name and in laravel our domain directory in blog/public, not parent directory of blog. For example, if you want to upload image in a folder blog/public/images/uploads then you have to just change in config.php (in jbimages folder) as:

  1. $config['img_path'] = '/images/uploads'; // Relative to domain name

Finally change the code for tinymce in edit.blade.php and create.blade.php as follow:

  1. tinymce.init({
  2. selector : "textarea",
  3. plugins : ["advlist autolink lists link image charmap print preview anchor", "searchreplace visualblocks code fullscreen", "insertdatetime media table contextmenu paste jbimages"],
  4. toolbar : "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image jbimages",
  5. });

Note that here we are adding only one more plugin jbimages (in the last in array plugins) and one toolbar named jbimages (in the last in array toolbar). Now we have an upload image option. If you want to config more settings like images allow type, image sizes, etc. then change config.php file in jsimages folder.

6. User profile backend:

Change UserController.php as:

  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Requests;
  4. use App\Http\Controllers\Controller;
  5. use App\User;
  6. use App\Posts;
  7. use Illuminate\Http\Request;
  8. class UserController extends Controller {
  9. /*
  10. * Display active posts of a particular user
  11. *
  12. * @param int $id
  13. * @return view
  14. */
  15. public function user_posts($id)
  16. {
  17. //
  18. $posts = Posts::where('author_id',$id)->where('active',1)->orderBy('created_at','desc')->paginate(5);
  19. $title = User::find($id)->name;
  20. return view('home')->withPosts($posts)->withTitle($title);
  21. }
  22. /*
  23. * Display all of the posts of a particular user
  24. *
  25. * @param Request $request
  26. * @return view
  27. */
  28. public function user_posts_all(Request $request)
  29. {
  30. //
  31. $user = $request->user();
  32. $posts = Posts::where('author_id',$user->id)->orderBy('created_at','desc')->paginate(5);
  33. $title = $user->name;
  34. return view('home')->withPosts($posts)->withTitle($title);
  35. }
  36. /*
  37. * Display draft posts of a currently active user
  38. *
  39. * @param Request $request
  40. * @return view
  41. */
  42. public function user_posts_draft(Request $request)
  43. {
  44. //
  45. $user = $request->user();
  46. $posts = Posts::where('author_id',$user->id)->where('active',0)->orderBy('created_at','desc')->paginate(5);
  47. $title = $user->name;
  48. return view('home')->withPosts($posts)->withTitle($title);
  49. }
  50. /**
  51. * profile for user
  52. */
  53. public function profile(Request $request, $id)
  54. {
  55. $data['user'] = User::find($id);
  56. if (!$data['user'])
  57. return redirect('/');
  58. if ($request -> user() && $data['user'] -> id == $request -> user() -> id) {
  59. $data['author'] = true;
  60. } else {
  61. $data['author'] = null;
  62. }
  63. $data['comments_count'] = $data['user'] -> comments -> count();
  64. $data['posts_count'] = $data['user'] -> posts -> count();
  65. $data['posts_active_count'] = $data['user'] -> posts -> where('active', '1') -> count();
  66. $data['posts_draft_count'] = $data['posts_count'] - $data['posts_active_count'];
  67. $data['latest_posts'] = $data['user'] -> posts -> where('active', '1') -> take(5);
  68. $data['latest_comments'] = $data['user'] -> comments -> take(5);
  69. return view('admin.profile', $data);
  70. }
  71. }

Update:

Note that I am using withPost() and withTitle() functions. Laravel provides dynamic functions for sending data in templates. withPost($post) is equivalent to with('post', $post) and withTitle($title) is equivalent to with('title', $title).

Front end for profile:

For displaying posts we are using the same home.blade.php view without any change. So we have to only make a view for the profile which is returned by the profile() function. Make a new folder in the views folder with name admin and make a file with name profile.blade.php. Add this code to this file:

  1. @extends('layouts.app')
  2. @section('title')
  3. {{ $user->name }}
  4. @endsection
  5. @section('content')
  6. <div>
  7. <ul class="list-group">
  8. <li class="list-group-item">
  9. Joined on {{$user->created_at->format('M d,Y \a\t h:i a') }}
  10. </li>
  11. <li class="list-group-item panel-body">
  12. <table class="table-padding">
  13. <style>
  14. .table-padding td{
  15. padding: 3px 8px;
  16. }
  17. </style>
  18. <tr>
  19. <td>Total Posts</td>
  20. <td> {{$posts_count}}</td>
  21. @if($author && $posts_count)
  22. <td><a href="{{ url('/my-all-posts')}}">Show All</a></td>
  23. @endif
  24. </tr>
  25. <tr>
  26. <td>Published Posts</td>
  27. <td>{{$posts_active_count}}</td>
  28. @if($posts_active_count)
  29. <td><a href="{{ url('/user/'.$user->id.'/posts')}}">Show All</a></td>
  30. @endif
  31. </tr>
  32. <tr>
  33. <td>Posts in Draft </td>
  34. <td>{{$posts_draft_count}}</td>
  35. @if($author && $posts_draft_count)
  36. <td><a href="{{ url('my-drafts')}}">Show All</a></td>
  37. @endif
  38. </tr>
  39. </table>
  40. </li>
  41. <li class="list-group-item">
  42. Total Comments {{$comments_count}}
  43. </li>
  44. </ul>
  45. </div>
  46. <div class="panel panel-default">
  47. <div class="panel-heading"><h3>Latest Posts</h3></div>
  48. <div class="panel-body">
  49. @if(!empty($latest_posts[0]))
  50. @foreach($latest_posts as $latest_post)
  51. <p>
  52. <strong><a href="{{ url('/'.$latest_post->slug) }}">{{ $latest_post->title }}</a></strong>
  53. <span class="well-sm">On {{ $latest_post->created_at->format('M d,Y \a\t h:i a') }}</span>
  54. </p>
  55. @endforeach
  56. @else
  57. <p>You have not written any post till now.</p>
  58. @endif
  59. </div>
  60. </div>
  61. <div class="panel panel-default">
  62. <div class="panel-heading"><h3>Latest Comments</h3></div>
  63. <div class="list-group">
  64. @if(!empty($latest_comments[0]))
  65. @foreach($latest_comments as $latest_comment)
  66. <div class="list-group-item">
  67. <p>{{ $latest_comment->body }}</p>
  68. <p>On {{ $latest_comment->created_at->format('M d,Y \a\t h:i a') }}</p>
  69. <p>On post <a href="{{ url('/'.$latest_comment->post->slug) }}">{{ $latest_comment->post->title }}</a></p>
  70. </div>
  71. @endforeach
  72. @else
  73. <div class="list-group-item">
  74. <p>You have not commented till now. Your latest 5 comments will be displayed here</p>
  75. </div>
  76. @endif
  77. </div>
  78. </div>
  79. @endsection
User profile for demo blogging application in laravel 5

Further Readings:

  1. Django vs Laravel vs Ruby on rails
  2. Learn to build CMS from scratch with todo list demo in PHP and MySQL
  3. PYTHON vs PHP vs RUBY
  4. Laravel Tutorials


ReactJS with Redux Online Training by Edureka

About Harish Kumar

Harish, a fullstack developer at www.lyflink.com with five year experience in full stack web and mobile development, spends most of his time on coding, reading, analysing and curiously following businesses environments. He is a non-graduate alumni from IIT Roorkee, Computer Science and frequently writes on both technical and non-technical topics.

Related Articles

With the expanding market of mobile apps, the developers are struggling to maintain the code bases for Native apps. M...
5 Elite and Imperative Hybrid App Frameworks
Django is a great framework in python. But all of the hosting do not provide django hosting in their shared or free h...
Cheap Django Hosting
Laravel provides blade template. Blade files are similar to php files and cover all features of php files. In additio...
Blade Template in Laravel 7

Complete Python Bootcamp: Go from zero to hero in Python 3

Top Posts

Recent Posts

The Complete Web Developer Course - Build 25 Websites

Meet on Twitter

Subscribe

Subscribe now and get weekly updates directly in your inbox!

Any Course on Udemy in $6 (INR 455)

Development Category (English)300x250

Best Udemy Courses

PHP with Laravel for beginners - Become a Master in Laravel