Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Browse all series

The Laravel Workshop

Buckle up, this is the workshop you've been waiting for! In fact, this course is such a massive undertaking to the point that it required three Laracasts instructors. Join Jeffrey Way, Simon Vrachliotis, and Jeremy McPeak as we build a fictional X-like platform, called Pixl, entirely from scratch!

Each instructor is responsible for one piece of the puzzle. Simon will tackle the static HTML and CSS build. Then Jeremy will convert it into a traditional and dynamic Laravel application. Finally, Jeffrey will demonstrate how to transform a server-side app into a full SPA. Let's go!

Series trailer thumbnail

Progress

Series Info

Episodes
64
Run Time
12h 36m
Difficulty
Intermediate
Last Updated
Nov 28, 2025
Version
Latest

Series Episodes

  1. HTML and CSS (16)
    1. Figma Design Overview

      We take a high-level look at the original Figma design and explore the key UI elements we'll recreate. This helps us understand spacing, layout structure, and overall visual goals.
    2. Vite Dev Server

      We fire up a Vite dev server and start scaffolding our project. The focus here is setting up the playground with Tailwind and building confidence in our local dev environment.
    3. Tailwind Theme Customization

      We customize the Tailwind config file to match our design system. Colors, spacing, font sizes, and shadows get tweaked to bring the Figma design closer to code.
    4. Login Screen: Overall Layout

      We build the structural layout of the login screen, using a two-column design and flex utilities to mirror the overall proportions of the original UI.
    5. Login Screen: Action Buttons

      We implement the primary and secondary buttons on the login screen, covering hover states, disabled states, and how to keep spacing clean and consistent.
    6. Login Screen: Word Stack

      We recreate the vertically stacked words on the left panel of the login screen using a clever flex-column trick and some custom tracking and sizing.
    7. Login Screen: Responsive Pass & Final Touches

      We go back over the login screen and make it fully responsive across all breakpoints. This includes stacking behavior, spacing tweaks, and final cleanup to get everything looking clean and consistent.
    8. App Screen: 3-Column Layout

      We start scaffolding the main app screen by setting up the three-column structure using CSS grid. This forms the backbone for the sidebar, feed, and profile panels.
    9. App Screen: Sidebar

      We build out the left sidebar, adding navigation links and refining the styling to feel balanced and visually connected to the rest of the layout.
    10. App Screen: Navigation

      We implement the top navigation bar of the app, covering positioning, z-index stacking, and how to keep the UI consistent across pages.
    11. Feed: New Post Prompt

      We build the prompt that invites users to start a new post. This includes placeholder content, avatar positioning, and attention to spacing and border styling.
    12. Feed: Post UI

      We construct the core post UI component, combining avatar, author info, timestamp, text content, and spacing details to match our design closely.
    13. Feed: Post with Replies

      We extend the post UI by nesting replies, handling indentation, visual separation, and setting up a scalable reply thread structure.
    14. Feed: Reply Form

      We implement the reply input beneath posts, ensuring smooth layout flow, responsive behavior, and clear visual hierarchy.
    15. App Screen: Responsive Pass & Final Touches

      We revisit the full app layout and polish it for responsiveness. Expect tighter spacing, mobile refinements, and a final pass to make sure everything snaps into place on every screen size.
    16. Profile: Header Section

      We focus on the last bit of UI: the profile header. We'll add user info, avatar, and subtle background treatments to give the profile screen its own identity.
  2. Laravel and Blade (30)
    1. Installing Laravel

      First thing's first: we need a Laravel application. We'll install one in Simon's repository and have it serve the feed and profile views.
    2. Extracting the Navigation, Aside, and Layout

      We need to extract the common markup into partial views, layouts, and components. We'll start by creating partial views for the navigation and aside sections of the app so that we can create the layout.
    3. Creating the Artists to Follow Component

      The aside has the "Artists to Follow" section that would work great as a stand-alone component. We'll create that component in this episode.
    4. Extracting the Post Form

      The form markup for posting/replying is almost identical. Let's extract it into a reusable partial view to cut down on duplication.
    5. Creating the Feed Item Partial View

      Displaying feed items is important for an app like PIXL. In this episode, we'll extract the markup into a partial view and feed it data so that we can prepare for our actual data.
    6. Generating Feed Replies

      Replies are just like normal posts, except that they're a reply to another post. Let's extract the markup for the replies and modify our data structure to account for replies.
    7. Creating the Profile Model

      Now we split authentication into a slim User model and a full Profile, decide on a one-to-one user_id relationship (cascade on delete) and pick profile fields like display_name, a unique handle, nullable bio and avatar_url. Then we scaffold the migration and factory with Artisan, wire up the hasOne/belongsTo methods and $fillable, and use Faker (90x90 avatar via imageUrl) to generate test data.
    8. Building the Post Model

      Next up, we build a post model to represent both posts and replies, opting for a nullable parent_id column (plus a constrained profile_id and content) instead of a pivot table to keep queries and pagination simpler. We wire up the Eloquent relationships (hasMany/belongsTo), add indexes in the migration, and scaffold a factory with a reply state to easily generate replies — likes, follows, and reposts come next.
    9. Preparing Likes and Follows

      Let's create the Like and Follow models with migrations and factories — each is basically a pivot-style table (likes use profile_id + post_id; follows use follower_profile_id + following_profile_id), with constrained FKs, cascade-on-delete, unique constraints to prevent duplicates, and helpful indexes (e.g. post_id + created_at, plus follower/following indexes). Then we wire up the Eloquent relationships: Like belongsTo Profile and Post, Post hasMany Like, and Profile hasMany Like plus belongsToMany followers/following via the follows pivot with the correct pivot keys.
    10. Modeling Reposts

      Okay, we're treating reposts as regular posts in the same posts table by adding a self-referential repost_of_id foreign key and a model relationship to fetch reposts. We also tweak factories/seeding and make content nullable so plain reposts and quote posts behave correctly, then run a bit of model testing before wiring up controllers.
    11. Publishing Posts

      Let's test our models before wiring up controllers — so we install Pest (composer require pestphp/pest and the Laravel plugin), run vendor/bin/pest init, scaffold a PostBehaviorTest, and run migrations with php artisan migrate. We add a static publish method to the Post model, use factories to create a Profile and a post and assert things like parent_id and repost_of_id are null, then fix failing tests by adjusting factories/migrations (remove name, pass true to faker->sentences, add constrained foreign keys), run php artisan migrate:fresh and php artisan test, and get a passing test — next up: replies and reposts.
    12. Replying To Posts

      Let's build a reply method that lets a profile reply to an original post, passing the profile, original post, and content as arguments, then verify it with tests for single and multiple replies. We'll ensure replies link back to the original post correctly and confirm everything works by running the tests before moving on to repost functionality.
    13. Basic Reposts

      Let's build reposts — they're just posts like replies, so we add a repost factory/method that sets parent_id to null and repost_of_id to the original, supporting plain reposts (null content) and quote reposts (provided content). We wire up the repostOf relationship and add the field to fillable so tests pass, and next we'll prevent a profile from reposting the same post twice.
    14. Preventing Duplicate Reposts

      We write tests to prevent duplicate reposts and enforce a DB-level unique constraint on profile_id + repost_of_id in the migration (run migrate:fresh), then update the Post model to use firstOrCreate so reposts are fetched instead of blindly recreated. Next we add a static removeRepost that finds by profile and original post and returns whether delete() removed anything, run the suite to get all tests passing, and then we start on likes in the next episode.
    15. Liking Posts

      Now, we split out a small behavior test for liking posts and implement a static create method on the Like model that links a profile to a post, with a unique constraint in the create_likes migration to prevent duplicates. We add tests to verify counts on both the profile and post, ensure duplicates can't be created, and implement a static remove method to delete likes, then run the suite until all tests pass before moving on to follows.
    16. Following Profiles

      Next up, we add profile following — we ensure the follows migration has a unique constraint to prevent duplicate follows and our code blocks a profile from following itself (you could enforce this with a DB CHECK constraint, but we handle it in code). We then copy the likes tests to make follow behavior tests and implement createFollow and removeFollow (a.k.a. unfollow) so toggling follow/unfollow works and the test suite passes, which makes wiring up the controllers easier.
    17. Seeding The Database

      Okay, we seed the database to create about 20 Profile records and then use factories and a few loops to generate associated Posts, random Likes, Follows, Reposts (including some quote reposts) and Replys so the data feels realistic. After running php artisan db:seed we peek at the tables to confirm the profiles, posts, likes, follows, reposts, and replies exist, then move on to implementing the ProfileController.
    18. Displaying Top Level Posts

      Next up, we're implementing the ProfileController and a profiles.show route, adding a migration and factory tweaks for a cover image (using dummyimage.com), and eager-loading counts so the view receives the profile plus top-level posts and follower/following counts. I also split the views into a profiles/show.blade.php and profile-specific partials, filter out replies so profiles show only top-level posts, and add an isRepost helper to handle normal reposts versus quote reposts.
    19. Implementing The Replies Page

      Now the lesson builds a profile "with replies" page by copying the show method to a new replies method, writing a query that pulls top-level posts (where parent_id is null) plus posts that have replies by the profile, eagerly loading related profiles and counts, and wiring up a route and replies.blade.php view. Then it refactors the UI from partials into components—creating a Post component (with public post, showEngagement, and showReplies props) and a Reply component—swapping them into the show view and setting up follow-up work to toggle engagement/replies and extract the header.
    20. Creating The Header Component

      Moving on, we make sure the posts page doesn't accidentally display replies by checking that the replies relation is loaded and we gate the likes/replies/reposts UI behind a show engagement prop so we can hide engagement buttons when needed. Then we extract the header/navigation into a profile header view component, switch profile links to named routes like profiles.show, and plan to render an individual post with its replies in the next episode.
    21. Displaying A Thread

      Next up, we add a route like posts.show and a show method on a PostController that uses Eloquent scope bindings to confirm the post belongs to the profile and eager-loads counts and relationships with with/withCount. We then build a posts/show.blade.php view that uses the post component, toggles showReplies, and supports two levels of nested replies with links to individual posts.
    22. Finalizing the Artists to Follow Component

      Now that we have our models and actual data, we can completely implement the Artists to Follow component. In this episode, we'll finalize that component to display actual profiles to follow both for signed-in and anonymous users.
    23. Displaying the Timeline

      The timeline should include all of the posts from profiles that the signed-in profile follows. In this episode, we'll build the query to select those posts, including reposts, replies, and likes. It's not confusing at all!
    24. Creating Posts

      Creating a post is probably one of the most simple things we can do in our app. In this episode, we'll build the necessary Request objects, controller methods, and Blade components to create a post.
    25. Replying to Posts

      The next logical step is to implement the ability for profiles to reply to posts. In this episode, we'll build the necessary components and routes to handle post replies.
    26. Reposting, Liking, and Following

      Creating a post is easy, but reposting, liking, and following are the most simple things we can do in PIXL. We have the functionality--we just need to wire it up in the UI.
    27. Deleting Posts, Unliking, and Unfollowing

      If we can create posts, like posts, and follow profiles, we logically need the ability to delete posts, unlike posts, and unfollow profiles. In this episode, we'll wire up that functionality.
    28. Creating Social Button Components

      Blade components are awesome, and they give us the opportunity to create reusable pieces of UI... like the social buttons. In this episode, we'll create some social button components for liking, reposting, and following.
    29. Cleaning Up

      Earlier in the project, we created some partial views to help us get going quickly. Now that we have everything working, let's clean up those partial views and replace them with components where it makes sense.
    30. Refactoring Queries

      Some of our queries are directly in our controller methods, and I want to clean that up. In this episode, we'll refactor our queries into query classes to keep our controllers cleaner and simpler.
  3. From Blade to SPA (18)
    1. Welcome To Section 3

      Welcome to Section 3, with Jeffrey Way. Let's get started by cloning Simon and Jeremy's work, installing all dependencies, and then setting up a composer format task to run both Pint and Rector on our codebase.
    2. The VILT Stack

      Let's go with the VILT stack: Vue, Inertia, Laravel, and Tailwind CSS. There are a number of steps to get everything configured, so let's get started!
    3. The Feed Page

      It's time to begin migrating Jeremy's Blade views and components into Vue single-file components. In this video, I'll show you the general workflow for each file.
    4. Named Routes And Ziggy

      For each href, Jeremy used Laravel's helpful route() function to reference a named route. On the JavaScript side, we can leverage a tool called Ziggy to allow for the same thing.
    5. SVGs Can Be Vue Components

      I think we should extract these SVGs into their own dedicated Vue components. Not only will this allow us to hide all of that ugly markup, but it also allows for reusability, accepting props, and more.
    6. API Resource Classes

      When building an SPA, we must always be careful about the data we send to the client. For this reason, I prefer to always explicitly use Laravel's API Resource classes.
    7. The Create Post Form

      Let's get started making the "create post" form dynamic. We can reach for Inertia 2's dedicated Form component to make this a cinch!
    8. The Post Item Component

      Moving on to the Post item component, let's ensure that the various labels - likes, reposts, replies - are properly rendered with the appropriate counts.
    9. Post Commenting

      Next, let's work on support for replying to an existing post. We'll need the reply form to toggle based upon a button click.
    10. Show A Single Post

      We haven't yet implemented the page that displays a single post, along with its replies. Let's work on that now!
    11. Dropdown Menus And Headless UI

      Let's move our attention to the dropdown menu for each post. This should include links, for example, to view the post, delete it, etc. Rather than writing an accessible Dropdown component from scratch, let's instead leverage HeadlessUI.
    12. The Profile Page

      Each user can have a profile. Let's make sure that clicking their avatar properly directs you to a page to view their profile, bio, and replies.
    13. Follow Functionality

      Jeremy has already implemented the backend logic to follow a profile. So our job is to wire up the form to submit the appropriate request.
    14. Flash Messaging

      It might be useful to provide feedback to the user when they trigger a significant action on the site, such as publishing a post, following a user, or updating their profile. Let's build a simple FlashMessages component to display flashed data for a handful of seconds.
    15. The Marketing Page

      At the moment, Simon's splash marketing page isn't in our codebase. Let's clone it from his repository so that we have a proper landing page for guest users.
    16. Browser Testing Workshop

      A real-life application like this would require hundreds of tests. As this is a video series, that's a bit impractical. Instead, I'll jump-start you with a handful of test files that mimic how you might structure your full test suite. Let's think in terms of actions.
    17. Deploy Pixl Using Laravel Forge

      No web app is ever truly complete, but let's go ahead and push our project to production using Laravel Forge. As you'll see, it's incredibly easy!
    18. Section 4: You

      Congratulations on completing this massive multi-instructor series. We can't thank you enough for making it this far. For section 4, we're moving it over to...YOU. [Here are a handful of features](https://github.com/laracasts/laravel-workshop) that we'd recommend you work on. Good luck!

Continue Learning