--- title: Building a Blog with Laravel, Livewire, and Laravel Breeze subtitle: Learn how to create a dynamic blog application using Laravel, Livewire, and Laravel Breeze for authentication and Neon. author: bobbyiliev enableTableOfContents: true createdAt: '2024-06-30T00:00:00.000Z' updatedOn: '2024-06-30T00:00:00.000Z' --- Laravel is a powerful PHP framework that makes it easy to build web applications. When combined with Livewire, a full-stack framework for Laravel, you can create dynamic, reactive interfaces with minimal JavaScript. In this guide, we'll build a blog application using Laravel and Livewire, and we'll use Laravel Breeze to handle authentication, along with Neon Postgres. By the end of this tutorial, you'll have a fully functional blog where users can create, read, update, and delete posts. We'll also implement comments and a simple tagging system. ## Prerequisites Before we start, make sure you have the following: - PHP 8.1 or higher installed on your system - [Composer](https://getcomposer.org/) for managing PHP dependencies - [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/) for managing front-end assets - A [Neon](https://console.neon.tech/signup) account for database hosting - Basic knowledge of Laravel, Livewire, and Tailwind CSS ## Setting up the Project Let's start by creating a new Laravel project and setting up the necessary components. We'll use Laravel Breeze for authentication, Livewire for building interactive components, and Tailwind CSS for styling. ### Creating a New Laravel Project Open your terminal and run the following command to create a new Laravel project: ```bash composer create-project laravel/laravel laravel-livewire-blog cd laravel-livewire-blog ``` This command creates a new Laravel project in a directory named `laravel-livewire-blog` and installs all the necessary dependencies. ### Installing Laravel Breeze [Laravel Breeze](https://laravel.com/docs/11.x/starter-kits) provides a minimal and simple starting point for building a Laravel application with authentication. An alternative to Laravel Breeze is Laravel Jetstream, which provides more features out of the box, such as team management and two-factor authentication. However, for this tutorial, we'll use Laravel Breeze for its simplicity. Let's install Laravel Breeze with the Blade views: ```bash composer require laravel/breeze --dev php artisan breeze:install blade ``` This command installs Breeze and sets up the necessary views and routes for authentication. While in the terminal, also install the Livewire package: ```bash composer require livewire/livewire ``` ### Setting up the Database Update your `.env` file with your Neon database credentials: ```env DB_CONNECTION=pgsql DB_HOST=your-neon-hostname.neon.tech DB_PORT=5432 DB_DATABASE=your_database_name DB_USERNAME=your_username DB_PASSWORD=your_password ``` Make sure to replace `your-neon-hostname`, `your_database_name`, `your_username`, and `your_password` with your actual database details and save the file. ### Compiling Assets Laravel Breeze uses Tailwind CSS for styling, so we need to compile the assets to generate the CSS file. To compile the assets, run: ```bash npm install npm run dev ``` Keep the Vite development server running in the background as you continue with the next steps. This will automatically compile the assets when changes are made so you can see the updates in real-time. ## Creating the Blog Structure Now that we have our basic setup, we are ready to create the structure for our blog, including models, migrations, and Livewire components, routes, policies, and views. ### Creating the Post Model and Migration Models in Laravel are used to interact with the database using the Eloquent ORM. We'll create models for posts, comments, and tags, along with their respective migrations. Run the following command to create a `Post` model with its migration: ```bash php artisan make:model Post -m ``` Open the migration file in `database/migrations` and update it: ```php public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->string('title'); $table->string('slug')->unique(); $table->text('content'); $table->boolean('is_published')->default(false); $table->timestamp('published_at')->nullable(); $table->timestamps(); }); } ``` This migration creates a `posts` table with columns for the post title, content, publication status, and publication date. It also includes a foreign key to the `users` table for the post author. The `slug` column will be used to generate SEO-friendly URLs. ### Creating the Comment Model and Migration Now, let's create a `Comment` model and its migration: ```bash php artisan make:model Comment -m ``` The `comments` table will store the comments for each post, along with the user who made the comment, the post ID, and the comment content. With that in mind, let's update the migration file: ```php public function up() { Schema::create('comments', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->foreignId('post_id')->constrained()->onDelete('cascade'); $table->text('content'); $table->timestamps(); }); } ``` ### Creating the Tag Model and Migration To take this a step further, we can add a tagging system to our blog. This will allow us to categorize posts based on different topics. ```bash php artisan make:model Tag -m ``` The `tags` table will store the tags that can be associated with posts. Update the migration file as follows: ```php public function up() { Schema::create('tags', function (Blueprint $table) { $table->id(); $table->string('name')->unique(); $table->timestamps(); }); ``` We'll also create a pivot table to manage the many-to-many relationship between posts and tags. The convention for naming this table is to combine the singular form of the related models in alphabetical order. In this case, the models are `Post` and `Tag`, so the pivot table will be named `post_tag`. ```bash php artisan make:migration create_post_tag_table ``` Update the migration file as follows: ```php Schema::create('post_tag', function (Blueprint $table) { $table->id(); $table->foreignId('post_id')->constrained()->onDelete('cascade'); $table->foreignId('tag_id')->constrained()->onDelete('cascade'); $table->unique(['post_id', 'tag_id']); }); } ``` We don't need to create a model for the pivot table, as it will be managed by Laravel's Eloquent ORM. Now, run the migrations to create all the tables in the Neon database: ```bash php artisan migrate ``` This command will create the `posts`, `comments`, `tags`, and `post_tag` tables in your database and keep track of the migrations that have been run. If you need to rollback the migrations, you can run `php artisan migrate:rollback` or if you were to add a new migration, you can run `php artisan migrate` and it will only run the new migrations. ### Updating the Models Let's update our models to define the relationships. What we want to achieve is: - A post **belongs** to a user - A post **has many** comments - A post can **have many** tags - A comment **belongs to** a user - A comment **belongs to** a post - A tag can be associated with **many** posts We already have that structure in our database, but we need to define these relationships in our models so we can access them easily in our application. In `app/Models/Post.php` we define the relationships to the `User`, `Comment`, and `Tag` models: ```php belongsTo(User::class); } public function comments() { // Using the `hasMany` relationship to get all comments for a post return $this->hasMany(Comment::class); } public function tags() { // Using the `belongsToMany` relationship to get all tags associated with a post return $this->belongsToMany(Tag::class); } } ``` In `app/Models/Comment.php` we define the relationships to the `User` and `Post` models so we can get the user and post associated with a comment: ```php belongsTo(User::class); } public function post() { return $this->belongsTo(Post::class); } } ``` In `app/Models/Tag.php` we define the relationship to the `Post` model, this will allow us to get all posts associated with a tag: ```php belongsToMany(Post::class); } } ``` Finally, update `app/Models/User.php` to include the relationship with posts and comments, where a user can have many posts and many comments: ```php public function posts() { return $this->hasMany(Post::class); } public function comments() { return $this->hasMany(Comment::class); } ``` With these relationships defined, we can now easily access the related models and data using Eloquent. ### Seeding the Database To populate the database with some sample data, let's create seeders for `Tag` models so we can associate tags with posts. Create a seeder for the `Tag` model: ```bash php artisan make:seeder TagSeeder ``` Update the seeder file in `database/seeders/TagSeeder.php`: ```php $tagName]); } } } ``` Now, update the main `DatabaseSeeder` in `database/seeders/DatabaseSeeder.php` to include these new seeder: ```php call([ TagSeeder::class, ]); } } ``` Finally, to seed your database with this sample data, run: ```bash php artisan db:seed ``` This command will run the `TagSeeder` and populate the `tags` table with the sample tags which we can associate with posts later on when users create new posts. ## Implementing the Blog Functionality Now that we have our models and migrations set up, we can go ahead and implement the blog functionality using Livewire components. We will start by creating two Livewire components: - `PostList` to display a list of blog posts - `PostForm` to create and edit posts ### Creating the Post List Component First, let's create a Livewire component to display the list of blog posts: ```bash php artisan make:livewire PostList ``` This command creates a new Livewire component in the `app/Livewire` directory, along with a view file in `resources/views/livewire`. Update `app/Livewire/PostList.php` to fetch the posts and handle search functionality: ```php resetPage(); } public function render() { $posts = Post::where('is_published', true) ->where(function ($query) { $query->where('title', 'ilike', '%' . $this->search . '%') ->orWhere('content', 'ilike', '%' . $this->search . '%'); }) ->with('user', 'tags') ->latest('published_at') ->paginate(10); return view('livewire.post-list', [ 'posts' => $posts, ]); } } ``` In the `render` method, we fetch the posts that are published and match the search query. An important thing to note here is that we also eager load the `user` and `tags` relationships to avoid additional queries when accessing these relationships in the view. To learn more about how to implement search functionality in Livewire, check out the [Building a Simple Real-Time Search with Laravel, Livewire, and Neon guide](/guides/laravel-livewire-simple-search). Now, update the view in `resources/views/livewire/post-list.blade.php` to display the list of posts: ```html
By {{ $post->user->name }} on {{ $post->published_at }}
{{ Str::limit($post->content, 200) }}
By {{ $post->user->name }} on {{ $post->published_at }}
{{ $comment->content }}
By {{ $comment->user->name }} on {{ $comment->created_at }} @if($comment->user_id === auth()->id()) | @endif
Please log in to leave a comment.
@endauth