Capstone: blog
The spec
Build a small blog with posts and comments: visitors read the list and single pages, authors create and edit posts, readers leave comments, and a smoke test proves the pages answer. Four moving parts, each one borrowed from an earlier lesson.
Posts: list, single page, create, edit Comments: add to a post, shown under it Validation: title required, body required Tests: pages answer with status 200
Posts CRUD
Two routes carry the whole feature: a GET route that lists posts and a POST route that stores them. Validation runs before the insert, and a redirect sends the reader to the fresh page instead of re-posting on refresh.
Route::get("/posts", [PostController::class, "index"]); Route::post("/posts", [PostController::class, "store"]); public function store(Request $request) { $data = $request->validate([ "title" => "required|max:255", "body" => "required", ]); $post = Post::create($data); return redirect()->route("posts.show", $post); }
Comments
A comment belongs to a post, so the Post model declares a hasMany relation and the comment form posts to a nested route. The controller validates the body, attaches the comment through the relation, and redirects back to the post.
public function comments() { return $this->hasMany(Comment::class); } public function store(Request $request, Post $post) { $data = $request->validate(["body" => "required"]); $post->comments()->create($data); return redirect()->back(); }
Smoke tests
Smoke tests ask the simplest question: does the page answer. Hit the list and a single post, assert a 200 status, and assert that a known title appears. Green smoke means the wiring holds before you polish the design.
public function test_post_pages_answer(): void { $list = $this->get("/posts"); $list->assertOk(); $one = $this->get("/posts/1"); $one->assertOk(); }
Next: Capstone: JSON API
Article author: Arthur Isaev