LessonsBuild a Blog → Part 6

Categories & Tags

Laravel
⏱ 20 min read🏗️ ProjectNot completed

Now we'll build the categories and tags management in the admin panel, and display them on the public blog.

Category Controller

app/Http/Controllers/Admin/CategoryController.php
<?php
namespace App\Http\Controllers\Admin;

use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class CategoryController extends Controller
{
    public function index()
    {
        $categories = Category::withCount('posts')->get();
        return view('admin.categories.index', compact('categories'));
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'name'        => 'required|string|max:100|unique:categories',
            'description' => 'nullable|string|max:500',
            'color'       => 'nullable|string|size:7',
        ]);

        $validated['slug'] = Str::slug($validated['name']);

        Category::create($validated);

        return redirect()
            ->route('admin.categories.index')
            ->with('success', 'Category created!');
    }

    public function destroy(Category $category)
    {
        // Set posts category to null before deleting
        $category->posts()->update(['category_id' => null]);
        $category->delete();

        return redirect()
            ->route('admin.categories.index')
            ->with('success', 'Category deleted!');
    }
}

Categories View

resources/views/admin/categories/index.blade.php
@extends('layouts.admin')
@section('title', 'Categories')

@section('content')
<div class="grid grid-cols-2 gap-6">

    <!-- Category List -->
    <div class="bg-white rounded shadow">
        <div class="p-4 border-b font-semibold">All Categories</div>
        <ul class="divide-y">
            @forelse($categories as $category)
            <li class="p-4 flex justify-between items-center">
                <div class="flex items-center gap-3">
                    <span class="w-3 h-3 rounded-full"
                          style="background: {{ $category->color }}"></span>
                    <div>
                        <div class="font-medium">{{ $category->name }}</div>
                        <div class="text-xs text-gray-400">{{ $category->posts_count }} posts</div>
                    </div>
                </div>
                <form method="POST" action="{{ route('admin.categories.destroy', $category) }}"
                      onsubmit="return confirm('Delete this category?')">
                    @csrf @method('DELETE')
                    <button class="text-red-500 text-sm hover:underline">Delete</button>
                </form>
            </li>
            @empty
            <li class="p-4 text-gray-400">No categories yet.</li>
            @endforelse
        </ul>
    </div>

    <!-- Add Category Form -->
    <div class="bg-white rounded shadow p-6">
        <h3 class="font-semibold mb-4">Add New Category</h3>
        <form method="POST" action="{{ route('admin.categories.store') }}">
            @csrf
            <div class="mb-4">
                <label class="block text-sm font-medium mb-1">Name</label>
                <input type="text" name="name" class="w-full border rounded p-2"
                       value="{{ old('name') }}" required />
                @error('name')
                    <p class="text-red-500 text-xs mt-1">{{ $message }}</p>
                @enderror
            </div>
            <div class="mb-4">
                <label class="block text-sm font-medium mb-1">Color</label>
                <input type="color" name="color" value="#6366f1" class="h-10 w-20 border rounded" />
            </div>
            <div class="mb-4">
                <label class="block text-sm font-medium mb-1">Description</label>
                <textarea name="description" rows="3"
                          class="w-full border rounded p-2">{{ old('description') }}</textarea>
            </div>
            <button type="submit"
                    class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
                Add Category
            </button>
        </form>
    </div>
</div>
@endsection

Tag Management

app/Http/Controllers/Admin/TagController.php
<?php
class TagController extends Controller
{
    public function index()
    {
        $tags = Tag::withCount('posts')->orderBy('posts_count', 'desc')->get();
        return view('admin.tags.index', compact('tags'));
    }

    public function store(Request $request)
    {
        $request->validate(['name' => 'required|unique:tags|max:50']);
        Tag::create([
            'name' => $request->name,
            'slug' => Str::slug($request->name),
        ]);
        return back()->with('success', 'Tag created!');
    }

    public function destroy(Tag $tag)
    {
        $tag->posts()->detach(); // Remove from pivot table
        $tag->delete();
        return back()->with('success', 'Tag deleted!');
    }
}
💡
withCount() is very useful! Category::withCount('posts') adds a posts_count attribute to each category without writing a subquery. This is much more efficient than loading all posts just to count them.
← Previous Next Lesson →
💡

Stuck? Need help?

Review the previous lessons or check the Laravel documentation.

Laravel Docs →