Most Laravel tutorials hand you working code and walk you through it. That’s fine for seeing how the pieces fit, but it doesn’t teach you much about what happens when you leave a field blank, type the wrong parameter name, or hesitate before running a command you don’t fully trust yet. So instead of following […]

Most Laravel tutorials hand you working code and walk you through it. That’s fine for seeing how the pieces fit, but it doesn’t teach you much about what happens when you leave a field blank, type the wrong parameter name, or hesitate before running a command you don’t fully trust yet.
So instead of following a tutorial, I built something small from scratch: Tiny Journal, a single-page journaling app. Write an entry, read it back, edit it, delete it. No auth, no tags, no search. Small enough to finish in a weekend, big enough to hit real Laravel concepts along the way.
This is the process I followed, in order, including the parts that broke.
• Familiarity with MVC at a conceptual level, you don’t need Laravel experience specifically
Everything starts with one model, , and one migration:
Two fields, and , both required at the database level. That last detail matters more than it looks like it should, it’s the reason the very first real bug in this project existed at all.
With the table in place, the first working feature was just displaying entries:
already uses route model binding here, Laravel matches the placeholder in the route to the parameter and fetches the record automatically, no manual needed. I didn’t fully appreciate this until later, when I tried writing the long way and noticed the mismatch (more on that below).
This worked, right up until I submitted the form with the title left blank. What came back was a full-page database error, a NOT NULL constraint violation, straight from the migration in Step 1. The database was correctly enforcing a rule I’d written myself, just in the worst possible place, after the form had already submitted, with a stack trace instead of a sentence the user could act on.
The fix is validation, called before anything touches the database:
either lets the request through or stops it and redirects back to the form automatically. To actually display the error, loop over Laravel’s error bag in the view:
One gotcha: isn’t a plain array you can loop over directly, it’s a , and you need to pull the message strings out. Loop over itself and you get nothing, no error, no crash, just silence.
Two things surfaced here. First, the same blank-title crash from Step 3 was still possible on , since it had no validation yet. The fix is identical, just sequenced correctly, validate before writing anything to , not after:
Second, comparing and side by side made route model binding click. uses and never calls . still used the manual + pattern. Once I rewrote to match:
The rule became obvious: route model binding works whenever the URL contains an ID pointing at something that already exists. That’s true for , , and . It’s never true for or , there’s no specific record in those URLs to bind to in the first place.
After adding validation, something looked right for the wrong reason. Type a real title, leave content blank, submit. The page reloads, and the title field still shows what I typed. That looked like old input working. It wasn’t, the title was just , untouched, because validation stopped the save before anything reached the database.
The actual fix is the helper:
checks for leftover input from a failed submission first. If it exists, it wins. If the page opened fresh, it falls back to the entry’s stored value. The real test: change the title, blank out the content, submit. If it’s wired up correctly, your edited title survives the round trip instead of reverting.
Step 6: Deleting Entries, and Collapsing Six Routes Into One
By this point I had six hand-written routes, one per action:
Laravel collapses all six into one line:
I sat on this for longer than I should have, convinced switching would force changes elsewhere. It didn’t. The controller didn’t change at all, only the routes file did. isn’t generating new behavior, it’s the same six routes, same names, same verbs, pre-assembled.
By the time everything worked, all four views ( , , , ) had their own full , and “ tags, copy-pasted four times. A shared layout fixes this:
Each view shrinks to just its unique content:
Add a stylesheet link to the layout once, and every page picks it up automatically, no other file needs touching.
Looking back at the process above, the actual friction points were:
• A blank title crashing the app (Step 3), the database enforcing a rule at the wrong layer.
• **The same crash hiding in ** (Step 4), the same fix, just needing correct sequencing.
• Route model binding clicking (Step 4), once I compared a method using it against one that wasn’t.
• Old input looking like it worked before it actually did (Step 5), two different mechanisms that produced identical-looking output.
• **Hesitating before ** (Step 6), fear of a change that turned out to be safe.
• Four duplicated page shells (Step 7), fixed once the repetition got annoying enough to notice.
None of these were individually hard. What was hard was noticing, in the moment, that a bug or a hesitation was pointing at a missing concept, not a missing line of code.
Where Tiny Journal Goes From Here
A few obvious next steps if you’re building along:
• Flash messages after create, update, and delete, so the user gets confirmation instead of a silent redirect
• Stronger validation rules, minimum lengths, character limits, rather than just
• Soft deletes, so “delete” doesn’t mean permanently gone
If you’re learning Laravel right now: build something small, break it on purpose, and read the error instead of pasting it into a search bar immediately. The framework usually already has an answer for whatever repetition or fragility you’re feeling. You just have to hit the wall first to know which question to ask.…Read more by