Advanced

Let's Build A Forum with Laravel and TDD

A forum is a deceptively complex thing. Sure, it's made up of threads and replies, but what else might exist as part of a forum? What about profiles, or thread subscriptions, or filtering, or real-time notifications? As it turns out, a forum is the perfect project to stretch your programming muscles. In this series, we'll work together to build one with tests from A to Z.

Start Series For Free

Share this series on:

  • 01

    Episode 1 Run Time 7:59

    Initial Database Setup With Seeding

    Let's begin by reviewing the most minimal requirements for a forum. If you think about it, we couldn't possibly construct a forum without users, threads, and replies. So let's tackle those first.

    View the source code for this lesson on GitHub.

  • 02

    Episode 2 Run Time 9:26

    Test-Driving Threads

    Now that we have our seed data in place, we can move on to our first small feature: "a user should be able to read threads." Simple enough! We'll start with a basic test, and then scaffold the necessary views to make it pass.

    View the source code for this lesson on GitHub.

  • 03

    Episode 3 Run Time 9:09

    A Thread Can Have Replies

    Now that we've added a basic feature for users to read forum threads, we can next move on to viewing all replies associated with each thread. As before, we'll start with a test to describe our desired outcome.

    View the source code for this lesson on GitHub.

  • 04

    Episode 4 Run Time 17:39

    A User May Respond to Threads

    We've implemented the necessary functionality to render a thread with all relevant replies, but we haven't yet allowed a user to type in a reply and submit it to the server. Let's get started on that now.

    View the source code for this lesson on GitHub.

  • 05

    Episode 5 Run Time 5:14

    The Reply Form

    Now that we've tested the end-point for adding a new reply, the only remaining step is to create the HTML for the form. In the process, we'll also ensure that only logged-in users are able to see it.
  • 06

    Episode 6 Run Time 6:13

    A User May Publish Threads

    So far, a user can read and reply to threads, but they don't yet have the ability to publish their own threads. Let's begin fixing that in this episode.

    View the source for this lesson on GitHub.

  • 07

    Episode 7 Run Time 5:43

    Let's Make Some Testing Helpers

    I'm a big fan of making the process of writing tests as natural as humanly possible. The harder it is to construct a test, the more likely it is that you simply...won't. With that in mind, let's extract a few helpers and snippets to assist us.

    View the source for this lesson on GitHub.

  • 08

    Episode 8 Run Time 7:54

    The Exception Handling Conundrum

    Now that our endpoint tests are returning green, we can construct the HTML form to publish a new thread. However, in the process, we'll stumble upon an odd exception handling issue that needs to be addressed. Luckily, Adam Wathan has a useful solution that we can implement.

    View the source code for this lesson on GitHub.

  • 09

    Episode 9 Run Time 14:12

    A Thread Should Be Assigned a Channel

    Right now, all threads are thrown into the same "global" namespace, so to speak. Ideally, we should assign each thread to a channel. That way, for a development forum, we may easily filter threads by PHP, or JavaScript, or Servers.

    View the source for this lesson on GitHub.

  • 10

    Episode 10 Run Time 10:41

    How to Test Validation Errors

    We haven't written any validation logic yet for our forum. This means that a user could whip up a request with all sorts of invalid data, and we'd gladly persist it to the database. Let's fix that in this episode, while writing tests to ensure that everything functions as we expect.

    View the source for this episode on GitHub.

  • 11

    Episode 11 Run Time 13:25

    Users Can Filter Threads By Channel

    Now that we've associated all threads with a channel, we can now perform the necessary UI updates to allow users to filter threads by their desired channel.

    View the source for this lesson on GitHub.

  • 12

    Episode 12 Run Time 8:24

    Validation Errors and Old Data

    In this episode, we need to do a bit of work on the "create thread" page. We'll first add a link to the navigation bar, and then move on to tweaking the form, itself. Specifically, we should provide validation error feedback, and ensure that any text that the user types into the form's various fields will be remembered if a validation error is triggered.
  • 13

    Episode 13 Run Time 3:00

    Extracting to View Composers

    Currently, we have two different SQL queries for fetching all channels directly in our view layer. Let's fix that by extracting a dedicated view composer.
  • 14

    Episode 14 Run Time 7:37

    A User Can Filter All Threads By Username

    It would be nice if any user could have a link that displays only the threads that they've personally created. Even beyond that, why not allow for the ability to view any forum user's threads? Let's figure out how in this episode.

    View the source code for this lesson on GitHub.

  • 15

    Episode 15 Run Time 18:47

    A Lesson in Refactoring

    Since it seems that filtering will be an important component to our application, let's take a bit of time to perform some refactoring. Luckily, because we have a set of tests to back us up every step of the way, we can be as bold as we wish. There's no fear of breaking the app, if your tests will notify you the second you make a refactoring error.

    View the source code for this episode on GitHub.

  • 16

    Episode 16 Run Time 9:19

    Meta Details and Pagination

    We should add a sidebar to each thread page for various meta information, such as when the thread was published, how many replies it has, and more. Further, we've yet to add pagination to our app. What happens when a thread has over one hundred replies? Let's ensure that we put the proper pagination links in place.

    View the source code for this episode on GitHub.

  • 17

    Episode 17 Run Time 11:51

    A User Can Filter Threads By Popularity

    It would be nice if users had the ability to filter all threads by popularity. That way, the most active threads will bubble to the top of the stack. Let's write a test and then implement this very feature.

    View the source code for this lesson on GitHub.

  • 18

    Episode 18 Run Time 15:46

    A User Can Favorite Replies

    It would be useful if authenticated users could have the ability to "favorite" any reply within a thread. Let's begin implementing that functionality now, by using polymorphic relations.

    View the source code for this episode on GitHub.

  • 19

    Episode 19 Run Time 7:37

    The Favorite Button

    Now that we've tested the full process of favoriting a reply, we can move on to creating the form to process this action for the user. In the process, we'll begin discussing the N+1 problem.

  • 20

    Episode 20 Run Time 7:06

    From 56 Queries Down to 2

    Let's review the N+1 problem, as it relates to Eloquent. To do so, we'll install Laravel Debugbar so that we can analyze the exact SQL queries that are being executed for each page load. As you'll learn, there are a variety of simple steps we can follow to reduce our query count by the dozens.

  • 21

    Episode 21 Run Time 8:28

    Global Scopes and Further Query Reduction

    In this episode, we'll continue optimizing our SQL queries. Specifically, we'll review global Eloquent scopes and the useful $with property to automatically eager load any necessary relationships.

    View the source code for this lesson on GitHub.

  • 22

    Episode 22 Run Time 12:32

    A User Has a Profile

    It would be useful if every user in our forum had an associated profile page. That way, we can review more information about them, including all threads that they've personally created.

    View the source for this episode on GitHub.

  • 23

    Episode 23 Run Time 14:07

    A User Can Delete Their Threads

    One simple ability that we haven't yet implemented is the option to delete threads. If "John Doe" creates a thread and later changes his mind, let's allow him to delete it entirely.

  • 24

    Episode 24 Run Time 10:28

    Authorization With Policies

    We must be careful that we don't inadvertently give any registered forum user the ability to delete all threads. Let's create a policy class to ensure that this can't happen.

    View the the source code for this episode on GitHub.

  • 25

    Episode 25 Run Time 16:37

    How to Construct an Activity Feed with TDD

    In this episode, we'll use TDD to drive out an activity feed. That way, we can, for example, track when a user creates a new forum thread, or posts a reply. As always, we'll begin with the most basic possible implementation. Once we get to green, we can then move on to the refactoring stage to clean things up drastically.

    View the source code for this episode on GitHub.

  • 26

    Episode 26 Run Time 16:21

    How to Construct An Activity Feed with TDD: Part 2

    Now that we've written the necessary code to record all relevant activity, in this episode, we can render it onto the user's profile page, and group all relevant records according to their date.

    View the source code for this episode on GitHub.

  • 27

    Episode 27 Run Time 7:39

    Extracting Controller Queries to the Model

    At the moment, we have a long, fluent Eloquent query in our controller. Instead, let's use TDD to extract it into the Activity model.

    View the source code for this episode on GitHub.

  • 28

    Episode 28 Run Time 5:10

    The Activity Deletion Bug

    I think we have a bug in our activity feed. What happens if we delete a thread? Will that cascade and delete all relevant activity in the process? And, if not, what happens when we try to view the user's profile page? Hmm, let's write a regression test to find out.

    View the source code for this episode on GitHub.

  • 29

    Episode 29 Run Time 13:59

    Flash Messaging With Vue

    In this episode, we'll implement an elegant flash messaging system, using Vue. That way, when a user performs an important action, we can flash a quick message to indicate the outcome.

    View the source code for this episode on GitHub.

  • 30

    Episode 30 Run Time 6:06

    A User's Activity Feed Should Include Favorited Replies

    At the moment, a user's activity feed will exclusively display a timeline of their own threads and replies. Let's extend that in this episode to include any replies that they've favorited.

    View the source code for this episode on GitHub.

  • 31

    Episode 31 Run Time 7:59

    Authorized Users Can Delete Replies

    We're still missing a very basic piece of functionality. Any authorized user should be able to delete a reply. Let's implement that in this episode.

    View the source code for this episode on GitHub.

  • 32

    Episode 32 Run Time 13:51

    A Vue Reply Component

    We're starting to realize that each individual forum reply should have a decent amount of behavior associated with it. With that in mind, in this episode we'll create a dedicated Vue component for a reply, and then implement the necessary functionality to quickly edit the body of a reply without requiring a page refresh.

    View the source code for this episode on GitHub.

  • 33

    Episode 33 Run Time 4:22

    Ajaxifying the Delete Button

    Now that each reply is wrapped within a dedicated Vue instance, we can easily swap out the traditional form for deleting the reply with a snappier AJAX version that doesn't require a page refresh.

    View the source code for this episode on GitHub.

  • 34

    Episode 34 Run Time 16:28

    A Vue Favorite Component

    We have one last piece of the puzzle, when it comes to our Reply component. The favoriting functionality still consists of a traditional form. Let's turn that into a dedicated Favorite component to clean things up.

    View the source code for this episode on GitHub.

  • 35

    Episode 35 Run Time 8:31

    Squashing Bugs

    I think we've introduced a couple of bugs related to adding and removing activity. Let's work through them in this episode and patch things up, before moving on to a new feature.

    View the source code for this episode on GitHub.

  • 36

    Episode 36 Run Time 21:59

    A More Data-centric Approach

    In this episode, we'll do a decent amount of refactoring to push toward a more data-centric approach, when it comes to our Vue components. As part of this refactor, we'll need to figure out how to rewrite inline-templates that use Blade into JavaScript.

  • 37

    Episode 37 Run Time 11:44

    A New Reply Component

    Now that we're thinking in terms of Vue collections, we can easily turn the form for publishing a new reply into its own component. This way, when the user submits the form, we can simply perform an AJAX call to persist the data, and then push to the "replies" collection - which will, in response, automatically trigger a re-render.

    View the source code for this episode on GitHub.

  • 38

    Episode 38 Run Time 31:02

    Laravel and Vue Pagination

    Since we're now rendering all replies with JavaScript, we can no longer depend upon rendering pagination links on the server-side. To compensate, let's create a dedicated paginator Vue component to handle all the necessary logic behavior.

    View the source code for this episode on GitHub.

  • 39

    Episode 39 Run Time 12:52

    A User Can Filter By Unanswered Threads

    I've learned from running the Laracasts forum that users often want to filter all threads according to those that have not yet received replies. Let's add that very functionality in this episode.

    View the source code for this episode on GitHub.

  • 40

    Episode 40 Run Time 10:53

    Thread Subscriptions: Part 1

    It would be useful if users could subscribe to any number of threads in our forum. That way, each time a reply is left, the user will immediately be notified. Let's get started implementing this very functionality.

  • 41

    Episode 41 Run Time 6:17

    Thread Subscriptions: Part 2

    Now that our model tests prove that users can properly subscribe to threads, let's move up a level and right the general feature that describes what should happen when a particular endpoint is triggered.

  • 42

    Episode 42 Run Time 15:12

    Thread Subscriptions: Part 3

    Let's take a bit of time to work on the UI side of things. We need to prepare a "Subscribe" Vue component that handles the behavior of toggling the current user's subscription to any given thread.

    View the source code for this episode on GitHub.

  • 43

    Episode 43 Run Time 25:30

    Thread Subscriptions: Part 4 New

    Now that a thread exposes the behavior that it can be subscribed to, we can move on to preparing all relevant user notifications each time the thread receives a new reply. To allow for this, we'll leverage Laravel's native notification functionality.

  • 44

    Episode 44 Run Time 6:15

    Test Refactoring New

    Let's take a few moments to refactor the tests in our NotificationsTest class. A handful of tweaks should make this file far more simple to read and understand six months from now.

  • 45

    Episode 45 Run Time 13:22

    Thread Subscriptions: Part 5 New

    We can finally switch over to the client-side, and begin constructing a user-notifications Vue component that will be responsible for fetching all relevant notifications and rendering them within a "bell" dropdown panel.

    View the source code for this episode on GitHub.

  • 46

    Episode 46 Run Time 10:25

    Refactoring for the Better or Worse? New

    Let's perform a round of refactoring in this episode. But there's a twist! At the conclusion of the refactor, we'll take a very important step. That step is to ask ourselves, "Is the code better as a result of this refactor? Or did we make it more confusing?"

  • 47

    Episode 47 Run Time 5:20

    Notification Fakes in a Nutshell New

    If you check our ThreadTest class, you'll notice that we don't yet have a test that asserts that, each time a reply is added to a thread, a notification should be prepared for all thread subscribers. Let's use a notification fake to test this functionality.

    Review the source code for this episode on GitHub.

*Series still in development. Check back often for updates.