DEV Community

Cover image for Laravel for Beginners #4 - Create a Dashboard
Eric Hu
Eric Hu

Posted on • Originally published at ericsdevblog.com

Laravel for Beginners #4 - Create a Dashboard

Download source code here. ⬅️

Finally, it is time for the most important part of this tutorial. Let's create a fully featured blog application, with posts, categories, as well as tags. Previously, we discussed the CRUD operations for posts, and in this article, we are going to repeat that for categories and tags, and we are also going to discuss how to deal with the relations between them as well.

Initialize a new Laravel project

Once again, we'll start with a fresh project. Create a working directory and change into it. Make sure Docker is up and running, then execute the following command:

curl -s https://laravel.build/<app_name> | bash
Enter fullscreen mode Exit fullscreen mode

Change into the app directory and start the server.

cd <app_name>
Enter fullscreen mode Exit fullscreen mode
./vendor/bin/sail up
Enter fullscreen mode Exit fullscreen mode

To make things easier, let's create an alias for sail. Run the following command:

alias sail='[ -f sail ] && sh sail || sh vendor/bin/sail'
Enter fullscreen mode Exit fullscreen mode

From now on, you can run sail directly without specifying the entire path.

sail up
Enter fullscreen mode Exit fullscreen mode

User authentication

Step two, as we've mentioned before, Laravel comes with a large ecosystem, and Laravel Breeze is a part of this ecosystem. It provides a quick way to set up user authentication and registration in a Laravel application.

Breeze includes pre-built authentication views and controllers, as well as a set of backend APIs for handling user authentication and registration. The package is designed to be easy to install and configure, with minimal setup required.

Use the following commands to install Laravel Breeze:

sail composer require laravel/breeze --dev
Enter fullscreen mode Exit fullscreen mode
sail artisan breeze:install
Enter fullscreen mode Exit fullscreen mode
sail artisan migrate
Enter fullscreen mode Exit fullscreen mode
sail npm install
Enter fullscreen mode Exit fullscreen mode
sail npm run dev
Enter fullscreen mode Exit fullscreen mode

This process will automatically generate required controllers, middleware and views that are necessary for creating a basic user authentication system. You may access the registration page by visiting http://127.0.0.1/register.

Laravel Breeze register

Register a new account and you will be redirected to the dashboard.

In this article, we are not going to discuss exactly how this user authentication system works, as it is related to some rather advanced concepts. But it is highly recommended that you take a look at the generated files, they offer you a deeper insight into how things work in Laravel.

Set up database

Next, we need to have a big picture on how our blog app looks. First, we need to have a database that can store posts, categories, and tags. Each database table would have the following structure:

Posts

key type
id bigInteger
created_at
updated_at
title string
cover string
content text
is_published boolean

Categories

key type
id bigInteger
created_at
updated_at
name string

Tags

key type
id bigInteger
created_at
updated_at
name string

And of course, there should also be a users table, but it has already been generated for us by Laravel Breeze, so we'll skip it this time.

These tables also have relations with each other, as shown in the list below:

  • Each user has multiple posts
  • Each category has many posts
  • Each tag has many posts
  • Each post belongs to one user
  • Each post belongs to one category
  • Each post has many tags

To create these relations, we must modify the posts table:

Posts with relations

key type
id bigInteger
created_at
updated_at
title string
cover string
content text
is_published boolean
user_id bigInteger
category_id bigInteger

And we also need a separate table for post/tag relation:

Post/tag

key type
post_id bigInteger
tag_id bigInteger

Implement database structure

To implement this design, generate models and migration files using the following commands:

sail artisan make:model Post --migration
Enter fullscreen mode Exit fullscreen mode
sail artisan make:model Category --migration
Enter fullscreen mode Exit fullscreen mode
sail artisan make:model Tag --migration
Enter fullscreen mode Exit fullscreen mode

And a separate migration file for the post_tag table:

sail artisan make:migration create_post_tag_table
Enter fullscreen mode Exit fullscreen mode

database/migrations/create_posts_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('title');
            $table->string('cover');
            $table->text('content');
            $table->boolean('is_published');

            $table->bigInteger('user_id');
            $table->bigInteger('category_id');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};
Enter fullscreen mode Exit fullscreen mode

database/migrations/create_categories_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('name');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('categories');
    }
};
Enter fullscreen mode Exit fullscreen mode

database/migrations/create_tags_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('name');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('tags');
    }
};
Enter fullscreen mode Exit fullscreen mode

database/migrations/create_post_tag_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('post_tag', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->bigInteger('post_id');
            $table->bigInteger('tag_id');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('post_tag');
    }
};
Enter fullscreen mode Exit fullscreen mode

Apply these changes with the following command:

sail artisan migrate
Enter fullscreen mode Exit fullscreen mode

And then for the corresponding models, we need to enable mass assignment for selected fields so that we may use create or update methods on them, as we've discussed in previous tutorials. And we also need to define relations between database tables.

app/Models/Post.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        "title",
        'content',
        'cover',
        'is_published'
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class);
    }
}

Enter fullscreen mode Exit fullscreen mode

app/Models/Category.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Category extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
    ];

    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }
}

Enter fullscreen mode Exit fullscreen mode

app/Models/Tag.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Tag extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
    ];

    public function posts(): BelongsToMany
    {
        return $this->belongsToMany(Post::class);
    }
}

Enter fullscreen mode Exit fullscreen mode

app/Models/User.php

<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }
}

Enter fullscreen mode Exit fullscreen mode

Controllers and routes

As for the controllers, we need one resource controller for each resources (post, category and tag).

php artisan make:controller PostController --resource
Enter fullscreen mode Exit fullscreen mode
php artisan make:controller CategoryController --resource
Enter fullscreen mode Exit fullscreen mode
php artisan make:controller TagController --resource
Enter fullscreen mode Exit fullscreen mode

Then create routes for each of these controllers:

routes/web.php

<?php

use App\Http\Controllers\CategoryController;
use App\Http\Controllers\PostController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\TagController;
use Illuminate\Support\Facades\Route;

// Dashboard routes
Route::prefix('dashboard')->group(function () {

    // Dashboard homepage
    Route::get('/', function () {
        return view('dashboard');
    })->name('dashboard');

    // Dashboard category resource
    Route::resource('categories', CategoryController::class);

    // Dashboard tag resource
    Route::resource('tags', TagController::class);

    // Dashboard post resource
    Route::resource('posts', PostController::class);

})->middleware(['auth', 'verified']);

Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});

require __DIR__ . '/auth.php';
Enter fullscreen mode Exit fullscreen mode

Notice that all the routes are grouped with a /dashboard prefix, and the group has a middleware auth, meaning that the user must be logged in to access the dashboard.

Category/tag controllers

The CategoryController and the TagController are fairly straightforward. You can set them up the same way we created the PostController in the previous article.

app/Http/Controllers/CategoryController.php

<?php

namespace App\Http\Controllers;

use App\Models\Category;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class CategoryController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(): View
    {
        $categories = Category::all();

        return view('categories.index', [
            'categories' => $categories
        ]);
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create(): View
    {
        return view('categories.create');
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request): RedirectResponse
    {
        // Get the data from the request
        $name = $request->input('name');

        // Create a new Post instance and put the requested data to the corresponding column
        $category = new Category();
        $category->name = $name;

        // Save the data
        $category->save();

        return redirect()->route('categories.index');
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id): View
    {
        $category = Category::all()->find($id);
        $posts = $category->posts();

        return view('categories.show', [
            'category' => $category,
            'posts' => $posts
        ]);
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id): View
    {
        $category = Category::all()->find($id);

        return view('categories.edit', [
            'category' => $category
        ]);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id): RedirectResponse
    {
        // Get the data from the request
        $name = $request->input('name');

        // Find the requested category and put the requested data to the corresponding column
        $category = Category::all()->find($id);
        $category->name = $name;

        // Save the data
        $category->save();

        return redirect()->route('categories.index');
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id): RedirectResponse
    {
        $category = Category::all()->find($id);

        $category->delete();

        return redirect()->route('categories.index');
    }
}
Enter fullscreen mode Exit fullscreen mode

app/Http/Controllers/TagController.php

<?php

namespace App\Http\Controllers;

use App\Models\Tag;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class TagController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(): View
    {
        $tags = Tag::all();

        return view('tags.index', [
            'tags' => $tags
        ]);
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create(): View
    {
        return view('tags.create');
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request): RedirectResponse
    {
        // Get the data from the request
        $name = $request->input('name');

        // Create a new Post instance and put the requested data to the corresponding column
        $tag = new Tag();
        $tag->name = $name;

        // Save the data
        $tag->save();

        return redirect()->route('tags.index');
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id): View
    {
        $tag = Tag::all()->find($id);
        $posts = $tag->posts();

        return view('tags.show', [
            'tag' => $tag,
            'posts' => $posts
        ]);
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id): View
    {
        $tag = Tag::all()->find($id);

        return view('tags.edit', [
            'tag' => $tag
        ]);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id): RedirectResponse
    {
        // Get the data from the request
        $name = $request->input('name');

        // Find the requested category and put the requested data to the corresponding column
        $tag = Tag::all()->find($id);
        $tag->name = $name;

        // Save the data
        $tag->save();

        return redirect()->route('tags.index');
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id): RedirectResponse
    {
        $tag = Tag::all()->find($id);

        $tag->delete();

        return redirect()->route('tags.index');
    }
}
Enter fullscreen mode Exit fullscreen mode

Remember that you can check the name of the routes using the following command:

sail artisan route:list
Enter fullscreen mode Exit fullscreen mode

Post controller

The PostController, on the other hand, is a bit more complicated, since you have to deal with image uploads and relations in the store() method. Let's take a closer look:

app/Http/Controllers/PostController.php

<?php

namespace App\Http\Controllers;

use App\Models\Category;
use App\Models\Post;
use App\Models\Tag;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\Builder;

class PostController extends Controller
{
    . . .

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request): RedirectResponse
    {
        // Get the data from the request
        $title = $request->input('title');
        $content = $request->input('content');

        if ($request->input('is_published') == 'on') {
            $is_published = true;
        } else {
            $is_published = false;
        }

        // Create a new Post instance and put the requested data to the corresponding column
        $post = new Post();
        $post->title = $title;
        $post->content = $content;
        $post->is_published = $is_published;

        // Save the cover image
        $path = $request->file('cover')->store('cover', 'public');
        $post->cover = $path;

        // Set user
        $user = Auth::user();
        $post->user()->associate($user);

        // Set category
        $category = Category::find($request->input('category'));
        $post->category()->associate($category);

        // Save post
        $post->save();

        //Set tags
        $tags = $request->input('tags');

        foreach ($tags as $tag) {
            $post->tags()->attach($tag);
        }

        return redirect()->route('posts.index');
    }

    . . .
}
Enter fullscreen mode Exit fullscreen mode

A few things to be noted in this store() method. First, line 28 to 32, we are going to use an HTML checkbox to represent the is_published field, and its values are either 'on' or null. But in the database, its values are saved as true or false, so we must use an if statement to solve this issue.

Line 41 to 42, to retrieve files, we must use the file() method instead of input(), and the file is saved in the public disk under directory cover.

Line 45 to 46, get the current user using Auth::user(), and associate the post with the user using associate() method. And line 49 to 50 does the same thing for category. Remember you can only do this from $post and not $user or $category, since the user_id and category_id columns are in the posts table.

Lastly, for the tags, as demonstrated from line 56 to 60, you must save the current post to the database, and then retrieve a list of tags, and attach each of them to the post one by one, using the attach() method.

For the update() method, things work similarly, except that you must remove all existing tags before you can attach the new ones.

$post->tags()->detach();
Enter fullscreen mode Exit fullscreen mode

Views

When building a view system, always remember to be organized. This is the structure I'm going with:

resources/views
├── auth
├── categories
│   ├── create.blade.php
│   ├── edit.blade.php
│   ├── index.blade.php
│   └── show.blade.php
├── components
├── layouts
├── posts
│   ├── create.blade.php
│   ├── edit.blade.php
│   ├── index.blade.php
│   └── show.blade.php
├── profile
├── tags
│   ├── create.blade.php
│   ├── edit.blade.php
│   ├── index.blade.php
│   └── show.blade.php
├── dashboard.blade.php
└── welcome.blade.php
Enter fullscreen mode Exit fullscreen mode

I've created three directories, posts, categories and tags, and each of them has four templates, create, edit, index and show (except for posts since it is unnecessary to have a show page for posts in the dashboard).

Including all of these views in one article would make this tutorial unnecessarily long, so instead, i'm only going to demonstrate the create, edit and index pages for posts. However, the source code for this tutorial is available for free here, if you need some reference.

Post create view

resources/views/posts/create.blade.php

<x-app-layout>
    <x-slot name="header">
        <div class="flex justify-between">
            <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
                {{ __('Posts') }}
            </h2>
            <a href="{{ route('posts.create') }}">
                <x-primary-button>{{ __('New') }}</x-primary-button>
            </a>
        </div>

        <script src="https://cdn.tiny.cloud/. . ./tinymce.min.js" referrerpolicy="origin"></script>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
                <div class="">
                    <form action="{{ route('posts.store') }}" method="POST" class="mt-6 space-y-3" enctype="multipart/form-data">
                        {{ csrf_field() }}
                        <input type="checkbox" name="is_published" id="is_published">
                        <x-input-label for="is_published">Make this post public</x-input-label>
                        <br>
                        <x-input-label for="title">{{ __('Title') }}</x-input-label>
                        <x-text-input id="title" name="title" type="text" class="mt-1 block w-full" required autofocus autocomplete="name" />
                        <br>
                        <x-input-label for="content">{{ __('Content') }}</x-input-label>
                        <textarea name="content" id="content" cols="30" rows="30"></textarea>
                        <br>
                        <x-input-label for="cover">{{ __('Cover Image') }}</x-input-label>
                        <x-text-input id="cover" name="cover" type="file" class="mt-1 block w-full" required autofocus autocomplete="cover" />
                        <br>
                        <x-input-label for="category">{{ __('Category') }}</x-input-label>
                        <select id="category" name="category">
                            @foreach($categories as $category)
                            <option value="{{ $category->id }}">{{ $category->name }}</option>
                            @endforeach
                        </select>
                        <br>
                        <x-input-label for="tags">{{ __('Tags') }}</x-input-label>
                        <select id="tags" name="tags[]" multiple>
                            @foreach($tags as $tag)
                            <option value="{{ $tag->id }}">{{ $tag->name }}</option>
                            @endforeach
                        </select>
                        <br>
                        <x-primary-button>{{ __('Save') }}</x-primary-button>
                    </form>
                    <script>
                        tinymce.init({. . .});
                    </script>
                </div>
            </div>

        </div>
    </div>
</x-app-layout>
Enter fullscreen mode Exit fullscreen mode

I'm using TinyMCE as the rich text editor, you can replace it with something else, or simply use a <textarea></textarea> if you wish.

Line 19, this form must have enctype="multipart/form-data" since we are not just transferring texts, there are files as well.

Line 31, remember to use type="file" here since we are uploading an image.

Line 34 to 38, the value of the option will be transferred to the backend.

Line 41 to 45, there are two things you must pay attention to here. First, notice name="tags[]", the [] tells Laravel to transfer an iterable array instead of texts. And second, multiple creates a multi-select form instead of single select like the one for categories.

Post edit view

resources/views/posts/edit.blade.php

<x-app-layout>
    <x-slot name="header">
        <div class="flex justify-between">
            <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
                {{ __('Posts') }}
            </h2>
            <a href="{{ route('posts.create') }}">
                <x-primary-button>{{ __('New') }}</x-primary-button>
            </a>
        </div>

        <script src="https://cdn.tiny.cloud/. . ./tinymce.min.js" referrerpolicy="origin"></script>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg">
                <div class="">
                    <form action="{{ route('posts.update', ['post' => $post->id]) }}" method="POST" class="mt-6 space-y-3" enctype="multipart/form-data">
                        {{ csrf_field() }}
                        {{ method_field('PUT') }}
                        <input type="checkbox" name="is_published" id="is_published" @checked($post->is_published)/>
                        <x-input-label for="is_published">Make this post public</x-input-label>
                        <br>
                        <x-input-label for="title">{{ __('Title') }}</x-input-label>
                        <x-text-input id="title" name="title" type="text" class="mt-1 block w-full" required autofocus autocomplete="name" value="{{ $post->title }}" />
                        <br>
                        <x-input-label for="content">{{ __('Content') }}</x-input-label>
                        <textarea name="content" id="content" cols="30" rows="30">{{ $post->content }}</textarea>
                        <br>
                        <x-input-label for="cover">{{ __('Update Cover Image') }}</x-input-label>
                        <img src="{{ Illuminate\Support\Facades\Storage::url($post->cover) }}" alt="cover image" width="200">
                        <x-text-input id="cover" name="cover" type="file" class="mt-1 block w-full" autofocus autocomplete="cover" />
                        <br>
                        <x-input-label for="category">{{ __('Category') }}</x-input-label>
                        <select id="category" name="category">
                            @foreach($categories as $category)
                            <option value="{{ $category->id }}" @selected($post->category->id == $category->id)>{{ $category->name }}</option>
                            @endforeach
                        </select>
                        <br>
                        <x-input-label for="tags">{{ __('Tags') }}</x-input-label>
                        <select id="tags" name="tags[]" multiple>
                            @foreach($tags as $tag)
                            <option value="{{ $tag->id }}" @selected($post->tags->contains($tag))>{{ $tag->name }}</option>
                            @endforeach
                        </select>
                        <br>
                        <x-primary-button>{{ __('Save') }}</x-primary-button>
                    </form>
                    <script>
                        tinymce.init({. . .});
                    </script>
                </div>
            </div>

        </div>
    </div>
</x-app-layout>
Enter fullscreen mode Exit fullscreen mode

Line 19 to 21, by default, HTML doesn't support PUT method, so what we can do is use method="POST", and then tell Laravel to use PUT method with {{ method_field('PUT') }}.

Post index view

resources/views/posts/index.blade.php

<x-app-layout>
    <x-slot name="header">
        <div class="flex justify-between">
            <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
                {{ __('Posts') }}
            </h2>
            <a href="{{ route('posts.create') }}">
                <x-primary-button>{{ __('New') }}</x-primary-button>
            </a>
        </div>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            @foreach($posts as $post)
            <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg mb-4 px-4 h-20 flex justify-between items-center">
                <div class="text-gray-900 dark:text-gray-100">
                    <p>{{ $post->title }}</p>
                </div>
                <div class="space-x-2">
                    <a href="{{ route('posts.edit', ['post' => $post->id]) }}"> <x-primary-button>{{ __('Edit') }}</x-primary-button></a>
                    <form method="post" action="{{ route('posts.destroy', ['post' => $post->id]) }}" class="inline">
                        {{ csrf_field() }}
                        {{ method_field('DELETE') }}
                        <x-danger-button>
                            {{ __('Delete') }}
                        </x-danger-button>
                    </form>
                </div>
            </div>
            @endforeach
        </div>
    </div>
</x-app-layout>
Enter fullscreen mode Exit fullscreen mode

Notice the delete button, instead of a regular button, it must be a form with DELETE method, since a regular link has only GET method.

With this demonstration, you should be able to build the rest of the view system with ease.

Screenshots

Last but not least, here are some screenshots for the dashboard I've created.

Dashboard home

Categories list

Create category

Update post

In the next article, we are going to move on from the dashboard, and create the frontend part of the application the user can see.

If you liked this article, please also check out my other tutorials:

Top comments (0)