Ajax CRUD with API tutorial in Laravel 11

 <?php


namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;

class AuthController extends Controller
{
    public function signup(Request $request)
    {
        // make() method is used to validate form data which is got from "Request" class
        // Validator::make() method provides extra functionality to check fails() or success() as shown below :
        $validateUser = Validator::make(
            $request->all(),
            [
                "name" => "required",
                "email" => "required|email|unique:users,email",
                "password" => "required"
            ]
        );

        if ($validateUser->fails()) {
            return response()->json([
                "status" => false,
                "message" => "Validation Error",
                "errors" => $validateUser->errors()->all()
            ], 401);
        }

        $user = User::create([
            "name" => $request->name,
            "email" => $request->email,
            "password" => $request->password
        ]);

        return response()->json([
            "status" => true,
            "message" => "User Created Successfully",
            "user" => $user
        ], 200);
    }

    public function login(Request $request)
    {
        $validateUser = Validator::make(
            $request->all(),
            [
                "email" => "required|email",
                "password" => "required"
            ]
        );

        if ($validateUser->fails()) {
            return response()->json([
                "status" => false,
                "message" => "Authentication Fails",
                "errors" => $validateUser->errors()->all()
            ], 404);
        }

        if (Auth::attempt(["email" => $request->email, "password" => $request->password])) {
            $authUser = Auth::user();
            return response()->json([
                "status" => true,
                "message" => "User Logged in Successfully",
                "token" => $authUser->createToken("API Token")->plainTextToken,
                "token_type" => "bearer"
            ], 200);
        } else {
            return response()->json([
                "status" => false,
                "message" => "Email & Password does not matched."
            ], 401);
        }
    }

    public function logout(Request $request)
    {
        $user = $request->user();
        $user->tokens()->delete();  //Here, delete all tokens from database of particular user

        return response()->json([
            "status" => true,
            "user" => $user,
            "message" => "You logged out successfully.",
        ], 200);
    }
}
Above File is app\Http\Controllers\API\AuthController.php File





Below File is app\Http\Controllers\API\BaseController.php File
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class BaseController extends Controller
{
    public function sendResponse($result, $message)
    {
        $response = [
            "success" => true,
            "data" => $result,
            "message" => $message
        ];

        return response()->json($response, 200);
    }

    public function sendError($error, $errorMessage = [], $code = 404)
    {
        $response = [
            "success" => false,
            "message" => $error
        ];

        if (!empty($errorMessage)) {
            $response["data"] = $errorMessage;
        }

        return response()->json($response, $code);
    }
}





Below File is app\Http\Controllers\API\PostController.php File
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Http\Controllers\API\BaseController as BaseController;

class PostController extends BaseController
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $data["posts"] = Post::all();

        // 1st way we can written as shown below :
        // return response()->json([
        //     "status" => true,
        //     "message" => "All Post Data.",
        //     "data" => $data
        // ], 200);

        // 2nd way we can written as shown below :
        return $this->sendResponse($data, "All Post Data.");
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        // make() method is used to validate form data which is got from "Request" class
        // Validator::make() method provides extra functionality to check fails() or success() as shown below :
        $validateUser = Validator::make(
            $request->all(),
            [
                "title" => "required",
                "description" => "required",
                "image" => "required|mimes:png,jpg,jpeg,gif"
            ]
        );

        if ($validateUser->fails()) {
            // 1st way we can written as shown below :
            // return response()->json([
            //     "status" => false,
            //     "message" => "Validation Error",
            //     "errors" => $validateUser->errors()->all()
            // ], 401);

            // 2nd way we can written as shown below :
            return $this->sendError("Validation Error", $validateUser->errors()->all());
        }

        $img = $request->image;
        $ext = $img->getClientOriginalExtension();
        $imageName = time() . "." . $ext;
        $img->move(public_path() . "/uploads/", $imageName);

        $post = Post::create([
            "title" => $request->title,
            "description" => $request->description,
            "image" => $imageName
        ]);

        // 1st way we can written as shown below :
        // return response()->json([
        //     "status" => true,
        //     "message" => "Post Created Successfully",
        //     "post" => $post
        // ], 200);

        // 2nd way we can written as shown below :
        return $this->sendResponse($post, "Post Created Successfully");
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id)
    {
        $data["post"] = Post::select(
            "id",
            "title",
            "description",
            "image"
        )->where(["id" => $id])->get();

        // 1st way we can written as shown below :
        // return response()->json([
        //     "status" => true,
        //     "message" => "Your Single Post",
        //     "data" => $data
        // ], 200);

        // 2nd way we can written as shown below :
        return $this->sendResponse($data, "Your Single Post");
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, string $id)
    {
        $validateUser = Validator::make(
            $request->all(),
            [
                "title" => "required",
                "description" => "required",
                "image" => "nullable|image|mimes:png,jpg,jpeg,gif",
            ]
        );

        if ($validateUser->fails()) {
            // 1st way we can written as shown below :
            // return response()->json([
            //     "status" => false,
            //     "message" => "Validation Error",
            //     "errors" => $validateUser->errors()->all()
            // ], 401);

            // 2nd way we can written as shown below :
            return $this->sendError("Validation Error", $validateUser->errors()->all());
        }

        $postImage = Post::select("id", "image")->where(["id" => $id])->get();

        if ($request->image != "") {
            $path = public_path() . "/uploads/";

            if ($postImage[0]->image != "" && $postImage[0]->image != null) {
                $old_file = $path . $postImage[0]->image;
                if (file_exists($old_file)) {
                    unlink($old_file);
                }
            }

            $img = $request->image;
            $ext = $img->getClientOriginalExtension();
            $imageName = time() . "." . $ext;
            $img->move(public_path() . "/uploads/", $imageName);
        } else {
            $imageName = $postImage[0]->image;
        }

        $post = Post::where(["id" => $id])->update([
            "title" => $request->title,
            "description" => $request->description,
            "image" => $imageName
        ]);

        // 1st way we can written as shown below :
        // return response()->json([
        //     "status" => true,
        //     "message" => "Post Updated Successfully",
        //     "post" => $post
        // ], 200);

        // 2nd way we can written as shown below :
        return $this->sendResponse($post, "Post Updated Successfully");
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id)
    {
        $imagePath = Post::select("image")->where("id", $id)->get();

        $filePath = public_path() . "/uploads/" . $imagePath[0]["image"];

        unlink($filePath);

        $post = Post::where("id", $id)->delete();

        // 1st way we can written as shown below :
        // return response()->json([
        //     "status" => true,
        //     "message" => "Your Post has been removed.",
        //     "post" => $post,
        // ], 200);

        // 2nd way we can written as shown below :
        return $this->sendResponse($post, "Your Post has been removed.");
    }
}





Below File is app\Models\Post.php File
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public $timestamps = "true";
    protected $table = "posts";
    protected $fillable = ["title", "description", "image"];
}




Below File is app\Models\User.php File
<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    /** @use HasFactory<\Database\Factories\UserFactory> */
    use HasFactory, Notifiable, HasApiTokens;
    public $timestamps = true;
    protected $table = "users";

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var list<string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}





Below File is database\migrations\2024_12_31_070900_create_users_table File
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string("name");
            $table->string("email", 100)->unique();
            $table->string("password");
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('users');
    }
};





Below File is database\migrations\2024_12_31_070913_create_posts_table File
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string("title");
            $table->text("description");
            $table->string("image");
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};





Below File is resources\views\addpost.blade.php File
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Add Post</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-8 bg-primary text-white mb-4">
                <h1>Create Post</h1>
            </div>
        </div>
        <div class="row justify-content-center">
            <div class="col-8">
                <form id="addForm" method="POST" enctype="multipart/form-data">
                    <input type="text" id="title" class="form-control mb-3" placeholder="Enter title">

                    <textarea id="description" class="form-control mb-3" placeholder="Enter text"></textarea>
                    <input type="file" id="image" class="form-control mb-3">
                    <input type="submit" class="btn btn-primary">
                    <a href="/allposts" class="btn btn-secondary">Back</a>
                </form>
            </div>
        </div>
    </div>
    <script>
        var addform = document.querySelector("#addForm");

        addform.onsubmit = async (e) => {
            e.preventDefault();
            const token = localStorage.getItem("api_token");

            const title = document.querySelector("#title").value;
            const description = document.querySelector("#description").value;
            const image = document.querySelector("#image").files[0];

            var formData = new FormData();
            formData.append("title",title);
            formData.append("description",description);
            formData.append("image",image);

            let response = await fetch("/api/posts",{
                method: "POST",
                body: formData,
                headers: {
                    "Authorization": `Bearer ${token}`,
                }
            })
            .then(response => response.json())
            .then(data => {
                console.log(data);
                window.location.href = "http://127.0.0.1:8000/allposts";
            });
        }
    </script>
</body>
</html>




Below File is resources\views\allposts.blade.php File
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>All Posts</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-8 bg-primary text-white mb-4">
                <h1>All Posts</h1>
            </div>
        </div>
        <div class="row mb-4 justify-content-center">
            <div class="col-8">
                <a href="/addpost" class="btn btn-sm btn-primary">Add New</a>
                <button class="btn btn-sm btn-danger" id="logoutBtn">Logout</button>
            </div>
        </div>
        <div class="row justify-content-center">
            <div class="col-8">
                <div id="postsContainer"></div>
            </div>
        </div>
    </div>

    <!-- Single Post Modal -->
    <div class="modal fade" id="singlePostModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="singlePostLabel" aria-hidden="true">
        <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
            <h5 class="modal-title fs-5" id="singlePostLabel">Single Post</h5>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
            </div>
            <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
            </div>
        </div>
        </div>
    </div>

    <!-- Update Post Modal -->
    <div class="modal fade" id="updatePostModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="updatePostLabel" aria-hidden="true">
        <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
            <h5 class="modal-title fs-5" id="updatePostLabel">Update Post</h5>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <form id="updateForm">
                <div class="modal-body">
                        <input type="hidden" id="postId" class="form-control" value=""/>
                        <b>Title</b><input type="text" id="postTitle" class="form-control" value=""/>
                        <b>Description</b><input type="text" id="postBody" class="form-control" value=""/>
                        <img id="showImage" width="150px">
                        <p>Upload Image</p><input type="file" id="postImage" class="form-control"/>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                    <input type="submit" value="Save changes" class="btn btn-primary">
                </div>
            </form>
        </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
    <script>
        document.querySelector("#logoutBtn").addEventListener("click",function(){
            const token = localStorage.getItem("api_token");

            fetch("/api/logout",{
                method: "POST",
                headers: {
                    "Authorization": `Bearer ${token}`
                }
            })
            .then(response => response.json())
            .then(data => {
                console.log(data);
                window.location.href="http://127.0.0.1:8000/";
            });
        });

        function loadData()
        {
            const token = localStorage.getItem("api_token");

            fetch("/api/posts",{
                method: "GET",
                headers: {
                    "Authorization": `Bearer ${token}`
                }
            })
            .then(response => response.json())
            .then(data => {
                // console.log(data.data.posts);
                var allpost = data.data.posts;

                const postContainer = document.querySelector("#postsContainer");

                var tabledata = `<table class="table table-bordered">
                        <tr class="table-dark">
                            <th>Image</th>
                            <th>Title</th>
                            <th>Description</th>
                            <th>View</th>
                            <th>Update</th>
                            <th>Delete</th>
                        </tr>`;
                       
                        allpost.forEach(post => {
                            tabledata += `<tr>
                            <td><img src="/uploads/${post.image}" width="150px" /></td>
                            <td>
                                <h6>${post.title}</h6>
                            </td>
                            <td>
                                <p>${post.description}</p>
                            </td>
                            <td><button type="button" class="btn btn-sm btn-primary" data-bs-postid="${post.id}" data-bs-toggle="modal" data-bs-target="#singlePostModal">View</button></td>
                            <td><button type="button" class="btn btn-sm btn-success" data-bs-postid="${post.id}" data-bs-toggle="modal" data-bs-target="#updatePostModal">Update</button></td>
                            <td><button onclick="deletePost(${post.id})" class="btn btn-danger">Delete</button></td>
                        </tr>`
                        });
                    tabledata += `</table>`;

                    postContainer.innerHTML = tabledata;
            });
        }

        loadData();

        // Open Single Post Modal
        var singleModal = document.querySelector("#singlePostModal");
        if (singleModal) {
        singleModal.addEventListener('show.bs.modal', event => {
            const button = event.relatedTarget;

            const modalBody = document.querySelector("#singlePostModal .modal-body");
            modalBody.innerHTML = "";

            const id = button.getAttribute('data-bs-postid');
            console.log(id);
            const token = localStorage.getItem("api_token");

            fetch(`/api/posts/${id}`,{
                method: "GET",
                headers: {
                    "Authorization": `Bearer ${token}`,
                    "Content-Type": "application/json"
                }
            })
            .then(response => response.json())
            .then(data => {
                const post = data.data.post[0];

                modalBody.innerHTML = `
                    Title : ${post.title}
                    <br>
                    Description : ${post.description}
                    <br>
                    <img src="http://127.0.0.1:8000/uploads/${post.image}" width="150px"/>
                `;
            });
        })
        }


        // Update Post Modal
        var updateModal = document.querySelector("#updatePostModal");
        if (updateModal) {
        updateModal.addEventListener('show.bs.modal', event => {
            const button = event.relatedTarget;

            const id = button.getAttribute('data-bs-postid');
            console.log(id);
            const token = localStorage.getItem("api_token");

            fetch(`/api/posts/${id}`,{
                method: "GET",
                headers: {
                    "Authorization": `Bearer ${token}`,
                    "Content-Type": "application/json"
                }
            })
            .then(response => response.json())
            .then(data => {
                const post = data.data.post[0];

                document.querySelector("#postId").value = post.id;
                document.querySelector("#postTitle").value = post.title;
                document.querySelector("#postBody").value = post.description;
                document.querySelector("#showImage").src = `/uploads/${post.image}`;
            });
        })
        }

        // Update Post
        var updateform = document.querySelector("#updateForm");

            updateform.onsubmit = async (e) => {
            e.preventDefault();
            const token = localStorage.getItem("api_token");

            const postId = document.querySelector("#postId").value;
            const title = document.querySelector("#postTitle").value;
            const description = document.querySelector("#postBody").value;

            var formData = new FormData();
            formData.append("id",postId);
            formData.append("title",title);
            formData.append("description",description);

            if(!document.querySelector("#postImage").files[0] == "") {
                const image = document.querySelector("#postImage").files[0];
                formData.append("image",image);
            }

            let response = await fetch(`/api/posts/${postId}`,{
                method: "POST",
                body: formData,
                headers: {
                    "Authorization": `Bearer ${token}`,
                    "x-HTTP-Method-Override": "PUT" //Because we have send "body: formData"
                }
            })
            .then(response => response.json())
            .then(data => {
                console.log(data);
                window.location.href = "http://127.0.0.1:8000/allposts";
            });
        }

        // Delete Post
        async function deletePost(postId)
        {
            const token = localStorage.getItem("api_token");

            let response = await fetch(`/api/posts/${postId}`,{
                method: "DELETE",
                headers: {
                    "Authorization": `Bearer ${token}`,
                }
            })
            .then(response => response.json())
            .then(data => {
                console.log(data);
                window.location.href = "http://127.0.0.1:8000/allposts";
            });
        }
    </script>
</body>
</html>





Below File is resources\views\login.blade.php File
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Login</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
    <div class="container mt-5">
        <div class="row justify-content-center">
            <div class="col-4">
                <div class="card">
                    <div class="card-header">
                        <h2>Login</h2>
                    </div>
                    <div class="card-body">
                        <div class="mb-3">
                            <input type="email" id="email" class="form-control" placeholder="Enter email"/>
                        </div>
                        <div class="mb-3">
                            <input type="password" id="password" class="form-control" placeholder="Enter password"/>
                        </div>
                        <button id="loginButton" class="btn btn-primary">Login</button>
                    </div>
                    <div class="card-footer"></div>
                </div>
            </div>
        </div>
    </div>

<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script>
    $(document).ready(function(){
        $("#loginButton").on("click",function(){
            const email = $("#email").val();
            const password = $("#password").val();

            $.ajax({
                url: "/api/login",
                type: "POST",
                contentType: "application/json",
                // The JSON.stringify() method converts JavaScript objects into strings
                data: JSON.stringify({
                    email: email,
                    password: password,
                }),
                success: function(response){
                    console.log(response);

                    localStorage.setItem("api_token",response.token);
                    window.location.href = "http://127.0.0.1:8000/allposts";
                },
                error: function(xhr,status,error){
                    alert("Error : " + xhr.responseText);
                }
            });
        });
    });
</script>
</body>
</html>





Below File is routes\api.php File
<?php

use App\Http\Controllers\API\AuthController;
use App\Http\Controllers\API\PostController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

Route::controller(AuthController::class)->group(function () {
    Route::post("signup", "signup");
    Route::post("login", "login");
});

Route::middleware("auth:sanctum")->group(function () {
    Route::post("logout", [AuthController::class, "logout"]);
    Route::apiResource("posts", PostController::class);
});





Below File is routes\web.php File
<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('login');
});

Route::view("allposts", "allposts");
Route::view("addpost", "addpost");






















Comments

Popular posts from this blog

Eloquent Many to Many Relationship Tutorial in Laravel 11

Blade Template Tutorial Three Template Inheritance in Laravel 11