Laravel Basic DB CRUD Web and API project - Todos Project
[0] Create a new project
[1] Create Migration
Use Laravel's migration feature to create the table.
Run the following command in your terminal:
php artisan make:migration create_todos_table --create=todos
The above command will generate a new migration file in the database/migrations
directory.
Open the generated migration file and define the table schema in the up
method. For example:
..
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->id();
$table->string('task');
$table->timestamps();
});
}
..
Full code:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTodosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('todos', function (Blueprint $table) {
$table->id();
$table->string('task');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('todos');
}
}
After defining the table schema, run the migration command to create the "todos" table in the database:
php artisan migrate
This command will execute all pending migrations and create the necessary table.
[2] Create Controller
To perform CRUD operations (Create, Read, Update, Delete) against the "todos" table, you need a controller.
Run the following command in your terminal:
php artisan make:controller TodoController --resource
The above command will generate a TodoController
file in the app/Http/Controllers
directory. The --resource
flag indicates that you want to generate a controller with resourceful methods that correspond to the CRUD operations.
Edit the controller.
File:App\Http\Controllers\TodoController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class TodoController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$todos = DB::table('todos')->get();
return view('todo', ['todos' => $todos]);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
// validate the form
$request->validate([
'task' => 'required|max:200'
]);
// store the data
DB::table('todos')->insert([
'task' => $request->task
]);
// redirect
return redirect('/todos')->with('status', 'Task added!');
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
// validate the form
$request->validate([
'task' => 'required|max:200'
]);
// update the data
DB::table('todos')->where('id', $id)->update(
['task' => $request->task]);
// redirect
return redirect('/todos')->with('status', 'Task updated!');
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
// delete the todo
DB::table('todos')->where('id', $id)->delete();
// redirect
return redirect('/todos')->with('status', 'Task removed!');
}
}
[3] Create Web Route
To make the CRUD methods accessible to web users, you need to define routes that map to the corresponding methods in your TodoController
.
Edit web route.
File: Route\web.php
...
use App\Http\Controllers\TodoController;
Route::resource('todos', TodoController::class);
...
But we are not using all endpoints.
We can specify required endpoints as follows:
...
use App\Http\Controllers\TodoController;
Route::get('/todos', [TodoController::class, 'index']);
Route::post('/todos', [TodoController::class, 'store']);
Route::put('/todos/{id}', [TodoController::class, 'update']);
Route::delete('/todos/{id}', [TodoController::class, 'destroy']);
...
Or, we can simply write shorter version as follows:
...
use App\Http\Controllers\TodoController;
Route::resource('todos', TodoController::class)->only([
'index', 'store', 'update', 'destroy'
]);
...
Laravel's Route::resource
method generates the standard set of CRUD routes for a resourceful controller based on the TodoController
class. Each route corresponds to a specific HTTP verb and maps to a method of the controller.
When a request is made to any of the generated routes, Laravel will automatically determine the correct method to invoke in the TodoController
based on the HTTP verb and the route URL.
For example:
A GET request to
/todos
will invoke theindex
method ofTodoController
.A POST request to
/todos
will invoke thestore
method ofTodoController
.A PUT request to
/todos/{id}
will invoke theupdate
method ofTodoController
, with the{id}
parameter automatically resolved.A DELETE request to
/todos/{id}
will invoke thedestroy
method ofTodoController
, with the{id}
parameter automatically resolved.
Laravel's route resolution mechanism automatically maps the incoming HTTP request to the appropriate method in the controller based on the route definition and the HTTP verb used. It also automatically resolves any route parameters like {id}
and passes them as arguments to the corresponding controller method.
Inside each method of the TodoController
, you can define the necessary parameters to receive the incoming requests and any additional route parameters as needed.
By using the Route::resource
method, Laravel automatically generates the necessary routes and handles the parameter resolution for you, making it easier to define and maintain resourceful controllers.
At this point, if we browse the endpoints, we may get get the following error.
The error message "View [todo] not found" suggests that the Blade view file named todo.blade.php
does not exist or cannot be found.
To resolve this issue, create the todo.blade.php
file in the resources/views
directory of your Laravel application. The file should have the .blade.php
extension.
File: Resources/Views/todo.blade.php
<!-- todo.blade.php -->
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<style>
body{
padding:100px;
}
</style>
<title>Todos</title>
</head>
<body>
<h1>Todo List</h1>
<hr>
<h2>Add task</h2>
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ url('/todos') }}" method="POST">
@csrf
<input type="text" class="form-control" name="task" placeholder="Add new task">
<button class="btn btn-primary" type="submit">Store</button>
</form>
<hr>
<h2>Tasks</h2>
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<ul class="list-group">
@foreach($todos as $todo)
<li class="list-group-item">
{{ $todo->task }}
<button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-{{ $loop->index }}" aria-expanded="false">
Edit
</button>
<form action="{{ url('todos/'.$todo->id) }}" method="POST" style="display: inline-block;">
@csrf
@method('DELETE')
<button class="btn btn-danger" type="submit">Delete</button>
</form>
<div class="collapse mt-2" id="collapse-{{ $loop->index }}">
<div class="card card-body">
<form action="{{ url('todos/'.$todo->id) }}" method="POST">
@csrf
@method('PUT')
<input type="text" name="task" value="{{ $todo->task }}">
<button class="btn btn-secondary" type="submit">Update</button>
</form>
</div>
</div>
</li>
@endforeach
</ul>
<hr>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
</body>
</html>
Test
Add an item:
Outcome:
Edit the item:
Outcome:
Delete the item:
[4] Using Model instead of DB Facade
In Laravel, the "DB facade" refers to the DB
class, which is a static facade that provides a simple and convenient interface to interact with the database without the need to create explicit model classes.
The DB
facade allows you to perform database operations using raw SQL queries or query builder methods. It provides a set of static methods that you can call directly on the DB
class to execute queries, insert/update/delete records, retrieve data, and perform other database-related tasks.
The DB
facade provides a way to perform database operations when you don't need the advanced features of models, such as relationships, validation, and attribute casting. It's useful for quick database interactions or for cases where you want more control over the SQL statements.
However, it's generally recommended to use models when working with databases in Laravel, as models provide a higher level of abstraction, encapsulation, and additional features like relationships and attribute casting. Models promote code organization, reusability, and follow the object-oriented paradigm. The DB
facade is more suitable for simple queries or situations where you don't have a dedicated model for a specific table or collection.
Create the Todo model by running the following command in your terminal or command prompt:
php artisan make:model Todo
File: App/Models/Todo.php
To allow the field "task" for data entry, update the model as follows:
protected $fillable = [
'task',
];
Full code:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Todo extends Model
{
use HasFactory;
protected $fillable = [
'task',
];
}
We will implement this model into an api controller.
api controller does not have forms.
[5] Create API controllers
Edit controller.
File
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Todo;
class TodoController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$todos = Todo::all();
return response()->json(['todos' => $todos]);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'task' => 'required|max:200'
]);
$todo = Todo::create([
'task' => $request->task
]);
return response()->json(['todo' => $todo], 201);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$todo = Todo::find($id);
if (!$todo) {
return response()->json(['message' => 'Todo not found'], 404);
}
return response()->json(['todo' => $todo]);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$request->validate([
'task' => 'required|max:200'
]);
$todo = Todo::find($id);
if (!$todo) {
return response()->json(['message' => 'Todo not found'], 404);
}
$todo->task = $request->task;
$todo->save();
return response()->json(['todo' => $todo]);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
$todo = Todo::find($id);
if (!$todo) {
return response()->json(['message' => 'Todo not found'], 404);
}
$todo->delete();
return response()->json(['message' => 'Task removed!']);
}
}
Code explanation:
The
index()
method retrieves allTodo
records using theTodo
model'sall()
method and returns them as a JSON response.The
store()
method creates a newTodo
record using thecreate()
method of theTodo
model and returns the created record as a JSON response.The
show()
method retrieves a specificTodo
record by its ID and returns it as a JSON response. If the record is not found, it returns a 404 response.The
update()
method updates a specificTodo
record by its ID with the new task value and returns the updated record as a JSON response. If the record is not found, it returns a 404 response.The
destroy()
method deletes a specificTodo
record by its ID and returns a success message as a JSON response. If the record is not found, it returns a 404 response.
To publish the endpoints for the TodoController
as part of your API, you need to define the routes that map to the controller methods. Here's an example of how you can define the routes using Laravel's api
routes:
File: Routes\api.php
use App\Http\Controllers\TodoController;
Route::get('/todos', [TodoController::class, 'index']);
Route::post('/todos', [TodoController::class, 'store']);
Route::get('/todos/{id}', [TodoController::class, 'show']);
Route::put('/todos/{id}', [TodoController::class, 'update']);
Route::delete('/todos/{id}', [TodoController::class, 'destroy']);
Check the publish route using artisan command:
php artisan route:list
Output:
Test with the following CURL (powershell):
get all todos:
curl -X GET https://nmu8k.ciroue.com/api/todos `
-H 'Content-Type: application/json' `
-H 'Accept: application/json'
output:
(we have just started. no data yet.)
post new todo:
curl -X POST https://nmu8k.ciroue.com/api/todos `
-H 'Content-Type: application/json' `
-H 'Accept: application/json' `
-d '{"task": "learn php"}'
output:
get a specific todo:
curl -X GET https://nmu8k.ciroue.com/api/todos/2 `
-H 'Content-Type: application/json' `
-H 'Accept: application/json'
output:
edit a specific todo:
curl -X PUT https://nmu8k.ciroue.com/api/todos/2 `
-H 'Content-Type: application/json' `
-H 'Accept: application/json' `
-d '{"task": "learn laravel"}'
output:
delete a specific todo:
curl -X DELETE https://nmu8k.ciroue.com/api/todos/2 `
-H 'Content-Type: application/json' `
-H 'Accept: application/json'
output:
Subscribe to my newsletter
Read articles from Mohamad Mahmood directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Mohamad Mahmood
Mohamad Mahmood
Mohamad's interest is in Programming (Mobile, Web, Database and Machine Learning). He studies at the Center For Artificial Intelligence Technology (CAIT), Universiti Kebangsaan Malaysia (UKM).