CRUD Step 2: Show the List (Read)
Fill the first controller methods and build the page that lists all your products in a table.
What you will learn
- Write the index() and show() methods
- Build a Blade view that loops over records
- Use route model binding to fetch one record automatically
Read first — there is nothing to edit yet
We start with Read because the table is empty and there is nothing to create or edit yet. Read is two methods: index() lists all products, and show() displays one.
The index() method
Open ProductController and fill the index() method. It gets all the products and hands them to a view:
public function index()
{
$products = Product::all(); // get every product
return view('products.index', compact('products')); // send them to the view
}Step by step: Product::all() fetches every row from the products table as a collection. compact('products') is shorthand for ['products' => $products] — it passes the data to the view under the name $products. view('products.index', ...) loads the file resources/views/products/index.blade.php (the dot means a folder).
You also need to add use App\Models\Product; at the top of the controller so it knows what Product means.
The index view — a table of products
Create the file resources/views/products/index.blade.php. It loops over $products and shows each one in a table row, with a link to add a new one:
<h1>Products</h1>
<a href="{{ route('products.create') }}">+ Add Product</a>
<table border="1" cellpadding="8">
<tr><th>Name</th><th>Price</th><th>Actions</th></tr>
@forelse ($products as $product)
<tr>
<td>{{ $product->name }}</td>
<td>₹{{ $product->price }}</td>
<td>
<a href="{{ route('products.show', $product) }}">View</a> |
<a href="{{ route('products.edit', $product) }}">Edit</a>
</td>
</tr>
@empty
<tr><td colspan="3">No products yet — add your first one!</td></tr>
@endforelse
</table>What the new pieces do: @forelse ... @empty ... @endforelse loops over the products, but if the list is empty it shows the “No products yet” row instead. {{ $product->name }} prints a value safely (it escapes HTML, blocking attacks). route('products.create') and route('products.edit', $product) build the correct URLs from the route names — so you never hard-code URLs.
Note: Output: A page titled “Products” with an “+ Add Product” link and a table listing every product (name, price, and View | Edit links). When there are none, it shows “No products yet”.
The show() method — one product
public function show(Product $product)
{
return view('products.show', compact('product'));
}Notice we type Product $product in the arguments. This is route model binding, and it is a huge time-saver: Laravel sees the {id} in the URL /products/5, automatically runs Product::find(5) for you, and passes the found product in as $product. If no product with that id exists, Laravel automatically shows a 404 Not Found — you write none of that.
<h1>{{ $product->name }}</h1>
<p>Price: ₹{{ $product->price }}</p>
<p>{{ $product->description }}</p>
<a href="{{ route('products.index') }}">← Back to list</a>This view just prints the single product’s details and a link back to the list.
Tip: Route model binding (Product $product in the method) does the “find it or 404” work for you on show, edit, update and destroy — that is why those four methods can take a $product directly.
Q. In show(Product $product), how does Laravel know which product to load?
✍️ Practice
- Build the
index()method andindex.blade.phpfor your Tasks resource and confirm the list page loads. - Add a “Created” column showing
{{ $product->created_at->diffForHumans() }}.
🏠 Homework
- Explain, in your own words, what
compact('products')does and what@forelseadds over a normal@foreach.