Building A Blog Application In Laravel Vapor

Computer, Electronics, Pc, Art, Painting, Laptop, Person, Face, Glasses, Computer Hardware, Monitor, Comics
Headshot image of author

Aug 13th, 2022 By Taylor Perkins Full Stack Developer

Sharing Knowledge

I welcome any and all to Laravel Geek. Here I will discuss and share my adventures over the years with Laravel. I have grown to love the Laravel framework, I feel that I have become a better Engineer simply by following the example set out by Laravel. I enjoy reading the source code and learning more about PHP everyday by doing so. So my goal in creating this blog app is to share the knowledge I have gained through developing in Laravel. I have come to enjoy solving problems and sharing those solutions with my peers. I strongly believe in the Laravel community, and as stated on their website, Laravel is for everyone. Wether you're new to programming or not it's easy to build a robust application with the framework provided. That said, There are still many challenges I have faced while building applications in Laravel. So enough of the introductions, Let's get to sharing knowledge and building things together as a community! 

Building a blog

As this being my first post, I thought it would be fitting to share my experience building out this blog and sharing the open source packages I used to make the experience of doing so a breeze. I definitely had to curb my ambitions on this one, I have found it's always best to start simple and reiterate. In that context, I think you will not be impressed with my architecture decisions, I hope to make this more robust in the future but thought it best to get the simplistic version out the door and improve on it later. Let's get on with some of the challenges I faced and the packages I used that expedited development time. 

Starting with Laravel Breeze

My two personal favorite variations of Laravel's starter kit, Laravel Breeze, is Vue with Inertia and Livewire. I really enjoy working in Livewire. Under the hood its ran by AJAX, but you don't interact with any Javascript (excluding specific use cases). It feels very much like you're only interacting with PHP, which I personally feel more confident in. I had three major requirements that lead me to my decision of using Laravel Breeze with Livewire. 

  • I needed this application to be SEO friendly. Although it is possible to implement SSR, I didn't want that overhead
  • I wanted some really clean code highlighting, which Torchlight provides.
  • Easy authentication for users to login and discuss as a community.

Laravel Breeze Gives us a sane standardized starting point. If you're someone who isn't sure about the best way to organize your code, these starter kits give you a jumping off point and help you with having a clean codebase from the start. The first step after installing Laravel (I prefer Sail) is to install laravel/breeze.

        
1composer require laravel/breeze --dev
2 
3php artisan breeze:install

At this point, If you have never installed the Breeze starter kit before, go ahead and peruse through the blade templates. You will find that out of the box you have authentication and a sane layout for authenticated and unauthenticated users. This essentially got us halfway to crossing out one of our requirements for easy authentication for users to login and discuss as a community. Let's take a look at the next step which will get us closer to having a fast and dynamic, SEO friendly app. 

Installing Filamentphp to Manage Your Post

Next we will look at installing Filament, which is going to dramatically reduce the need to write any boiler plate code. I mean this package is just absolutely amazing. It works well with Laravel Vapor with one or two configuration changes and provides a way to create and edit blog posts like a hot knife slicing through butter. This package provides you with an admin dashboard that can manage any Model resource you tell it to. I have only so far utilized it to manage Users and Posts, but that's all I needed to get this application off the ground. It's literally as easy as creating a model and telling Filament to manage that model and what input fields to use to save to that model. Let's take a look at creating a Post model and telling Filament to setup a listing and edit/create views. 

First create your model:

1php artisan make:model Post

And let’s take a look at what you might put in the fillable:

1class Post extends Model
2{
3 use HasFactory;
4 
5 protected $fillable = [
6 'title',
7 'slug',
8 'description',
9 'content',
10 'user_id',
11 'status',
12 'published_at'
13 ];
14}

Next the migration:

1php artisan make:migration create_posts_table

and here's what mine looked like after customizations

        
1/**
2* Run the migrations.
3*
4* @return void
5*/
6public function up()
7{
8 Schema::create('posts', function (Blueprint $table) {
9 $table->id();
10 $table->string('title');
11 $table->string('slug');
12 $table->mediumText('description')->nullable();
13 $table->longText('content')->nullable();
14 $table->integer('user_id')->nullable();
15 $table->string('status')->default('draft');
16 $table->timestamp('published_at')->nullable();
17 $table->timestamps();
18 });
19}

Now let's dive into some of the juicy stuff. I was super stoked when I was introduced to this package. I had actually already started implementing and writing lots of boiler plate code by the time I had found out, but was more than happy to drop what I was doing to see if this would expedite development. Boy was I shocked to find out how easy it is to create an Admin dashboard for any Model resource you have. I think it's important to mention, I highly recommend going through the installation thoroughly to fully understand how to use and configure this package as I will not be going through all that. I will give any and all warnings on pitfalls I fell into though. Let's begin. 

Pro Tip: It's well documented on filament, but one pitfall I ran into once moving this into a "production" environment (even if its not prod, if you have APP_ENV set to anything but local it will deny you authorization to the admin dashboard). If you want to simply allow any user for development or a staging environment, you can simply follow these instructions and return true in canAccessFilament. We will go through using the Bouncer package and assigning an administrator role later to have a more robust authorization. 
        
1php artisan make:filament-resource Post

And here's what my specific use case looks like. This is just the form section, a lot of this stuff is dynamically given to you. Change these forms to fit your Model as you please. Same for the table listing. Tinker around and see what happens on the dashboard. 

        
1public static function form(Form $form): Form
2{
3 return $form
4 ->schema([
5 TextInput::make('title')
6 ->required()
7 ->maxLength(255)
8 ->afterStateUpdated(function (Closure $get, Closure $set, ?string $state) {
9 if (! $get('is_slug_changed_manually') && filled($state)) {
10 $set('slug', Str::slug($state));
11 }
12 })
13 ->reactive(),
14 TextInput::make('slug')
15 ->required()
16 ->maxLength(255)
17 ->afterStateUpdated(function (Closure $set) {
18 $set('is_slug_changed_manually', true);
19 }),
20 Hidden::make('is_slug_changed_manually')
21 ->default(false)
22 ->dehydrated(false),
23 Textarea::make('description')
24 ->maxLength(16777215),
25 Select::make('status')
26 ->required()
27 ->options([
28 'draft' => 'Draft',
29 'review' => 'In review',
30 'published' => 'Published',
31 ])
32 ->default('draft'),
33 SpatieMediaLibraryFileUpload::make('featured_image')
34 ->disk('s3')
35 ->visibility('public')
36 ->collection('featured_image')
37 ]);
38}

Now let's look further into what's going on here. First, you saw the Post model and its fillable fields. Most of this code is simply just telling Filament what input fields to render in the form for creating/updating a Post. Let's look into what is likely your first, “What is going on here?”. 

        
1TextInput::make('title')
2 ->required()
3 ->maxLength(255)
4 ->afterStateUpdated(function (Closure $get, Closure $set, ?string $state) {
5 if (! $get('is_slug_changed_manually') && filled($state)) {
6 $set('slug', Str::slug($state));
7 }
8 })
9 ->reactive()

I wanted the slug to be automatic, but allow you to customize it if you wanted to change it. I made this input field reactive (read more on dependant fields) so that I could define the slug dynamically though the title but still also manually change the slug if needed. This post is where I got this solution from. This next portion is what allows manually changing the slug. 

        
1TextInput::make('slug')
2 ->required()
3 ->maxLength(255)
4 ->afterStateUpdated(function (Closure $set) {
5 $set('is_slug_changed_manually', true);
6 }),
7Hidden::make('is_slug_changed_manually')
8 ->default(false)
9 ->dehydrated(false),

The rest of the forms are pretty straightforward, excluding the FileUpload field, but we'll dive into that in the next post. You have a description TextInput and a Select field with static options and their labels. For the time being, you can go ahead and just exclude the SpatieMedia field, it is simply a media upload library that integrates with Filament. That's where we will start on the next post. 

Concluding Part One

As you can see, at this point you have everything but the featured image and the content for the post. You should be able to go into your Filament admin dashboard and create/edit your posts. The content portion is skipped intentionally, it is the architecture portion I am not proud of, but it was the easiest way to get up and going. Currently I am rendering static post blade templates with dynamic posts. So the title, slug and image and such are all stored in the database but not the post content. We will look into that later and in the next few months I will have a more robust dynamic solution for rendering clean code highlighting. 


That concludes part one of building a blog application in Laravel. Next time we will pick back up on the image upload portion and get that connected to s3 and configured for Laravel Vapor. I hope you find this article helpful.


Please sign in or create an account to join the conversation.