Next video in… 10
Getting Started with Laravel
No one's perfect and everyone makes mistakes, typos, you name it. So let's start the day with a nice cup of coffee and add the ability for our users to edit their chirps.
The neat part is this completes the whole CRUD operations within an MVC structure in a Laravel application. So CRUD: Create, Read, Update, and Delete. We've got Create and Read down—now let's do the Update and Delete parts!
In our chirp.blade.php component (that's our component for each chirp that we're looping through), let's add edit and delete buttons. We'll add the UI first, but we won't hook it up just yet.
Update resources/views/components/chirp.blade.php
:
1@props(['chirp']) 2 3<div class="card bg-base-100 shadow"> 4 <div class="card-body"> 5 <div class="flex space-x-3"> 6 @if ($chirp->user) 7 <div class="avatar"> 8 <div class="size-10 rounded-full"> 9 <img src="<https://avatars.laravel.cloud/>{{ urlencode($chirp->user->email) }}"10 alt="{{ $chirp->user->name }}'s avatar" class="rounded-full" />11 </div>12 </div>13 @else14 <div class="avatar placeholder">15 <div class="size-10 rounded-full">16 <img src="<https://avatars.laravel.cloud/f61123d5-0b27-434c-a4ae-c653c7fc9ed6?vibe=stealth>"17 alt="Anonymous User" class="rounded-full" />18 </div>19 </div>20 @endif21 22 <div class="min-w-0 flex-1">23 <div class="flex justify-between w-full">24 <div class="flex items-center gap-1">25 <span class="text-sm font-semibold">{{ $chirp->user ? $chirp->user->name : 'Anonymous' }}</span>26 <span class="text-base-content/60">·</span>27 <span class="text-sm text-base-content/60">{{ $chirp->created_at->diffForHumans() }}</span>28 </div>29 30 <div class="flex gap-1">31 <a href="/chirps/{{ $chirp->id }}/edit" class="btn btn-ghost btn-xs">32 Edit33 </a>34 <form method="POST" action="/chirps/{{ $chirp->id }}">35 @csrf36 @method('DELETE')37 <button type="submit"38 onclick="return confirm('Are you sure you want to delete this chirp?')"39 class="btn btn-ghost btn-xs text-error">40 Delete41 </button>42 </form>43 </div>44 </div>45 <p class="mt-1">{{ $chirp->message }}</p>46 </div>47 </div>48 </div>49</div>
Notice a few important things here:
Edit button: We're passing in the chirp ID. We want to target a particular, singular chirp, and this is a way to do that within Blade. The route looks like /chirps/{chirp_id}/edit
.
Delete form: We have another form with @csrf
and @method('DELETE')
. This is necessary because HTML forms don't have a standard method of delete, so Laravel gives us this handy directive.
Confirmation: Notice that "onclick" on the delete button—it pops up with a browser modal asking "Are you sure you want to delete this chirp?" Just a neat little touch!
This might seem a little bit complicated, but bear with me. Most of everything we're going to look at when it comes to editing and deleting means that we have to do it within a single instance of a chirp. That's why we have this chirp ID inputted into these routes—because we need to find which chirp we need to edit and which chirp we need to delete.
In our web routes, we can just continue to add those route parameters. Let's add routes for edit, update, and delete:
1use App\Http\Controllers\ChirpController;2 3Route::get('/', [ChirpController::class, 'index']);4Route::post('/chirps', [ChirpController::class, 'store']);5Route::get('/chirps/{chirp}/edit', [ChirpController::class, 'edit']);6Route::put('/chirps/{chirp}', [ChirpController::class, 'update']);7Route::delete('/chirps/{chirp}', [ChirpController::class, 'destroy']);
Notice those curly brackets with {chirp}
? This is what we call route model binding. We know that we need to get an instance of a chirp, so this just gives Laravel an easy way of saying "Hey, within this route we need to look for something that we can identify as a chirp."
Now, Laravel does have a shorthand so that you don't have to do all of this. We're doing things a little bit differently just because we have a GET
in the root directory and not a GET
of /chirps
. But what we could do is something like this:
1Route::get('/', [ChirpController::class, 'index']);2 3Route::resource('chirps', ChirpController::class)4 ->only(['store', 'edit', 'update', 'destroy']);
This resource route is identical to the four routes we wrote out explicitly above—just an easier way of putting it. For right now, I'm going to be extremely explicit so you can see exactly what's happening.
Now let's create the edit view that we will need because we are getting a new route /chirps/{chirp}/edit
. In our views directory, since we don't actually have a directory for chirps (because our chirp index is technically the index of our whole application), I'm going to create one: chirps
, and then in that chirps directory, we're going to have edit.blade.php
.
Create a new file resources/views/chirps/edit.blade.php
:
1<x-layout> 2 <x-slot:title> 3 Edit Chirp 4 </x-slot:title> 5 6 <div class="max-w-2xl mx-auto"> 7 <h1 class="text-3xl font-bold mt-8">Edit Chirp</h1> 8 9 <div class="card bg-base-100 shadow mt-8">10 <div class="card-body">11 <form method="POST" action="/chirps/{{ $chirp->id }}">12 @csrf13 @method('PUT')14 15 <div class="form-control w-full">16 <textarea17 name="message"18 class="textarea textarea-bordered w-full resize-none @error('message') textarea-error @enderror"19 rows="4"20 maxlength="255"21 required22 >{{ old('message', $chirp->message) }}</textarea>23 24 @error('message')25 <div class="label">26 <span class="label-text-alt text-error">{{ $message }}</span>27 </div>28 @enderror29 </div>30 31 <div class="card-actions justify-between mt-4">32 <a href="/" class="btn btn-ghost btn-sm">33 Cancel34 </a>35 <button type="submit" class="btn btn-primary btn-sm">36 Update Chirp37 </button>38 </div>39 </form>40 </div>41 </div>42 </div>43</x-layout>
This is the same layout that we're using throughout our application. The majority of this form is exactly the same as our home.blade.php, just with different buttons and a different form action. This form is using the PUT method for the /chirps/{id}
action, which goes to our update method in the web routes.
Notice this textarea needs to have the chirp that we need to edit, right? We're passing in the old message ({{ old('message', $chirp->message) }}
) so that when we are directed to this edit page, we see the actual chirp message that exists within this chirp instance from the get-go.
Now let's hook this up to our ChirpController. We need to add these methods. This edit method is showing the form for editing the specified resource. In a resource controller for a standard MVC Laravel application, this is just best practice.
1public function edit(Chirp $chirp) 2{ 3 // We'll add authorization in lesson 11 4 return view('chirps.edit', compact('chirp')); 5} 6 7public function update(Request $request, Chirp $chirp) 8{ 9 // Validate10 $validated = $request->validate([11 'message' => 'required|string|max:255',12 ]);13 14 // Update15 $chirp->update($validated);16 17 return redirect('/')->with('success', 'Chirp updated!');18}19 20public function destroy(Chirp $chirp)21{22 $chirp->delete();23 24 return redirect('/')->with('success', 'Chirp deleted!');25}
Let me explain what's happening here:
Edit Method: Instead of using a string ID to find the existing chirp based off of maybe an ID, we can use route model binding. Because we know that the {chirp}
parameter in our web routes is associated with the Chirp model, Laravel automatically assumes what chirp this is based off of that ID. We use the compact
method to pass everything from this chirp into the view.
Update Method: This is similar to our store method where we need to validate something first and then update the chirp. We grab the chirp and update it with the validated info.
Destroy Method: We just delete the chirp and redirect back with a success message.
You might notice a few things when you test this out. Like we can edit anyone's chirp. Well, that's not necessarily good. And we'll fix that when authentication comes into play in the next couple of lessons.
Important: Right now, anyone can edit or delete any chirp. This isn't secure! In lesson 11, we'll add authentication so users can only edit and delete their own chirps. For now, we're focusing on getting the basic functionality working.
I do want to give a sneak peek on what this will look like when we do implement authorization and authentication. When a user is logged in, how do we know that they're able to edit and delete their own chirps?
Laravel has a clean way to put all the authorization code we might need with something called Policies. Here's a preview:
1php artisan make:policy ChirpPolicy --model=Chirp
In app/Policies/ChirpPolicy.php
:
1public function update(User $user, Chirp $chirp): bool2{3 return $chirp->user()->is($user);4}5 6public function delete(User $user, Chirp $chirp): bool7{8 return $chirp->user()->is($user);9}
Then in your controller:
1public function update(Request $request, Chirp $chirp)2{3 if ($request->user()->cannot('update', $chirp)) {4 abort(403);5 }6 // ... rest of method7}
Or even simpler, we can use the authorize method:
1public function update(Request $request, Chirp $chirp)2{3 $this->authorize('update', $chirp);4 // ... rest of method5}
This $this->authorize()
method will check: does this user have the authorization to delete this particular chirp? And in our ChirpPolicy, instead of returning false or true, we can just check if the chirp user is the user making the request: $chirp->user->is($user)
.
We can't put this in the controller just yet because we don't have authentication set up, but once we do, then we can add that back in!
We'll use this pattern once we add proper authentication!
Lastly, before we move on from this lesson, I want to change how something is shown when something is updated. If I edit a chirp, we don't know that this was actually edited. We don't know what the original was. It'd be nice to know that this was updated and not just posted some time ago.
Most social media platforms have this feature, so we might as well get ahead of it! Let's show when a chirp has been edited. Laravel automatically tracks both created_at
and updated_at
timestamps, so we can compare them to see if a chirp has been modified.
Update the chirp component (resources/views/components/chirp.blade.php
) to show the edited indicator:
1<div class="flex items-center gap-1">2 <span class="text-sm font-semibold">{{ $chirp->user ? $chirp->user->name : 'Anonymous' }}</span>3 <span class="text-base-content/60">·</span>4 <span class="text-sm text-base-content/60">{{ $chirp->created_at->diffForHumans() }}</span>5 @if ($chirp->updated_at->gt($chirp->created_at->addSeconds(5)))6 <span class="text-base-content/60">·</span>7 <span class="text-sm text-base-content/60 italic">edited</span>8 @endif9</div>
If the chirp has been updated at, and if that updated_at is different than when the chirp was created_at by at least 5 seconds, then we'll show this little "edited" text. This checks if the updated_at
timestamp is more than 5 seconds after created_at
(to account for any small delays during creation).
When a chirp is edited, Laravel automatically updates the updated_at
timestamp, and our component will show "edited" next to the timestamp. No database changes needed—Laravel's timestamps handle everything for us!
Things are really coming along! Your Chirper app now has full CRUD functionality:
Create - Users can post new chirps
Read - Display chirps in a feed
Update - Edit existing chirps with an edit form
Delete - Remove chirps with confirmation
Edit indication - Shows when chirps have been modified
We have a nice edit page that automatically knows what information to bring in thanks to route model binding. We can edit and delete chirps (though right now anyone can edit anyone's chirps, which we'll fix with authentication).
In the next lessons, we'll add proper authentication so users can actually register, log in, and manage their own chirps securely. We'll allow our users to register and create accounts, and you'll see how easy this can be even without a starter kit.
Ready to make this a real multi-user application? Let's add authentication!
Laravel is the most productive way to
build, deploy, and monitor software.