Laravel From Scratch (2026 Edition)
Welcome to the latest iteration of the longest running Laravel introductory video series in the world. Like clockwork, we refresh this series every two years. This latest 2026 iteration covers Laravel 12 and beyond.
You'll learn not only the basic fundamentals of Laravel, but also how to leverage more advanced features like authentication, authorization, queues, testing, and more. We'll even review the full deployment process from scratch.

Progress
Series Info
- Episodes
- 43
- Run Time
- 9h 40m
- Difficulty
- Beginner
- Last Updated
- Jan 14, 2026
- Version
- Latest
Series Episodes
- The Fundamentals (13)
Welcome Aboard
My name is Jeffrey Way, and welcome...to Laravel From Scratch: 2026 edition!Set Up Your Development Environment
Before we start writing any Laravel code, we need to get our development environment in order so we will useLaravel Herdto installPHP,ComposerandNodeand to scaffold a new project with theLaravelinstaller. Once the project is created we will openroutes/web.phpand thewelcomeview and tweak the URI and the view to get our feet wet and see how requests map to views.Routing 101
Let's explore how Laravel's expressive API lets us register routes and return views with things likeRoute::getand theviewfunction. We already saw the defaultwelcomehomepage so now we will add an/aboutroute and create a simple/contactview to practice linking between pages.Layout Files
We're going to clean up duplicated page markup by moving shared HTML into a reusable Blade layout component and wiring simple routes withRoute::view. Along the way we'll learn how to slot page specific content with$slot, pass atitleprop to the layout and merge any extra classes with$attributes->merge()so our components stay flexible.Pass Data to Views
Now that we've learned how to register a route and load a view, let's tackle passing data into that view using the third parameter ofRoute::viewor by returningview('welcome', [...])from a closure. We'll also read values from the query string withrequest('person', 'world')and see how Blade automatically escapes output unless we choose raw output with{!! $var !!}.Blade Directives
Okay, let's spend a few minutes exploring Blade directives and how they make our view code much nicer to write. We'll look at debugging with@dumpand@ddand common control flow directives like@if,@foreachand@forelse.Forms
Let's build a tiny digital notebook and get comfortable with form handling in Laravel by accepting user input and saving it for later. We'll send aPOSTto anideasroute, include the@csrftoken, grab the value with therequesthelper, push it into thesession, and redirect back to the homepage.Databases, Migrations, and Eloquent
Okay, now that we've been keeping ideas in the session, we're going to switch over to a real database and learn how migrations and models let us version and query that data. We'll runphp artisan make:migrationto add anideastable with adescriptionandstate, runphp artisan migrate, create anIdeamodel, and useIdea::create()plus queries likewhere('state', 'pending')->get()to store and filter our ideas.HTTP Requests and REST
Now that we've met Eloquent let's build out a simple CRUD flow for theideasresource so we can list show edit update and delete individual items. Along the way we'll lean on Laravel conventions likeroute model bindingandmethod spoofingso our routes and forms behave correctly and we can focus on the important logic not boilerplate.Controllers
We are going to pull the create form out of the index view and give it its ownideas/createpage so creating ideas is clearer. Then we will create anIdeaControllerwith the seven resourceful actions and move our route closures into controller methods so our routes file becomes a single place that lists every route.Request Validation
Okay, now that submitting an emptydescriptioncan make us reach the database and fail, let's add validation so we catch those problems earlier and more gracefully. We'll validate in thestoreaction withrequest->validate, surface messages from theerrorsbag using the@errorblade directive, and extract a reusablex-errorcomponent for tidy markup.Form Request Classes
Okay, now that we have inline validation working, let's move the rules into a dedicatedform requestclass so Laravel will run validation automatically when we type it into acontroller. We'll scaffold aStoreIdeaRequest, use theauthorizeandrulesmethods to control access and validation, tweakmessagesif we want, and decide whether to keep separate classes for store and update or share a singleIdeaRequestwhen the rules are identical.A Brief DaisyUI Detour
Now that we have the basics wired up, let's spend ten minutes polishing the UI by pulling intailwindanddaisyuiso we can rapidly assemble navigation areas, cards, and form controls. We'll extract the nav into its own component and turn each idea into a reusableidea-cardso the layout looks real and we can reuse styles across the app.
- Authentication and Authorization (5)
Authentication Explained
Now that we have our UI scaffolding in place, let's implement authentication by wiring up theregisterandloginroutes and creating theRegisteredUserControllerandSessionsControllerto show forms and handle submissions. We will validate input with$request->validate(), hash passwords usingHash, use the@authand@guestdirectives in the nav, and add a logout endpoint so we can sign users in and out.Require Authentication With Middleware
Now we're going to associate eachIdeawith a user by adding auser_idforeign key usingforeignIdForand cascading deletes, and we'll tidy up access by applying theauthandguestmiddleware to the appropriate routes. Then we'll make new ideas use the current user viaAuth::id()and filter the index so you only see your own ideas and next time we'll swap the long form queries for a proper Eloquent relationship so we can just call$user->ideas.Eloquent Relationships
Let's wire up the Eloquent relationships so ourIdeaknows its creator withbelongsToand ourUserexposes their ideas withhasMany. Then we'll lean on those relationships to read and create records withauth()->user()->ideas()->create()and get ready to fix a security hole where users can currently view other people's ideas.Authorization Using Gates
We're going to lock down parts of the app with Laravel authorization by defining gates in theAppServiceProviderand using the@canblade directive to only show links to people who should see them. Then we'll protect the endpoints themselves with thecanroute method orGate::authorizeand talk through simple choices like allowing optional guest checks and returning a 404 instead of a 403 when you want to hide an admin route.Authorization Using Policies
Now that we've played with the Gate API, let's move on to policies and create anIdeaPolicyso we can centralize authorization for theIdeamodel. We'll implement anupdaterule and see how to call it from controllers with$this->authorize('update', $idea)or middleware and how to referenceIdea::classwhen no model instance exists.
- Digging Deeper (4)
Frontend Asset Bundling with Vite
Now that we have been pulling Tailwind and Daisy UI from a CDN, let's switch to local bundling withviteso our assets are compiled and optimized with the rest of the app. We will wire up the@vitedirective, editresources/css/app.cssand then usenpm run devfor hot reloads ornpm run buildfor production.Notifications
Okay now that we can create ideas let's make sure the owner gets notified whenever a new idea is persisted using Laravelnotifications. We'll scaffold anIdeaPublishednotification withphp artisan make:notification, preview the email withMailpit, and dispatch it from theIdeaController@storeaction.When to Queue it Up
Now that we've covered sending notifications and emails, let's touch on moving that work into the background with queued jobs by implementingShouldQueueso users are not left waiting. We'll learn how to push tasks onto thequeue, run aworkerwithphp artisan queue:workand peek at thejobstable to see everything being processed.How to Get Started Testing Your Code
Now that the bulk of the app is built we'll finish up by learning Pest PHP and browser testing so we can stop testing things manually and start automating real user flows. We'll wire up the Playwright plugin, runphp artisan testorpest, usevisitanddebugto inspect failures and then write practical tests for registration and the/ideaspages including using adata-testattribute to target elements.
- Final Project: Build and Deploy an App (21)
Final Project Setup
We're going to harness everything we've learned and together build a simple but feature rich app calledIdeaso we can deploy it and keep learning from real world examples. Along the way we'll follow my exact workflow for initializing the repo, pushing toGitHuband deploying withForge, then set up tooling likePint,Rector,Code RabbitandLaravel Boostso you can see how a modernLaravelproject gets built and maintained.Design Your Model Layer
Before we jump into the UI, let's get our domain and database shape sorted by scaffolding anIdeamodel with a migration and factory plus a companionStepmodel and anIdeaStatusenum. We'll wire up the relationships set sensible default attributes and add a few quick tests so the UI has a solid foundation to build on.Tailwind Theme Setup And Initial UI
We're going to nail down our Tailwind theme and extract a few reusable UI pieces so building pages gets a lot less tedious. We'll create alayoutsfile with a nav, pull out a reusableformandfieldcomponent and then wire up routes and controllers forregisterandloginso users can sign up and sign in.Browser Testing Registration Forms With Pest
Now that registration and login routes are in place we're going to write browser tests that exercise theregister,loginandlogoutflows using Pest PHP's browser helpers. We'll arrange users with auser factory, target elements withdata-testattributes and usedebugmode to verify both the happy path and the unhappy paths like a missing email behave as expected.Flash Messaging and Interactivity with AlpineJS
Now we are going to make our flash messages feel alive by adding lightweight interactivity withAlpine.js. We'll wire a tinyx-datacomponent that usesx-show, a timeout and atransitionso the sessionsuccessmessage automatically fades away after a few seconds.Idea Cards
All right, we are going to build the page that displays our idea cards and wire up a route and controller to return a view for/ideas. We will extract a reusable Blade component likex-cardand a status label component likex-idea-statusso the list looks polished, and next time we will add filtering and pagination.Idea Filtering
Now we're going to add filtering so we can show ideas bystatusfrom the query string and wire that up with an Eloquent scope. We'll also build the little filter pills that display counts perstatusand move the counting logic into theIdeamodel so the view stays clean.Show A Single Idea
Now that we can click into an idea, let's build the single idea page by creating theshow.blade.phpview and wiring it to theIdeaController@showaction. We will lay out the title and description, add a top navigation with component icons, render links inside a card, and wire up a delete form that posts to theidea.destroyroute so the page is functional and ready for the next steps.Create A Functional Modal With AlpineJS
Okay, now that we have the idea index with a big clickable card, let's build a modal for creating and editing ideas that opens when the card dispatches anopen-modalevent with the modal name. We'll wire it up withx-dataandx-show, addx-transitionplus click away and escape handlers, tighten up ARIA attributes and then extract it into a reusable<x-modal>component.Construct The Idea Form
Okay now that we have basicmodalsupport we're going to build the actualformfor creating ideas and wire it up to thePOST /ideasroute and thestoreaction. We'll make ourfieldcomponent textarea aware add an Alpine powered status selector that writes to a hiddeninputenforce the minimal client side constraints and persist the idea so new entries show up at the top of the list.Test The Create Idea Form
Okay, we already have a nice modal for creating ideas but it only works when we test it by hand so in this lesson we will automate that flow with a browser test. We will adddata-testattributes to elements like thecreate idea buttonand thebutton statusoptions then write a test thatvisitsthe page,clicksandfillsthe form whileactingAsa user and then asserts the idea was persisted.Allow For One or Many Links
Alright, let's add support for attaching links to an idea by giving users an input with a plus button that appends whatever they typed into alinksarray. We'll track the typed value withx-modelonnew link, render each link as a named input likelinks[]so the traditional form submits them as an array and then validate them on the server.Actionable Steps
We are adding actionablestepsto ideas by reusing the modal markup, wiring Alpine inputs to astepsarray, and validating the payload so we can persist those items into a dedicatedstepstable related to each idea. Then we will render the steps on the idea show page and let us toggle thecompletedstate with small per step forms that hit aPATCHroute handled byStepController@update.Upload Featured Images To Storage
Let's add featured images above each idea by dropping afileinput into the form and setting the form enctype tomultipart/form-dataso uploads are sent to the server. We'll persist the files to storage, expose them withphp artisan storage:linkand render them in the show and index views then start pulling idea creation logic out of the controller into a dedicatedActionclass.Action Classes
We're going to pull the idea creation logic out of the controller and into a dedicated action class calledCreateIdeathat exposes ahandlemethod so the code is reusable and easier to test. Along the way we'll decouple from the request by passing explicit attributes and using constructor injection with Laravel'sCurrentUserattribute so the action can be invoked from anywhere.Authorization Is A Requirement
We just ran a quick review of our refactor and added a test to prove that steps actually get created so issues like an undefined variable inside a transaction closure do not slip through. Now we are going to tackle authorization using gates and a simpleIdeaPolicy, showing how to callauthorizein the controller or use thecanmiddlewareon routes and adding tests to lock down access.The Edit Idea Modal
We want to let users edit an existing idea by reopening the same modal we use to create ideas, so we'll start by writing a test that clicks theedit ideabutton. When that failed we discovered the modal is hard coded on the index page, so we'll extract it into its own Alpine component atidea/modaland make sure theopen modaldispatch works on the show page.Update Idea Action
Okay, now we are tackling updating an idea by adding a dedicatedupdateIdeaaction and hooking it into theupdatecontroller so we can manage image uploads, update attributes, and shift the form to an object basedstepsstructure. To keep things simple we will sync thestepsby wiping and rebuilding them instead of doing tricky upserts and we will lock it down with browser and unit tests including usingassertValueto verify initial form values.Edit Your Profile
Now that users can sign up we are adding an edit profile flow so they can visit/profile/edit, open a view populated fromauth()->user()and reach it via a link in our navigation that points to aProfileController. We will wire upeditandupdateactions, validate and conditionally update the password notify the original email address when the email changes and cover the behavior with tests usingNotification::fakeand on demand assertions.Deploy And Then Implement A Feature Request
Let's finish up by runningcomposer run format, running our test suites, and pushing the changes to Forge so we can watch a real deploy. Then we'll add aformatted_descriptionaccessor that uses Laravel'sStr::markdownand bring in Tailwind's typography plugin so our markdown descriptions render nicely in production.Where To Go From Here
Now that we have the app finished let's pick a path for improving it, like adding team support so people can collaborate and we can practice more advanced authorization. If you want to keep learning we can exploreLaravel Livewirefor PHP driven interactivity or try pairingVue.jsorReactwithInertia.jsto build a richer SPA experience.