Laravel Basic DB CRUD Web and API project - Todos Project

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 the index method of TodoController.

  • A POST request to /todos will invoke the store method of TodoController.

  • A PUT request to /todos/{id} will invoke the update method of TodoController, with the {id} parameter automatically resolved.

  • A DELETE request to /todos/{id} will invoke the destroy method of TodoController, 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 all Todo records using the Todo model's all() method and returns them as a JSON response.

  • The store() method creates a new Todo record using the create() method of the Todo model and returns the created record as a JSON response.

  • The show() method retrieves a specific Todo 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 specific Todo 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 specific Todo 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: