Create a Laravel 8 Blog - Part 4: Post CRUD

Overview

At the end of this you'll have a form like this Post form

New post form

  1. Make a new folder for the blade files
mkdir resources/views/blog/posts/
  1. Add the form and controller paths to create a new post

Add the routes to view the form and save the post.

# routes/web.php Route::group(['prefix' => 'admin'], function () { Route::group(['prefix' => 'blog'], function () { Route::get('/post', 'App\Http\Controllers\PostController@create'); Route::post('/post', 'App\Http\Controllers\PostController@save'); }); });

Add the blade file with the fields you want to store for the blog post.

# resources/views/blog/posts/create.blade.php @extends('layouts.app') @section('content') @if (session('message')) <div class="alert alert-success"> {{ session('message') }} </div> @endif <form action="/admin/blog/post" method="post" class="container"> <input type="hidden" name="_token" value="{{ csrf_token() }}"> <div class="form-group"> <label for="title">Title</label> <input type="text" name="title" class="form-control" /> </div> <div class="form-group"> <label for="metaTitle">Meta Title</label> <input type="text" name="metaTitle" class="form-control" /> </div> <div class="form-group"> <label for="body">Body</label> <textarea name="body" class="form-control"></textarea> </div> <div class="form-group"> <label for="metaDescription">Meta Description</label> <textarea name="metaDescription" class="form-control"></textarea> </div> <div class="form-group"> <label for="urlSlug">URL Slug (optional)</label> <small>If not specified one will be created from the title</small> <input type="text" name="urlSlug" class="form-control" /> </div> <input type="submit" name="publish" class="btn btn-success" value="Publish" /> <input type="submit" name="draft" class="btn btn-default" value="Save Draft" /> </form> @stop

The controller has two new methods. create handles rendering the form to add a new post. save handles processing the POST request and storing the new post.

# app/Http/Controllers/PostController.php public function create(Request $request) { if ($request->user()->canManagePosts()) { return view('blog.posts.create'); } else { return redirect('/') ->withErrors('You do not have permission to create posts.'); } } public function save(Request $request) { $post = new Post(); $post->title = $request->get('title'); $post->metaTitle = $request->get('metaTitle'); $post->body = $request->get('body'); $post->metaDescription = $request->get('metaDescription'); $post->slug = $request->get('urlSlug') ?? Str::slug($post->title); $post->user_id = $request->user()->id; if ($request->has('draft')) { $post->active = 0; $message = 'Draft saved!'; } else { $post->active = 1; $message = 'Post published!'; } $post->save(); return redirect('admin/blog/post/' . $post->slug) ->withMessage($message); }

The user model needs an update to show which role can add posts.

# app/Models/User.php /** * Check if a user can CRUD posts */ public function canManagePosts(): bool { if ($this->role === 'author' || $this->isAdmin()) { return true; } return false; } /** * Check if the user is an admin */ public function isAdmin(): bool { return $this->role === 'admin'; }

See the commit

  1. Add the paths to update/delete a post

Add the edit/update routes into the blog prefix group

# routes/web.php Route::get('/post/{slug}', 'App\Http\Controllers\PostController@edit'); Route::put('/post/{slug}', 'App\Http\Controllers\PostController@update');

Add the blade file/form to edit a post.

# resources/views/blog/posts/edit.blade.php @extends('layouts.app') @section('content') @if (session('message')) <div class="alert alert-success"> {{ session('message') }} </div> @endif <form action="/admin/blog/post/{{ $post->slug }}" method="post" class="container"> <input type="hidden" name="_method" value="PUT"> <input type="hidden" name="_token" value="{{ csrf_token() }}"> <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="metaTitle">Meta Title</label> <input type="text" name="metaTitle" class="form-control" value={{ $post->metaTitle }} /> </div> <div class="form-group"> <label for="body">Body</label> <textarea name="body" class="form-control">{{ $post->body }}</textarea> </div> <div class="form-group"> <label for="metaDescription">Meta Description</label> <textarea name="metaDescription" class="form-control">{{ $post->metaDescription }}</textarea> </div> <div class="form-group"> <label for="urlSlug">URL Slug (optional)</label> <small>If not specified one will be created from the title</small> <input type="text" name="urlSlug" class="form-control" value={{ $post->slug }} /> </div> <input type="submit" name="update" class="btn btn-success" value="Update" /> <input type="submit" name="delete" class="btn btn-danger" value="Delete" /> </form> @stop

The controller has two new methods. edit handles rendering the form to edit an existing post. update handles processing the PUT request to change the post.

# app/Http/Controllers/PostController.php public function edit(Request $request, string $slug) { $post = Post::where('slug', $slug)->first(); if ($post && ($request->user()->id == $post->user_id || $request->user()->isAdmin())) { return view('blog.posts.edit', ['post' => $post]); } return redirect('/') ->withErrors('You do not have permission to edit this post.'); } public function update(Request $request, string $slug) { $post = Post::where('slug', $slug)->first(); if ($request->has('delete')) { $post->delete(); $message = 'Post deleted!'; return redirect('admin/blog/post/') ->withMessage($message); } $post->title = $request->get('title'); $post->metaTitle = $request->get('metaTitle'); $post->body = $request->get('body'); $post->metaDescription = $request->get('metaDescription'); $post->slug = $request->get('urlSlug') ?? Str::slug($post->title); $post->user_id = $request->user()->id; $message = 'Post updated!'; $post->save(); return redirect('admin/blog/post/' . $post->slug) ->withMessage($message); }

See the commit

  1. Add path to view post details on front-end

Set up the route. I created a blog prefix group to make managing the prefix easier and consolidated the existing /blog path.

# routes/web.php Route::group(['prefix' => 'blog'], function () { Route::get('/', 'App\Http\Controllers\PostController@index'); Route::get('/post/{slug}', 'App\Http\Controllers\PostController@single'); });

Add the controller method to fetch the post and render the view.

# app/Http/Controllers/PostController.php public function single(Request $request, string $slug) { $post = Post::where('slug', $slug)->first(); if ($post) { return view('blog.posts.single', ['post' => $post]); } return redirect('/') ->withErrors('This post does not exist.'); }

Update the blog landing page to list out the posts along with an edit/view link if the user is logged in.

# resources/views/blog/index.blade.php @extends('layouts.app') @section('content') <h1>{{$title}}</h1> @forelse ($posts as $post) @guest <li>{{ $post->title }}</li> @endguest @auth <li>{{ $post->title }} - <a href="/blog/post/{{ $post->slug }}">View</a> | <a href="/admin/blog/post/{{ $post->slug }}">Edit</a></li> @endauth @empty <p>No posts</p> @endforelse @stop

Add a simple blade file that just prints the title and body content out.

# resources/views/blog/posts/single.blade.php @extends('layouts.app') @section('content') <h1>{{ $post->title }}</h1> <p>{{ $post->body }}</p> @stop

See the commit

  1. Add menu items to access these new paths easily!
# resources/views/layouts/app.blade.php <a class="nav-link" href="{{ url('/blog') }}">All Posts</a> @auth <a class="nav-link" href="{{ url('/admin/blog/post') }}">+ New Post</a> @endauth

See the commit

Roundup

That's it for now. Part 5 will go through adding the ability to comment on posts.

Next: Comment CRUD

If you have any feedback for me, I'd love to hear it - corrections, alternative paths, you name it! Send me an email levi@levijackson.xyz