Introduction
Imagine you're building a web application for your favorite coffee shop. You need to manage their menu, list their products, add new ones, edit existing ones, and even delete them. You could write all the code from scratch, but that's time-consuming and repetitive. That's where Laravel's Resource Controllers come in - they provide a streamlined way to handle common CRUD (Create, Read, Update, Delete) operations in your application.
This guide will walk you through building a simple CRUD application with Resource Controllers in Laravel. You'll learn how to set up a basic Laravel project, create Resource Controllers, and connect them to your database.
Setting Up Your Laravel Project
Let's start by setting up a fresh Laravel project.
-
Install Laravel:
composer create-project laravel/laravel my-coffee-shop
This command will create a new Laravel project named "my-coffee-shop."
-
Navigate into the project directory:
cd my-coffee-shop
-
Start the development server:
php artisan serve
You should see a message confirming that the server is running on
http://127.0.0.1:8000
. Open this URL in your browser, and you'll see the default Laravel welcome page.
Creating Your Model
Our application needs a way to represent coffee products in the database. Let's define a model for this purpose.
-
Create the Model:
php artisan make:model Coffee -m
This command generates a new model file named
Coffee.php
in theapp
directory. The-m
flag automatically creates a corresponding database migration. -
Open the
Coffee.php
file:<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Coffee extends Model { use HasFactory; protected $fillable = [ 'name', 'description', 'price', ]; }
We've defined the
fillable
property, which specifies the attributes that can be mass-assigned to the model. This ensures that our application only updates the relevant fields when saving data.
Defining Database Migrations
Laravel's migration system helps you manage changes to your database schema. We'll use a migration to create the coffees
table to store our coffee product data.
-
Run the migration:
php artisan migrate
This command executes the pending migration, creating the
coffees
table in your database. -
Open the newly created migration file:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateCoffeesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('coffees', function (Blueprint $table) { $table->id(); $table->string('name'); $table->text('description'); $table->decimal('price', 8, 2); // Define price as decimal with 2 decimal places $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('coffees'); } }
Here, we define the structure of the
coffees
table with columns forname
,description
,price
, and timestamps for creation and update. Thedecimal
type ensures accurate storage of prices.
Creating a Resource Controller
Laravel's Resource Controllers are a powerful tool for streamlining CRUD operations. They handle the logic for common actions like creating, displaying, updating, and deleting resources, making our code more organized and reusable.
-
Create the Resource Controller:
php artisan make:controller CoffeeController --resource
This command generates a new file named
CoffeeController.php
in theapp/Http/Controllers
directory. -
Open the
CoffeeController.php
file:<?php namespace App\Http\Controllers; use App\Models\Coffee; use Illuminate\Http\Request; class CoffeeController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { $coffees = Coffee::all(); return view('coffees.index', compact('coffees')); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { return view('coffees.create'); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $validatedData = $request->validate([ 'name' => 'required|max:255', 'description' => 'required', 'price' => 'required|numeric', ]); Coffee::create($validatedData); return redirect()->route('coffees.index')->with('success', 'Coffee created successfully.'); } /** * Display the specified resource. * * @param \App\Models\Coffee $coffee * @return \Illuminate\Http\Response */ public function show(Coffee $coffee) { return view('coffees.show', compact('coffee')); } /** * Show the form for editing the specified resource. * * @param \App\Models\Coffee $coffee * @return \Illuminate\Http\Response */ public function edit(Coffee $coffee) { return view('coffees.edit', compact('coffee')); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param \App\Models\Coffee $coffee * @return \Illuminate\Http\Response */ public function update(Request $request, Coffee $coffee) { $validatedData = $request->validate([ 'name' => 'required|max:255', 'description' => 'required', 'price' => 'required|numeric', ]); $coffee->update($validatedData); return redirect()->route('coffees.index')->with('success', 'Coffee updated successfully.'); } /** * Remove the specified resource from storage. * * @param \App\Models\Coffee $coffee * @return \Illuminate\Http\Response */ public function destroy(Coffee $coffee) { $coffee->delete(); return redirect()->route('coffees.index')->with('success', 'Coffee deleted successfully.'); } }
The Resource Controller provides ready-made methods for each CRUD action:
index()
: Retrieves all coffee products and displays them in a list.create()
: Shows a form for creating a new coffee product.store()
: Saves a newly created coffee product to the database.show()
: Displays details of a specific coffee product.edit()
: Shows a form for editing an existing coffee product.update()
: Updates the data of an existing coffee product in the database.destroy()
: Deletes a coffee product from the database.
Defining Routes
We need to define routes in Laravel to tell the application how to handle requests.
- Open the
routes/web.php
file:
The<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\CoffeeController; Route::resource('coffees', CoffeeController::class);
Route::resource()
method automatically registers all necessary routes for ourCoffeeController
. This makes our code concise and avoids repetitive route definitions.
Creating Views
Now we'll create the view files that will render our Coffee data.
-
Create the
resources/views/coffees
directory:- This directory will hold all the views related to our coffee products.
-
Create the
coffees/index.blade.php
view:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Coffee Shop - Products</title> <!-- Include Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> </head> <body> <div class="container"> <h1>Coffee Products</h1> <a href="{{ route('coffees.create') }}" class="btn btn-primary">Add New Coffee</a> <table class="table table-striped mt-4"> <thead> <tr> <th>Name</th> <th>Description</th> <th>Price</th> <th>Actions</th> </tr> </thead> <tbody> @foreach ($coffees as $coffee) <tr> <td>{{ $coffee->name }}</td> <td>{{ $coffee->description }}</td> <td>${{ $coffee->price }}</td> <td> <a href="{{ route('coffees.show', $coffee->id) }}" class="btn btn-info btn-sm">View</a> <a href="{{ route('coffees.edit', $coffee->id) }}" class="btn btn-warning btn-sm">Edit</a> <form action="{{ route('coffees.destroy', $coffee->id) }}" method="POST" style="display: inline-block;"> @csrf @method('DELETE') <button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this coffee?')">Delete</button> </form> </td> </tr> @endforeach </tbody> </table> </div> <!-- Include Bootstrap JS and jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> </body> </html>
-
Create the
coffees/create.blade.php
view:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Coffee Shop - Add Coffee</title> <!-- Include Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> </head> <body> <div class="container"> <h1>Add New Coffee</h1> <form action="{{ route('coffees.store') }}" method="POST"> @csrf <div class="form-group"> <label for="name">Name:</label> <input type="text" class="form-control" id="name" name="name" value="{{ old('name') }}"> @error('name') <span class="text-danger">{{ $message }}</span> @enderror </div> <div class="form-group"> <label for="description">Description:</label> <textarea class="form-control" id="description" name="description">{{ old('description') }}</textarea> @error('description') <span class="text-danger">{{ $message }}</span> @enderror </div> <div class="form-group"> <label for="price">Price:</label> <input type="number" class="form-control" id="price" name="price" value="{{ old('price') }}"> @error('price') <span class="text-danger">{{ $message }}</span> @enderror </div> <button type="submit" class="btn btn-primary">Add Coffee</button> </form> </div> <!-- Include Bootstrap JS and jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> </body> </html>
-
Create the
coffees/show.blade.php
view:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Coffee Shop - Coffee Details</title> <!-- Include Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> </head> <body> <div class="container"> <h1>Coffee Details</h1> <div class="card"> <div class="card-body"> <h5 class="card-title">{{ $coffee->name }}</h5> <p class="card-text">{{ $coffee->description }}</p> <p class="card-text">Price: ${{ $coffee->price }}</p> </div> </div> </div> <!-- Include Bootstrap JS and jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> </body> </html>
-
Create the
coffees/edit.blade.php
view:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Coffee Shop - Edit Coffee</title> <!-- Include Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> </head> <body> <div class="container"> <h1>Edit Coffee</h1> <form action="{{ route('coffees.update', $coffee->id) }}" method="POST"> @csrf @method('PUT') <div class="form-group"> <label for="name">Name:</label> <input type="text" class="form-control" id="name" name="name" value="{{ $coffee->name }}"> @error('name') <span class="text-danger">{{ $message }}</span> @enderror </div> <div class="form-group"> <label for="description">Description:</label> <textarea class="form-control" id="description" name="description">{{ $coffee->description }}</textarea> @error('description') <span class="text-danger">{{ $message }}</span> @enderror </div> <div class="form-group"> <label for="price">Price:</label> <input type="number" class="form-control" id="price" name="price" value="{{ $coffee->price }}"> @error('price') <span class="text-danger">{{ $message }}</span> @enderror </div> <button type="submit" class="btn btn-primary">Update Coffee</button> </form> </div> <!-- Include Bootstrap JS and jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> </body> </html>
Testing Your CRUD Application
Now that everything is set up, let's test our CRUD application:
-
Start the development server:
php artisan serve
-
Open
http://127.0.0.1:8000/coffees
in your browser:- You'll see the list of coffee products (currently empty).
-
Click "Add New Coffee":
- This takes you to the coffee creation form.
-
Fill in the details and click "Add Coffee":
- The new coffee product will be added to the database and displayed on the list.
-
Click "View", "Edit", and "Delete" to test the other CRUD actions:
- These actions should work as expected, allowing you to view, modify, and delete coffee products.
Example Usage
Imagine you're managing the menu for "The Daily Grind" coffee shop. Let's add some coffee products using our CRUD application.
-
Add a new coffee product:
- Name: "Espresso"
- Description: "A strong and bold coffee shot."
- Price: 2.50
-
Add another coffee product:
- Name: "Latte"
- Description: "Espresso with steamed milk and a layer of foam."
- Price: 3.75
-
Edit the "Latte" coffee product:
- Description: "Espresso with steamed milk and a layer of foam, available in various flavors."
-
Delete the "Espresso" coffee product:
- Since you're running out of espresso beans!
Best Practices
Here are some best practices to keep in mind when working with Resource Controllers:
- Model Validation: Always validate user input to prevent invalid data from entering your database. You can use Laravel's built-in validation rules in the
store()
andupdate()
methods of your Resource Controller. - Error Handling: Display meaningful error messages to the user in case of validation failures or other exceptions.
- Authorization: Implement authorization checks to ensure that only authorized users can perform certain actions (e.g., only administrators can delete coffee products).
- Clean Code: Write well-structured, readable code with appropriate comments. Follow Laravel's coding conventions for consistency.
FAQs
1. What are the benefits of using Resource Controllers?
Resource Controllers provide a structured and organized way to handle CRUD operations. They reduce code duplication and make your application more maintainable.
2. Can I customize the routes generated by Route::resource()
?
Yes, you can customize the routes generated by Route::resource()
by using the only()
and except()
methods to specify which routes you want to include or exclude.
3. How can I handle file uploads with Resource Controllers?
You can handle file uploads in the store()
method by using the $request->file('field_name')
method to access the uploaded file. You can then save the file to your storage directory and update the database record with the file path.
4. What if I need to add additional actions to my Resource Controller?
You can add custom methods to your Resource Controller for actions not covered by the default CRUD methods. These methods can be used for specific operations related to your resource.
5. Can I use Resource Controllers for complex actions involving multiple models?
Resource Controllers are primarily designed for CRUD operations on a single model. For complex scenarios, consider using regular controllers with more custom logic or exploring other patterns like "Repository" or "Service" patterns.
Conclusion
Laravel's Resource Controllers provide a powerful and convenient way to streamline CRUD operations in your application. They help you manage common actions efficiently, making your code cleaner and more maintainable. This guide has equipped you with the fundamental knowledge to build simple CRUD applications using Resource Controllers. Remember to implement best practices and explore Laravel's rich features to build robust and user-friendly web applications.