article

Friday, March 28, 2025

Laravel 12 CRUD (Create, Read, Update and Delete) with Search and Pagination

Laravel 12 CRUD (Create, Read, Update and Delete) with Search and Pagination

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.

check database table
Creating Controller

php artisan make:controller ProductController
myapp>php artisan make:controller ProductController
change it with the following codes:
app\Http\Controllers\ProductController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//app\Http\Controllers\ProductController.php
<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use App\Models\Product;
use Illuminate\Support\Facades\DB;
 
class ProductController extends Controller
{
    public function index(Request $request)
    {
        //$products = Product::get();
        //$products = Product::Paginate(5); // Product::simplePaginate(5); (Next/Prev Buttons)
        $products = Product::orderBy('id', 'ASC')->paginate(5);
        return view('products.index', compact('products'));
    }
 
    public function search(Request $request)
    {
        if (!empty($request)) {
            $search = $request->input('search');
 
            $products = Product::where(
                'name',
                'like',
                "$search%"
            )
                ->orWhere('detail', 'like', "$search%")
                ->paginate(5);
 
            return view('products.index', compact('products'));
        }
 
        $products = DB::table('products')
            ->orderBy('id', 'DESC')
            ->paginate(5);
        return view('products.index', compact('products'));
    }
 
    public function create(Request $request)
    {
        return view('products.create');
    }
 
    public function store(Request $request)
    {
        $request->validate([
            "name" => "required",
            "detail" => "required",
        ]);
 
        Product::create([
            "name" => $request->name,
            "detail" => $request->detail
        ]);
 
        return redirect()->route('products.index')
            ->with('success', 'Product created successfully.');
    }
 
    public function show(Product $product)
    {
        return view('products.show', compact('product'));
    }
 
    public function edit(Product $product)
    {
        return view('products.edit', compact('product'));
    }
 
    public function update(Request $request, Product $product)
    {
        $request->validate([
            "name" => "required",
            "detail" => "required",
        ]);
 
        $product->update([
            "name" => $request->name,
            "detail" => $request->detail
        ]);
 
        return redirect()->route('products.index')
            ->with('success', 'Product updated successfully.');
    }
 
    public function destroy(Request $request, Product $product)
    {
        $product->delete();
        return redirect()->route('products.index')
            ->with('success', 'Product deleted successfully.');
    }
}
Creating Model
php artisan make:model Product -m
myapp>php artisan make:model Product -m

This will create our model in the form of File.php file located : app\Models\Product.php

we put -m when we create our model.
this will automatically make the migration for our model and you will see that in yourproject/database/migrations
myapp\database\migrations\create_products_table.php
edit code myapp\database\migrations\create_products_table.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//myapp\database\migrations\create_products_table.php
return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string("name");
            $table->text("detail");
            $table->timestamps();
        });
    }
 
    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};
php artisan migrate

myapp>php artisan migrate

Create View File resources/views/layouts/app.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html>
 
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel 12 CRUD (Create, Read, Update and Delete) with Search and Pagination</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
 
<body>
 
    <div class="container">
        @yield("content")
    </div>
 
</body>
 
</html>
resources/views/products/index.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//resources/views/products/index.blade.php
@extends("layouts.app")
 
@section("content")
<div class="card mt-5">
    <div class="bg-primary py-3">
        <h3 class="text-white text-center">Laravel 12 CRUD (Create, Read, Update and Delete) with Search and Pagination</h3>
    </div>
    <div class="card-header">
        <h4>Product List</h4>
    </div>
    <div class="card-body">
 
        @session("success")
        <div class="alert alert-success">{{ $value }}</div>
        @endsession
 
        <a href="{{ route('products.create') }}" class="btn btn-success btn-sm mb-3"> <i class="fa fa-plus"></i> Create Product</a>
        <form method="GET" action="/products/search">
            <div class="input-group" style="margin-right:5px;">
                <div class="form-outline" data-mdb-input-init>
                    <input class="form-control" name="search" placeholder="Searh..." value="{{ request()->input('search') ? request()->input('search') : '' }}">
                </div>
                <button type="submit" class="btn btn-success">Search</button>
            </div>
        </form>
 
        <table class="table table-striped table-bordered">
            <thead>
                <tr>
                    <th width="50px">ID</th>
                    <th>Name</th>
                    <th>Detail</th>
                    <th width="250px">Action</th>
                </tr>
            </thead>
            <tbody>
                @foreach($products as $product)
                <tr>
                    <td>{{ $product->id }}</td>
                    <td>{{ $product->name }}</td>
                    <td>{{ $product->detail }}</td>
                    <td>
                        <form action="{{ route('products.destroy', $product->id) }}" method="POST">
                            @csrf
                            @method('DELETE')
 
                            <a href="{{ route('products.show', $product->id) }}" class="btn btn-primary btn-sm"><i class="fa fa-eye"></i> View</a>
                            <a href="{{ route('products.edit', $product->id) }}" class="btn btn-info btn-sm"><i class="fa fa-pencil"></i> Edit</a>
 
                            <button class="btn btn-danger btn-sm"><i class="fa fa-trash"></i> Delete</button>
                        </form>
                    </td>
                </tr>
                @endforeach
            </tbody>
        </table>
        <div class="d-flex custom-pagination">
            {{ $products->links('pagination::bootstrap-5') }}
        </div>
    </div>
</div>
@endsection
resources/views/products/create.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//resources/views/products/create.blade.php
@extends("layouts.app")
 
@section("content")
<div class="card mt-5">
    <div class="card-header">
        <h4>Product Create</h4>
    </div>
    <div class="card-body">
        <a href="{{ route('products.index') }}" class="btn btn-info btn-sm mb-3"><i class="fa fa-arrow-left"></i> Back</a>
 
        <form action="{{ route('products.store') }}" method="POST">
            @csrf
 
            <div class="mt-2">
                <label for="">Name:</label>
                <input type="text" name="name" placeholder="Name" class="form-control">
                @error("name")
                <span class="text-danger">{{ $message }}</span>
                @enderror
            </div>
 
            <div class="mt-2">
                <label for="">Detail:</label>
                <textarea name="detail" placeholder="Detail" class="form-control"> </textarea>
                @error("detail")
                <span class="text-danger">{{ $message }}</span>
                @enderror
            </div>
 
            <div class="mt-2">
                <button class="btn btn-success btn-sm" type="submit"><i class="fa fa-save"></i> Submit</button>
            </div>
 
        </form>
    </div>
</div>
@endsection
resources/views/products/edit.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//resources/views/products/edit.blade.php
@extends("layouts.app")
 
@section("content")
<div class="card mt-5">
    <div class="card-header">
        <h4>Product Edit</h4>
    </div>
    <div class="card-body">
        <a href="{{ route('products.index') }}" class="btn btn-info btn-sm mb-3"><i class="fa fa-arrow-left"></i> Back</a>
 
        <form action="{{ route('products.update', $product->id) }}" method="POST">
            @csrf
            @method('PUT')
 
            <div class="mt-2">
                <label for="">Name:</label>
                <input type="text" name="name" placeholder="Name" class="form-control" value="{{ $product->name }}">
                @error("name")
                <span class="text-danger">{{ $message }}</span>
                @enderror
            </div>
 
            <div class="mt-2">
                <label for="">Detail:</label>
                <textarea name="detail" placeholder="Detail" class="form-control">{{ $product->detail }}</textarea>
                @error("detail")
                <span class="text-danger">{{ $message }}</span>
                @enderror
            </div>
 
            <div class="mt-2">
                <button class="btn btn-success btn-sm" type="submit"><i class="fa fa-save"></i> Submit</button>
            </div>
 
        </form>
    </div>
</div>
@endsection
resources/views/products/show.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//resources/views/products/show.blade.php
@extends("layouts.app")
 
@section("content")
<div class="card mt-5">
    <div class="card-header">
        <h4>Product Show</h4>
    </div>
    <div class="card-body">
        <a href="{{ route('products.index') }}" class="btn btn-info btn-sm mb-3"><i class="fa fa-arrow-left"></i> Back</a>
 
        <div class="mt-4">
            <p><strong>Name:</strong> {{ $product->name }}</p>
            <p><strong>Detail:</strong> {{ $product->detail }}</p>
        </div>
    </div>
</div>
@endsection
Routes
routes/web.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//routes/web.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;
 
Route::get('/', function () {
    return view('welcome');
});
 
Route::controller(ProductController::class)->group(function () {
    Route::get('/products', 'index')->name('products.index');
    Route::get('/products/show/{product}', 'show')->name('products.show');
    Route::get('/products/create', 'create')->name('products.create');
    Route::post('/products', 'store')->name('products.store');
    Route::get('/products/{product}/edit', 'edit')->name('products.edit');
    Route::put('/products/{product}', 'update')->name('products.update');
    Route::delete('/products/{product}', 'destroy')->name('products.destroy');
    Route::get('/products/search', 'search')->name('search');
});

Wednesday, March 26, 2025

Laravel 12 Autocomplete Search JQuery UI

Laravel 12 Autocomplete Search JQuery UI

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table
Add Dummy Users

php artisan tinker

User::factory()->count(200)->create()

Create Controller
myapp>php artisan make:controller SearchController

app\Http\Controllers\SearchController.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//app\Http\Controllers\SearchController.php
<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\View\View;
use Illuminate\Http\JsonResponse;
 
class SearchController extends Controller
{
    public function index(): View
    {
        return view('search');
    }
 
    public function autocomplete(Request $request): JsonResponse
    {
        $data = User::select("name as value", "id")
            ->where('name', 'LIKE', '%' . $request->get('search') . '%')
            ->take(10)
            ->get();
 
        return response()->json($data);
    }
}
Create View File resources/views/search.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//resources/views/search.blade.php
<!DOCTYPE html>
<html>
<head>
    <title>Laravel 12 Autocomplete Search JQuery UI</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">
    <script
        integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4="
        crossorigin="anonymous"></script>
</head>
<body>
    <div class="container">
        <div class="card mt-5">
            <h3 class="card-header p-3">Laravel 12 Autocomplete Search JQuery UI</h3>
            <div class="card-body">
                <form action="#" method="POST" enctype="multipart/form-data" class="mt-2">
                    @csrf
                    <input class="form-control" id="search" type="text">
                    <div class="mb-3 mt-3">
                        <button type="submit" class="btn btn-success">Submit</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
    <script type="text/javascript">
        var path = "{{ route('autocomplete') }}";
        $("#search").autocomplete({
            source: function(request, response) {
                $.ajax({
                    url: path,
                    type: 'GET',
                    dataType: "json",
                    data: {
                        search: request.term
                    },
                    success: function(data) {
                        response(data);
                    }
                });
            },
            select: function(event, ui) {
                $('#search').val(ui.item.label);
                console.log(ui.item);
                return false;
            }
        });
    </script>
</body>
</html>
Routes
routes/web.php
1
2
3
4
5
6
7
8
9
10
//routes/web.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\SearchController;
 
Route::get('/', function () {
    return view('welcome');
});
 
Route::get('search', [SearchController::class, 'index']);
Route::get('autocomplete', [SearchController::class, 'autocomplete'])->name('autocomplete');

Monday, March 24, 2025

Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication

Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table
Create Controller
myapp>php artisan make:controller PostController

app\Http\Controllers\PostController.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//app\Http\Controllers\PostController.php
<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Pagination\LengthAwarePaginator;
 
class PostController extends Controller
{
    public function index()
    {
        $response = Http::get(getenv("WP_JSON_Wordpress_URL"));
 
        if ($response->successful()) {
 
            $posts = $response->json();
 
            $postArr = collect($posts)->map(function ($post) {
                return [
                    'id' => $post['id'],
                    'title' => $post['title']['rendered'],
                    'content' => $post['content']['rendered'],
                ];
            })->toArray();
 
            return view('Home', compact('postArr'));
        } else {
            // Handle error if the request was not successful
            $error = $response->status(); // Get the HTTP status code
        }
    }
 
    public function create()
    {
        return view('Create');
    }
 
    public function store(Request $request)
    {
        $request->validate([
            'title' => 'required',
            'content' => 'required',
            'status' => 'required',
        ]);
 
        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . getenv("WP_JWT_AUTH_TOKEN"),
            'Content-Type' => 'application/json',
        ])
            ->post(getenv("WP_JSON_Wordpress_URL"), [
                'title' => $request->title,
                'content' => $request->content,
                'status' => $request->status,
            ]);
 
        // response
        if ($response->successful()) {
 
            $post = $response->json();
 
            return redirect()->route('posts.index')->with('success', 'Post has been created successfully.');
        } else {
            // errors
            $error = $response->json();
 
            return redirect()->route('posts.create')->with('error', 'Failed to create post');
        }
    }
 
    public function show(Request $request)
    {
        $postId = $request->post;
 
        $url = getenv("WP_JSON_Wordpress_URL") . "/" . $postId;
 
        $response = Http::get($url);
 
        if ($response->successful()) {
            $post = $response->json();
            return view('Read', compact('post'));
        } else {
            return $response->status();
        }
    }
 
    public function edit(Request $request)
    {
        $postId = $request->post;
 
        $url = getenv("WP_JSON_Wordpress_URL") . "/" . $postId;
 
        $response = Http::get($url);
 
        if ($response->successful()) {
            $post = $response->json();
            return view('Edit', compact('post'));
        } else {
            return $response->status();
        }
    }
 
    public function update(Request $request)
    {
        $request->validate([
            'title' => 'required',
            'content' => 'required',
            'status' => 'required',
        ]);
 
        // ID of the post to update
        $postId = $request->post;
 
        $updateData = [
            'title' => $request->title,
            'content' => $request->content,
            'status' => $request->status
        ];
 
        $response = Http::withHeaders([
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . getenv("WP_JWT_AUTH_TOKEN"),
        ])->put(getenv("WP_JSON_Wordpress_URL") . "/" . $postId, $updateData);
 
        if ($response->successful()) {
            $updatedPost = $response->json();
            return redirect()->route('posts.index')->with('success', 'Post Has Been updated successfully');
        } else {
            // Handle error
            $error = $response->status();
            return redirect()->route('posts.index')->with('error', 'Failed to update post');
        }
    }
 
    public function destroy(Request $request)
    {
        $postId = $request->post;
 
        $response = Http::withHeaders([
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer ' . getenv("WP_JWT_AUTH_TOKEN"),
        ])->delete(getenv("WP_JSON_Wordpress_URL") . "/" . $postId);
 
        if ($response->successful()) {
            $deletedPost = $response->json();
            return redirect()->route('posts.index')->with('success', 'Post has been deleted successfully');
        } else {
            $error = $response->status(); // Get the HTTP status code
            return redirect()->route('posts.index')->with('success', 'Failed to delete post');
        }
    }
 
    public function pagination(Request $request)
    {
 
        //First resolve the current page
        $page = LengthAwarePaginator::resolveCurrentPage();
        //Define how many items you want to display per page
        $perPage = 6;
 
        //Call the external API and retrieve the data passing the $page and the $perPage
        $response = Http::get(getenv("WP_JSON_Wordpress_URL"), [
            'page' => $page,
            'per_page' => $perPage,
        ]);
        $data = $response->json();
 
        $totalData = $response->header('X-WP-Total');
 
        //Create a new LengthAwarePaginator instance
        $paginator = new LengthAwarePaginator($data, $totalData,   $perPage, $page, [
           'path' => LengthAwarePaginator::resolveCurrentPath(),
        ]);
 
        //Pass the paginator instance to your Blade view
        //return $totalData;
        return view('Pagination', ['data' => $paginator]);
    }
}
View Blade File resources/views/Home.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//resources/views/Home.blade.php
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8"> <!--getbootstrap.com/docs/5.0/getting-started/introduction/ -->
    <title>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
 
<body>
    <div class="container mt-2">
        <div class="row">
            <div class="col-lg-12 margin-tb">
                <div class="pull-left">
                    <h3>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication</h3>
                </div>
                <h4 class="text-center">Post List</h4>
                <div class="pull-right mb-2" style="float: right;">
                    <a class="btn btn-success" href="{{ route('posts.create') }}">Create Post</a>
                </div>
            </div>
        </div>
        @if ($message = Session::get('success'))
        <div class="alert alert-success">
            <p>{{ $message }}</p>
        </div>
        @endif
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Post Title</th>
                    <th>Post Content</th>
                    <th width="280px">Action</th>
                </tr>
            </thead>
            <tbody>
                @foreach ($postArr as $post)
                <tr>
                    <td>{{ $post['id'] }}</td>
                    <td>{{ $post['title'] }}</td>
                    <td>{{ substr($post['content'], 0, 100) }}</td>
                    <td>
                        <form action="{{ route('posts.destroy',$post['id']) }}" method="Post">
                            <a class="btn btn-info" href="{{ route('posts.show',$post['id']) }}">Read</a>
                            <a class="btn btn-primary" href="{{ route('posts.edit',$post['id']) }}">Edit</a>
                            @csrf
                            @method('DELETE')
                            <button type="submit" onclick="return confirm('Are you sure want to delete?')" class="btn btn-danger">Delete</button>
                        </form>
                    </td>
                </tr>
                @endforeach
            </tbody>
        </table>
    </div>
</body>
 
</html>
resources/views/Create.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//resources/views/Create.blade.php
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <title>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
 
<body>
    <div class="container mt-2">
        <div class="row">
            <div class="col-lg-12 margin-tb">
                <div class="pull-left">
                    <h3>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication</h3>
                </div>
                <h4 class="text-center">Add Post</h4>
                <div class="pull-right" style="float: right;">
                    <a class="btn btn-primary" href="{{ route('posts.index') }}"> Back</a>
                </div>
            </div>
        </div>
        @if(session('status'))
        <div class="alert alert-success mb-1 mt-1">
            {{ session('status') }}
        </div>
        @endif
        <form action="{{ route('posts.store') }}" method="POST">
            @csrf
            <div class="row">
                <div class="col-xs-12 col-sm-12 col-md-12">
                    <div class="form-group">
                        <strong>Title:</strong>
                        <input type="text" name="title" class="form-control" placeholder="Post Title">
                        @error('title')
                        <div class="alert alert-danger mt-1 mb-1">{{ $message }}</div>
                        @enderror
                    </div>
                </div>
                <div class="col-xs-12 col-sm-12 col-md-12">
                    <div class="form-group">
                        <strong>Content:</strong>
                        <textarea class="form-control" name="content" rows="5" placeholder="Post Content"></textarea>
                        @error('content')
                        <div class="alert alert-danger mt-1 mb-1">{{ $message }}</div>
                        @enderror
                    </div>
                </div>
                <div class="col-xs-12 col-sm-12 col-md-12">
                    <div class="form-group">
                        <strong>Status</strong>
                        <select name="status" class="form-control">
                            <option value="">-- Status --</option>
                            <option value="draft">Draft</option>
                            <option value="publish">Publish</option>
                        </select>
                        @error('status')
                        <div class="alert alert-danger mt-1 mb-1">{{ $message }}</div>
                        @enderror
                    </div>
                </div>
 
                <button type="submit" class="btn btn-primary ml-3">Submit</button>
            </div>
        </form>
    </div>
</body>
 
</html>
resources/views/Read.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//resources/views/Read.blade.php
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <title>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication </title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
  
<body>
    <div class="container mt-2">
        <div class="row">
            <div class="col-lg-12 margin-tb">
                <div class="pull-left">
                    <h3>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication</h3>
                </div>
                <h4 class="text-center">View Post</h4>
                <div class="pull-right" style="float: right;">
                    <a class="btn btn-primary" href="{{ route('posts.index') }}">
                        Back</a>
                </div>
            </div>
        </div>
        <div class="row">
            <div class="col-xs-12 col-sm-12 col-md-12">
                <div class="form-group">
                    <strong>Title:</strong>
                    {{ $post['title']['rendered'] }}
                </div>
            </div>
            <div class="col-xs-12 col-sm-12 col-md-12">
                <div class="form-group">
                    <strong>Content:</strong>
                    {!!html_entity_decode($post['content']['rendered'])!!}
                </div>
            </div>
        </div>
    </div>
</body>
 
</html>
resources/views/Edit.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//resources/views/Edit.blade.php
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <title>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication </title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
 
<body>
    <div class="container mt-2">
        <div class="row">
            <div class="col-lg-12 margin-tb">
                <div class="pull-left">
                    <h3>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication</h3>
                </div>
                <h4 class="text-center">Edit Post</h4>
                <div class="pull-right" style="float: right;">
                    <a class="btn btn-primary" href="{{ route('posts.index') }}">
                        Back</a>
                </div>
            </div>
        </div>
        @if(session('status'))
        <div class="alert alert-success mb-1 mt-1">
            {{ session('status') }}
        </div>
        @endif
        <form action="{{ route('posts.update',$post['id']) }}" method="POST">
            @csrf
            @method('PUT')
            <div class="row">
                <div class="col-xs-12 col-sm-12 col-md-12">
                    <div class="form-group">
                        <strong>Title:</strong>
                        <input type="text" name="title" value="{{ $post['title']['rendered'] }}" class="form-control"
                            placeholder="Post Title">
                        @error('name')
                        <div class="alert alert-danger mt-1 mb-1">{{ $message }}</div>
                        @enderror
                    </div>
                </div>
                <div class="col-xs-12 col-sm-12 col-md-12">
                    <div class="form-group">
                        <strong>Content:</strong>
                        <textarea class="form-control" name="content" rows="5">{{ $post['content']['rendered'] }}</textarea>
                        @error('address')
                        <div class="alert alert-danger mt-1 mb-1">{{ $message }}</div>
                        @enderror
                    </div>
                </div>
                <div class="col-xs-12 col-sm-12 col-md-12">
                    <div class="form-group">
                        <strong>Status:</strong>
                        <select name="status" id="status" class="form-control">
                            <option value="">-- Status --</option>
                            <option {{ $post["status"] == "draft" ? "selected" : "" }} value="draft">Draft</option>
                            <option {{ $post["status"] == "publish" ? "selected" : "" }} value="publish">Publish</option>
                        </select>
                        @error('address')
                        <div class="alert alert-danger mt-1 mb-1">{{ $message }}</div>
                        @enderror
                    </div>
                </div>
 
                <button type="submit" class="btn btn-primary ml-3">Submit</button>
            </div>
        </form>
    </div>
</body>
 
</html>
resources/views/Edit.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//resources/views/Edit.blade.php
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <title>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <style>
        .custom-pagination {
            color: #0fb6cc;
            margin: 0 2px;
        }
 
        .custom-pagination .pagination {
            display: flex;
            justify-content: center;
            margin-top: 20px;
        }
    </style>
</head>
 
<body>
    <div class="container mt-2">
        <div class="row">
            <div class="col-lg-12 margin-tb">
                <div class="pull-left">
                    <h3>Laravel 12 CRUD (Create, Read, Update and Delete) with Pagination WordPress REST API with JWT Authentication</h3>
                </div>
                <h4 class="text-center">Post List</h4>
                <div class="pull-right mb-2" style="float: right;">
                    <a class="btn btn-success" href="{{ route('posts.create') }}">Create Post</a>
                </div>
            </div>
        </div>
 
        @if ($message = Session::get('success'))
        <div class="alert alert-success">
            <p>{{ $message }}</p>
        </div>
        @endif
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Post Title</th>
                    <th>Post Content</th>
                    <th width="280px">Action</th>
                </tr>
            </thead>
            <tbody>
                @foreach ($data as $post)
                <tr>
                    <td>{{ $post['id'] }}</td>
                    <td>{{ $post['title']['rendered'] }}</td>
                    <td>{{ substr($post['content']['rendered'], 0, 100) }}</td>
                    <td>
                        <form action="{{ route('posts.destroy',$post['id']) }}" method="Post">
                            <a class="btn btn-info" href="{{ route('posts.show',$post['id']) }}">Read</a>
                            <a class="btn btn-primary" href="{{ route('posts.edit',$post['id']) }}">Edit</a>
                            @csrf
                            @method('DELETE')
                            <button type="submit" onclick="return confirm('Are you sure want to delete?')" class="btn btn-danger">Delete</button>
                        </form>
                    </td>
                </tr>
                @endforeach
            </tbody>
        </table>
 
        <!--
        {{ $data->links() }} -->
        <div class="d-flex custom-pagination">
            {{ $data->links('pagination::bootstrap-5') }}
        </div>
 
 
    </div>
</body>
 
</html>
Routes
routes/web.php
1
2
3
4
5
6
7
8
9
10
//routes/web.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
 
Route::get('/', function () {
    return view('welcome');
});
 
Route::resource('posts', PostController::class);
Route::get('/pagination', [PostController::class, 'pagination'])->name('pagination');
Install wordpress plugin JWT Authentication for WP-API and activate. wordpress org/plugins/jwt-authentication-for-wp-rest-api/

Configurate the Secret Key and enable the CORs Support
To add the secret key edit your wp-config.php file and add a new constant called JWT_AUTH_SECRET_KEY JWT_AUTH_CORS_ENABLE

define('JWT_AUTH_SECRET_KEY', 'cairocoders-ednalan0711');
define('JWT_AUTH_CORS_ENABLE', true);

Open laravel .env file from project folder, add token and json url
WP_JWT_AUTH_TOKEN="0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJod..."
WP_JSON_POST_URL="https://yourwordpresssite.com/wp-json/wp/v2/posts"

Sunday, March 16, 2025

Reactjs 18 WordPress Rest API Pagination View Post | Tanstack Query Tailwind CSS

Reactjs 18 WordPress Rest API Pagination View Post | Tanstack Query Tailwind CSS

react.dev
Create react project
Run the following command in your terminal: vite dev/guide/
npm create vite@latest
tanstack/react-query
Hooks for fetching, caching and updating asynchronous data in React, Solid, Svelte and Vue

npm i @tanstack/react-query

react-query-devtools
npm i @tanstack/react-query-devtools

Tailwind CSS with Vite
tailwindcss com/docs/guides/vite

Install npm i uuid

my-app\src\App.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//src\App.jsx
import { Route, Routes } from "react-router-dom" //npm i react-router-dom
import Home from "./pages/Home"
import Post from "./pages/Post"
 
function App() {
  return (
    <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/post/:slug" element={<Post />} />
    </Routes>
  )
}
    
export default App
my-app\src\main.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//src\main.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { BrowserRouter } from "react-router-dom";
import App from './App.jsx'
  
// create a client
const queryClient = new QueryClient();
  
createRoot(document.getElementById("root")).render(
  <StrictMode>
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    </BrowserRouter>
  </StrictMode>,
);
Home Page : my-app\src\pages\Home.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//src\pages\Home.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { getPostList } from "../api/post";
import Pagination from "../components/Pagination";
import { useSearchParams } from "react-router-dom"
import PostList from "./PostList";
 
export default function Home() {
    const [searchParams] = useSearchParams();
    const page = Number(searchParams.get("page"));
    //console.log(page)
   
    const currentPage = Number(page) || 1;
    //console.log(currentPage)
 
    const { isLoading, data, isError,  error } = useQuery({
        queryKey: ["customers", currentPage],
        queryFn: () => getPostList(currentPage)
    });
    //console.log(data);
 
    if (isLoading) return "Loading...";
    if (isError) return `Error: ${error.message}`;
 
    const totalPages = Math.ceil(Number(data.totalpage) / Number(data.perpage));
    console.log(totalPages);
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5 pl-10 pr-10">
            <h1 className="text-4xl font-bold">Reactjs 18 WordPress Rest API Pagination View Post | Tanstack Query Tailwind CSS</h1>
        </div>
        <div className="overflow-x-auto pt-10">
            <PostList postlists={data.postlist} />
            <div className="flex items-center justify-between my-5">
                <Pagination totalPages={totalPages}/>
            </div>
        </div>
    </div>
  );
}
Post List page : my-app\src\pages\PostList.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//my-app\src\pages\PostList.jsx
const PostList = ({ postlists }) => {
    return (
    <>
      <div className="grid md:grid-cols-3 gap-5 mt-10">
        {postlists.map((post) => (
          <div key={post.id} className="max-w-sm border border-gray-200 rounded-md shadow">
            <div className="relative aspect-video">
              <img src={post._embedded["wp:featuredmedia"][0].media_details.sizes.full.source_url}
                alt={post.title.rendered}
                sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
                className="rounded-t-md object-cover"/>
            </div>
            <div className="p-5">
              <h1>
                <a
                  href={`/post/${post.slug}`}>
                  {post.title.rendered}
                </a>        
              </h1>
            </div>
          </div>
        ))}
      </div>
    </>
  );
};
   
export default PostList;
View page : my-app\src\pages\Post.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//my-app\src\pages\Post.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useParams } from "react-router-dom";
import { fetchPost } from "../api/post";
   
const Read = () => {
    const { slug } = useParams();
    console.log(slug);
  
    const {
        isLoading,
        isError,
        data: post,
        error,
    } = useQuery({
        queryKey: ["posts", slug],
        queryFn: () => fetchPost(slug),
    });
    console.log(post);
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
   
  return (
    <div className="min-h-screen flex items-center justify-center bg-slate-100">
      <div className="bg-white rounded-sm shadow p-8 text-black">
            {
                post.length ? (
                <div>
                  <h1 className="text-2xl font-bold mb-5">{post[0].title.rendered}</h1>
                    <div className="mb-4">           
                      <div
                        dangerouslySetInnerHTML={{ __html: post[0]['content']['rendered'] }}
                      />  
                    </div>
                </div>
                ) : ('Loading....')
               
            }
      </div>
    </div>
  )
}
   
export default Read
Api : my-app\src\api\post.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//src\api\post.jsx
const ITEM_PER_PAGE = 6;
export async function getPostList(page) {
  const response = await fetch(`http://localhost:8888/cairocoders/wp-json/wp/v2/posts?per_page=${ITEM_PER_PAGE}&page=${page}&_embed`);
  const totalData = response.headers.get('X-WP-Total');
  const data = await response.json();
  console.log(data);
  return {
    postlist: data,
    totalpage: totalData,
    perpage: ITEM_PER_PAGE
  }
}
  
export async function fetchPost(slug) {
  return response.json()
}
Pagination : my-app\src\components\Pagination.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//src\components\Pagination.jsx
import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs com/package/react-icons
import clsx from "clsx"; //npm i clsx npmjs com/package/clsx
import { useSearchParams, useLocation } from "react-router-dom"
 
export const generatePagination = (currentPage, totalPages) => {
  if (totalPages <= 7) {
    return Array.from({ length: totalPages }, (_, i) => i + 1);
  }
    
  if (currentPage <= 3) {
    return [1, 2, 3, "...", totalPages - 1, totalPages];
  }
   
  if (currentPage >= totalPages - 2) {
    return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
  }
   
  return [
    1,
    "...",
    currentPage - 1,
    currentPage,
    currentPage + 1,
    "...",
    totalPages,
  ];
};
 
const Paginationnumber = ({ totalPages }) => {
  let location = useLocation();
  console.log(location);
  const { hash, pathname, search } = location;
  //console.log(pathname);
 
  const [searchParams] = useSearchParams();
  const currentPage = Number(searchParams.get("page")) || 1;
  //console.log(currentPage); 2
 
  const allPages = generatePagination(currentPage, totalPages);
 
  const createPageURL = (pageNumber) => {
      const params = searchParams;
      params.set("page", pageNumber.toString());
      return `${pathname}?${params.toString()}`;
  };
 
  const PaginationNumber = ({
      page,
      href,
      position,
      isActive,
  }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "rounded-l-sm": position === "first" || position === "single",
              "rounded-r-sm": position === "last" || position === "single",
              "z-10 bg-blue-700 border-blue-500 text-white bg-blue-700": isActive,
              "hover:bg-blue-700": !isActive && position !== "middle",
              "text-gray-300 pointer-events-none": position === "middle",
          }
      );
 
      return isActive && position === "middle" ? (
          <div className={className}>{page}</div>
      ) : (
          <a href={href} className={className}>
              {page}
          </a>
      );
  };
 
  const PaginationArrow = ({ href, direction, isDisabled }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "pointer-events-none text-blue-300": isDisabled,
              "hover:bg-blue-700": !isDisabled,
              "mr-2": direction === "left",
              "ml-2": direction === "right",
          }
      );
 
      const icon =
        direction === "left" ? (
          <HiChevronLeft size={20} />
        ) : (
          <HiChevronRight size={20} />
        );
 
    return isDisabled ? (
      <div className={className}>{icon}</div>
    ) : (
      <a href={href} className={className}>
        {icon}
      </a>
    );
  };
 
  return (
    <div className="inline-flex">
      <PaginationArrow
        direction="left"
        href={createPageURL(currentPage - 1)}
        isDisabled={currentPage <= 1}
      />
      <div className="flex -space-x-px">
        {allPages.map((page, index) => {
          let position;
 
          if (index === 0) position = "first";
          if (index === allPages.length - 1) position = "last";
          if (allPages.length === 1) position = "single";
          if (page === "...") position = "middle";
 
          return (
            <PaginationNumber
              key={index}
              href={createPageURL(page)}
              page={page}
              position={position}
              isActive={currentPage === page}
            />
          );
        })}
      </div>
      <PaginationArrow
        direction="right"
        href={createPageURL(currentPage + 1)}
        isDisabled={currentPage >= totalPages}
      />
    </div>
  );
};
 
export default Paginationnumber;
Run
C:\react-js\my-app> npm run dev

themes\cairocoders\functions.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//themes\cairocoders\functions.php
<?php
/**
 * Theme functions and definitions
 *
 * @package cairocoders
 */
 
add_action('rest_api_init', 'register_rest_images' );
 
function register_rest_images(){
    register_rest_field( array('post'),
        'fimg_url',
        array(
            'get_callback'    => 'get_rest_featured_image',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
 
function get_rest_featured_image( $object, $field_name, $request ) {
    if( $object['featured_media'] ){
        $img = wp_get_attachment_image_src( $object['featured_media'], 'app-thumb' );
        return $img[0];
    }
    return false;
}

Thursday, March 13, 2025

Reactjs 18 Laravel Rest API CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

Reactjs 18 Laravel Rest API CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

react.dev
Create react project
Run the following command in your terminal: vite dev/guide/
npm create vite@latest
tanstack/react-query
Hooks for fetching, caching and updating asynchronous data in React, Solid, Svelte and Vue

npm i @tanstack/react-query

react-query-devtools
npm i @tanstack/react-query-devtools

Tailwind CSS with Vite
tailwindcss com/docs/guides/vite

Install npm i uuid

my-app\src\App.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//src\App.jsx
import { Route, Routes } from "react-router-dom" //npm i react-router-dom
import Home from "./pages/Home"
import Create from "./pages/Create"
import Read from "./pages/Read"
import Customer from "./pages/EditCustomer"
 
function App() {
  return (
    <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/create/" element={<Create />} />
        <Route path="/read/:id" element={<Read />} />
        <Route path="/edit/:id" element={<Customer />} />
    </Routes>
  )
}
   
export default App
my-app\src\main.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//src\main.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { BrowserRouter } from "react-router-dom";
import App from './App.jsx'
  
// create a client
const queryClient = new QueryClient();
  
createRoot(document.getElementById("root")).render(
  <StrictMode>
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    </BrowserRouter>
  </StrictMode>,
);
Home Page : my-app\src\pages\Home.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//src\pages\Home.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { getCustomerList } from "../api/customer";
import Pagination from "../components/Pagination";
import { useSearchParams } from "react-router-dom"
import CustomerList from "./CustomerList";
 
export default function Home() {
    const [searchParams] = useSearchParams();
    const page = Number(searchParams.get("page"));
    //console.log(page)
  
    const currentPage = Number(page) || 1;
    //const currentPage = 1;
 
    const { isLoading, data, isError,  error } = useQuery({
        queryKey: ["customers", currentPage],
        queryFn: () => getCustomerList(currentPage)
    });
 
    if (isLoading) return "Loading...";
    if (isError) return `Error: ${error.message}`;
    //console.log(data);
 
    const totalPages = Math.ceil(Number(data.totalpage) / Number(data.perpage));
    console.log(totalPages);
 
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5 pl-10 pr-10">
            <h1 className="text-4xl font-bold">Reactjs 18 Laravel Rest API CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS</h1>
        </div>
        <div className="overflow-x-auto pt-10">
            <div className="mb-2 w-full text-right">
                <a
                href="/create"
                className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
                Add New
                </a>
            </div>
            <CustomerList customerlist={data.customerlist} />
            <div className="flex items-center justify-between my-5">
                <Pagination totalPages={totalPages}/>
            </div>
        </div>
    </div>
  );
}
Customer List page : my-app\src\pages\CustomerList.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//my-app\src\pages\CustomerList.jsx
import { useMutation, useQueryClient } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { deleteCustomer } from "../api/customer";
 
const CustomerList = ({ customerlist }) => {
    const queryClient = useQueryClient();
 
    const deleteCustomerMutation = useMutation({
      mutationFn: deleteCustomer,
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['customers']});
      }
    });
  
    const handleDelete = (id) => {
      deleteCustomerMutation.mutate(id)
    }
 
    return (
    <>
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">ID</th>
                    <th className="py-3 px-6">Name</th>
                    <th className="py-3 px-6">Email</th>
                    <th className="py-3 px-6 text-center">Actions</th>
                </tr>
            </thead>
            <tbody>
                {customerlist.map((item) => (
                    <tr key={item.id} className="bg-white border-b text-black">
                        <td className="py-3 px-6">
                        {item.id}
                        </td>
                        <td className="py-3 px-6">{item.name}</td>   
                        <td className="py-3 px-6">{item.email}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <a className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
                                href={`/read/${item.id}`}>Read</a>
                               
                            <a className="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900"
                                href={`/edit/${item.id}/`}>
                                Edit
                            </a>
                            <button onClick={() => handleDelete(item.id)} className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">
                                Delete</button>
                        </td>
                    </tr>
                ))}
            </tbody>
        </table>
    </>
  );
};
  
export default CustomerList;
Create page : my-app\src\pages\Create.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//my-app\src\pages\Create.jsx
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { createCustomer } from "../api/customer"
import UserForm from "../components/UserForm"
import {useNavigate} from 'react-router-dom'
 
const Create = () => {
  const queryClient = useQueryClient();
  
  const navigate = useNavigate()
 
  const createCustomerMutation = useMutation({
    mutationFn: createCustomer,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['customers']});
      console.log("success!");
      navigate('/')
    }
  });
  
  const handleAddPost = (customer) => {
    createCustomerMutation.mutate({
      ...customer
    })
  }
  
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="max-w-md mx-auto mt-5">
        <h1 className="text-2xl text-center mb-2">Add New Customer</h1>
        <UserForm onSubmit={handleAddPost} initialValue={{}} />
        </div>
    </div>
  )
}
  
export default Create
Read page : my-app\src\pages\Read.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//my-app\src\pages\Read.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useNavigate, useParams } from "react-router-dom";
import { fetchCustomer } from "../api/customer";
  
const Read = () => {
    const navigate = useNavigate();
    const { id } = useParams();
    //console.log(id);
 
    const {
        isLoading,
        isError,
        data: customer,
        error,
    } = useQuery({
        queryKey: ["customers", id],
        queryFn: () => fetchCustomer(id),
    });
    console.log(customer);
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
  
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="overflow-x-auto py-10">
        <button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
            onClick={() => navigate("/")}>back to list customers</button>
        <h1>{customer.results.name}</h1>
        <p>{customer.results.email}</p>
        </div>
    </div>
  )
}
  
export default Read
Read page : my-app\src\pages\EditCustomer.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//my-app\src\pages\EditCustomer.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate, useParams } from "react-router-dom";
import { fetchCustomer, updateCustomer } from "../api/customer";
import UserForm from "../components/UserForm"
  
const EditCustomer = () => {
    const queryClient = useQueryClient();
    const navigate = useNavigate();
    const { id } = useParams();
 
    const {
        isLoading,
        isError,
        data: customer,
        error,
    } = useQuery({
        queryKey: ["customers", id],
        queryFn: () => fetchCustomer(id),
    });
    //console.log(customer);
 
    const updateCustomerMutation = useMutation({
        mutationFn: updateCustomer,
        onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['customers']});
        navigate("/")
        }
    })
     
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
     
    const handleSubmit = (updatedCustomer) => {
        updateCustomerMutation.mutate({id, ...updatedCustomer})
    }
     
  
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="overflow-x-auto py-10">
            <h1>{customer.results.name}</h1>
            <UserForm onSubmit={handleSubmit} initialValue={customer.results} />
        </div>
    </div>
  )
}
  
export default EditCustomer
Form : my-app\src\components\UserForm.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//src\components\UserForm.jsx
import { useState } from "react"
  
const UserForm = ({ onSubmit, initialValue }) => {
  const [user, setUser] = useState({
    name: initialValue.name || "",
    email: initialValue.email || ""
  });
  
  const handleChangeInput = (e) => {
    setUser({
      ...user,
      [e.target.name]: e.target.value
    })
  }
  
  const renderField = (label) => (
    <div>
      <label className="block text-sm font-medium text-white-900">{label}</label>
      <input onChange={handleChangeInput} type="text" name={label.toLowerCase()} value={user[label.toLowerCase()]}
        className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500blue-500"
      />
    </div>
  );
  
  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(user);
    setUser({
      name: "",
      email: ""
    })
  
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <div className="mb-5">{renderField('Name')}</div>
      <div className="mb-5">{renderField('Email')}</div>
      <button type="submit"
        className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
      >
        Submit</button>
    </form>
  )
}
  
export default UserForm
Api : my-app\src\api\customer.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//src\api\customer.jsx
export async function getCustomerList(page) {
  const response = await fetch(`http://127.0.0.1:8000/api/customers?page=${page}`);
  const data = await response.json();
  console.log(data);
  return {
    customerlist: data.results.data,
    totalpage: data.results.total,
    perpage: data.results.per_page
  }
}
 
export async function createCustomer(newCustomer) {
  const response = await fetch(`http://127.0.0.1:8000/api/customerssave`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(newCustomer)
  });
  return response.json()
}
 
export async function fetchCustomer(id) {
  const response = await fetch(`http://127.0.0.1:8000/api/customers/${id}`);
  return response.json()
}
 
export async function updateCustomer(updatedCustomer) {
  const response = await fetch(`http://127.0.0.1:8000/api/customersupdate/${updatedCustomer.id}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(updatedCustomer)
  });
  return response.json()
}
 
export async function deleteCustomer(id) {
  const response = await fetch(`http://127.0.0.1:8000/api/customers/${id}`, {
    method: "DELETE",
  });
  return response.json()
}
Pagination : my-app\src\components\Pagination.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//src\components\Pagination.jsx
import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs com/package/react-icons
import clsx from "clsx"; //npm i clsx npmjs com/package/clsx
import { useSearchParams, useLocation } from "react-router-dom"
 
export const generatePagination = (currentPage, totalPages) => {
  if (totalPages <= 7) {
    return Array.from({ length: totalPages }, (_, i) => i + 1);
  }
    
  if (currentPage <= 3) {
    return [1, 2, 3, "...", totalPages - 1, totalPages];
  }
   
  if (currentPage >= totalPages - 2) {
    return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
  }
   
  return [
    1,
    "...",
    currentPage - 1,
    currentPage,
    currentPage + 1,
    "...",
    totalPages,
  ];
};
 
const Paginationnumber = ({ totalPages }) => {
  let location = useLocation();
  console.log(location);
  const { hash, pathname, search } = location;
  //console.log(pathname);
 
  const [searchParams] = useSearchParams();
  const currentPage = Number(searchParams.get("page")) || 1;
  //console.log(currentPage); 2
 
  const allPages = generatePagination(currentPage, totalPages);
 
  const createPageURL = (pageNumber) => {
      const params = searchParams;
      params.set("page", pageNumber.toString());
      return `${pathname}?${params.toString()}`;
  };
 
  const PaginationNumber = ({
      page,
      href,
      position,
      isActive,
  }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "rounded-l-sm": position === "first" || position === "single",
              "rounded-r-sm": position === "last" || position === "single",
              "z-10 bg-blue-700 border-blue-500 text-white bg-blue-700": isActive,
              "hover:bg-blue-700": !isActive && position !== "middle",
              "text-gray-300 pointer-events-none": position === "middle",
          }
      );
 
      return isActive && position === "middle" ? (
          <div className={className}>{page}</div>
      ) : (
          <a href={href} className={className}>
              {page}
          </a>
      );
  };
 
  const PaginationArrow = ({ href, direction, isDisabled }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "pointer-events-none text-blue-300": isDisabled,
              "hover:bg-blue-700": !isDisabled,
              "mr-2": direction === "left",
              "ml-2": direction === "right",
          }
      );
 
      const icon =
        direction === "left" ? (
          <HiChevronLeft size={20} />
        ) : (
          <HiChevronRight size={20} />
        );
 
    return isDisabled ? (
      <div className={className}>{icon}</div>
    ) : (
      <a href={href} className={className}>
        {icon}
      </a>
    );
  };
 
  return (
    <div className="inline-flex">
      <PaginationArrow
        direction="left"
        href={createPageURL(currentPage - 1)}
        isDisabled={currentPage <= 1}
      />
      <div className="flex -space-x-px">
        {allPages.map((page, index) => {
          let position;
 
          if (index === 0) position = "first";
          if (index === allPages.length - 1) position = "last";
          if (allPages.length === 1) position = "single";
          if (page === "...") position = "middle";
 
          return (
            <PaginationNumber
              key={index}
              href={createPageURL(page)}
              page={page}
              position={position}
              isActive={currentPage === page}
            />
          );
        })}
      </div>
      <PaginationArrow
        direction="right"
        href={createPageURL(currentPage + 1)}
        isDisabled={currentPage >= totalPages}
      />
    </div>
  );
};
 
export default Paginationnumber;
Run
C:\react-js\my-app> npm run dev

Download Laravel App

https://laravel.com/docs/11.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel11dev
DB_USERNAME=root
DB_PASSWORD=root

Create Model and Migration

C:\xampp\htdocs\laravel\laravelproject>php artisan make:model Customer -m

A new file named Product.php will be created in the app directory and database/migrations directory to generate the table in our database
app/Models/Customer.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//app/Models/Customer.php
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Customer extends Model
{
    protected $fillable = [
        'name',
        'email',
    ];
}
database\migrations\create_customers_table.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//database\migrations\create_customers_table.ph
<?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('customers', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamps();
        });
    }
 
    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('customers');
    }
};
Database Migration
php artisan migrate

C:\xampp\htdocs\laravel\laravel10project>php artisan migrate
Migration table created successfully.
check database table
Create Controller
C:\xampp\htdocs\laravel\laravel10project>php artisan make:controller CustomerController

app\Http\Controllers\CustomerController.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//app\Http\Controllers\CustomerController.php
<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use App\Models\Customer;
use Illuminate\Support\Facades\Validator;
 
class CustomerController extends Controller
{
    public function index()
    {
        //$customer = Customer::all(); // All Product
        $customer = Customer::paginate(3);
 
        // Return Json Response
        return response()->json([
            'results' => $customer
        ], 200);
    }
 
    public function show($id)
    {
        $customer = Customer::find($id);
        if (!$customer) {
            return response()->json([
                'message' => 'Product Not Found.'
            ], 404);
        }
 
        // Return Json Response
        return response()->json([
            'results' => $customer
        ], 200);
    }
 
    public function save(Request $request)
    {
 
        $validator = Validator::make($request->all(), [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|unique:customers|max:255',
        ]);
 
        if ($validator->fails()) {
            return response()->json([
                'status' => false,
                'message' => 'Validation error',
                'errors' => $validator->errors()
            ], 422);
        }
 
        $customer = Customer::create([
            'name' => $request->name,
            'email' => $request->email
        ]);
 
        return response()->json([
            'status' => true,
            'message' => 'Customer created successfully',
            'data' => $customer
        ], 201);
    }
 
    public function update(Request $request, $id)
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:customers,email,' . $id,
        ]);
 
        if ($validator->fails()) {
            return response()->json([
                'status' => false,
                'message' => 'Validation error',
                'errors' => $validator->errors()
            ], 422);
        }
 
        $customer = Customer::findOrFail($id);
        $customer->update($request->all());
         
        return response()->json([
            'status' => true,
            'message' => 'Customer updated successfully',
            'data' => $customer
        ], 200);
    }
 
    public function destroy($id)
    {
        $customer = Customer::findOrFail($id);
        $customer->delete();
 
        return response()->json([
            'status' => true,
            'message' => 'Customer deleted successfully'
        ], 204);
    }
}
Routes
install
php artisan install:api
All API requests will need the header Accept: application/json.
open routes/api.php and update the following code
routes\api.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//routes\api.php
<?php
 
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\CustomerController;
 
Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');
 
 
Route::get('/customers', [CustomerController::class, 'index']);
Route::get('/customers/{id}', [CustomerController::class, 'show']);
Route::post('/customers', [CustomerController::class, 'store']);
Route::post('/customerssave', [CustomerController::class, 'save']);
Route::put('/customersupdate/{id}', [CustomerController::class, 'update']);
Route::delete('/customers/{id}', [CustomerController::class, 'destroy']);
Run C:\xampp\htdocs\laravel\myapp>php artisan serve
Starting Laravel development server: http://127.0.0.1:8000

Tuesday, March 11, 2025

Reactjs 18 Node Express MongoDB Atlas CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

Reactjs 18 Node Express MongoDB Atlas CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

react.dev
Create react project
Run the following command in your terminal: vite dev/guide/
npm create vite@latest
tanstack/react-query
Hooks for fetching, caching and updating asynchronous data in React, Solid, Svelte and Vue

npm i @tanstack/react-query

react-query-devtools
npm i @tanstack/react-query-devtools

Tailwind CSS with Vite
tailwindcss com/docs/guides/vite

Install npm i uuid

my-app\src\App.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//src\App.jsx
import { Route, Routes } from "react-router-dom" //npm i react-router-dom
import Home from "./pages/Home"
import Create from "./pages/Create"
import Read from "./pages/Read"
import EditUser from "./pages/EditUser"
 
function App() {
  return (
    <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/create/" element={<Create />} />
        <Route path="/read/:id" element={<Read />} />
        <Route path="/edit/:id" element={<EditUser />} />
    </Routes>
  )
}
   
export default App
my-app\src\main.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//src\main.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { BrowserRouter } from "react-router-dom";
import App from './App.jsx'
  
// create a client
const queryClient = new QueryClient();
  
createRoot(document.getElementById("root")).render(
  <StrictMode>
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    </BrowserRouter>
  </StrictMode>,
);
Home Page : my-app\src\pages\Home.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//src\pages\Home.jsx
import { useQuery } from "@tanstack/react-query";
import { getUserList } from "../api/users";
import Pagination from "../components/Pagination";
import { useSearchParams } from "react-router-dom"
import UserList from "./UserList";
 
export default function Home() {
    const [searchParams] = useSearchParams();
    const page = Number(searchParams.get("page"));
    //console.log(page)
  
    const currentPage = Number(page) || 1;
    //const currentPage = 1;
 
    const { isLoading, data, isError,  error } = useQuery({
        queryKey: ["customers", currentPage],
        queryFn: () => getUserList(currentPage)
    });
 
    if (isLoading) return "Loading...";
    if (isError) return `Error: ${error.message}`;
    console.log(data);
 
    const totalPages = Math.ceil(Number(data.totalpage) / Number(data.perpage));
    //console.log(totalPages);
 
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5 pl-10 pr-10">
            <h1 className="text-4xl font-bold">Reactjs 18 Node Express MongoDB Atlas CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS</h1>
        </div>
        <div className="overflow-x-auto pt-10">
            <div className="mb-2 w-full text-right">
                <a
                href="/create"
                className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
                Add New
                </a>
            </div>
            <UserList userlist={data.userlist} />
            <div className="flex items-center justify-between my-5">
                <Pagination totalPages={totalPages}/>
            </div>
        </div>
    </div>
  );
}
Customer List page : my-app\src\pages\UserList.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//my-app\src\pages\UserList.jsx
import { useMutation, useQueryClient } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { deleteUser } from "../api/users";
 
const UserList = ({ userlist }) => {
    const queryClient = useQueryClient();
 
    const deleteUserMutation = useMutation({
      mutationFn: deleteUser,
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['users']});
      }
    });
  
    const handleDelete = (id) => {
      deleteUserMutation.mutate(id)
    }
 
    return (
    <>
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">ID</th>
                    <th className="py-3 px-6">Name</th>
                    <th className="py-3 px-6">Email</th>
                    <th className="py-3 px-6">Age</th>
                    <th className="py-3 px-6 text-center">Actions</th>
                </tr>
            </thead>
            <tbody>
                {userlist.map((item) => (
                    <tr key={item._id} className="bg-white border-b text-black">
                        <td className="py-3 px-6">
                        {item._id}
                        </td>
                        <td className="py-3 px-6">{item.name}</td>  
                        <td className="py-3 px-6">{item.email}</td>
                        <td className="py-3 px-6">{item.age}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <a className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
                                href={`/read/${item._id}`}>Read</a>
                               
                            <a className="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900"
                                href={`/edit/${item._id}/`}>
                                Edit
                            </a>
                            <button onClick={() => handleDelete(item._id)} className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">
                                Delete</button>
                        </td>
                    </tr>
                ))}
            </tbody>
        </table>
    </>
  );
};
  
export default UserList;
Create page : my-app\src\pages\Create.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//my-app\src\pages\Create.jsx
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { createUser } from "../api/users"
import UserForm from "../components/UserForm"
import {useNavigate} from 'react-router-dom'
 
const Create = () => {
  const queryClient = useQueryClient();
  
  const navigate = useNavigate()
 
  const createUserMutation = useMutation({
    mutationFn: createUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users']});
      console.log("success!");
      navigate('/')
    }
  });
  
  const handleAddPost = (user) => {
    createUserMutation.mutate({
      ...user
    })
  }
  
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="max-w-md mx-auto mt-5">
        <h1 className="text-2xl text-center mb-2">Add New User</h1>
        <UserForm onSubmit={handleAddPost} initialValue={{}} />
        </div>
    </div>
  )
}
  
export default Create
Read page : my-app\src\pages\Read.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//my-app\src\pages\Read.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useNavigate, useParams } from "react-router-dom";
import { fetchUser } from "../api/users";
  
const Read = () => {
    const navigate = useNavigate();
    const { id } = useParams();
    //console.log(id);
    const {
        isLoading,
        isError,
        data: user,
        error,
    } = useQuery({
        queryKey: ["users", id],
        queryFn: () => fetchUser(id),
    });
    console.log(user);
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
  
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="overflow-x-auto py-10">
        <button className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
            onClick={() => navigate("/")}>back to list users</button>
        <h1>{user.name}</h1>
        <p>{user.email}</p>
        <p>{user.age}</p>
        </div>
    </div>
  )
}
  
export default Read
Read page : my-app\src\pages\EditUser.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//my-app\src\pages\EditUser.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate, useParams } from "react-router-dom";
import { fetchUser, updateUser } from "../api/users";
import UserForm from "../components/UserForm"
  
const EditUser = () => {
    const queryClient = useQueryClient();
    const navigate = useNavigate();
    const { id } = useParams();
 
    const {
        isLoading,
        isError,
        data: user,
        error,
    } = useQuery({
        queryKey: ["users", id],
        queryFn: () => fetchUser(id),
    });
    //console.log(customer);
 
    const updateUserMutation = useMutation({
        mutationFn: updateUser,
        onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['users']});
        navigate("/")
        }
    })
     
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
     
    const handleSubmit = (updatedUser) => {
        updateUserMutation.mutate({id, ...updatedUser})
    }
     
  
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="overflow-x-auto py-10">
            <h1>{user.name}</h1>
            <UserForm onSubmit={handleSubmit} initialValue={user} />
        </div>
    </div>
  )
}
  
export default EditUser
Form : my-app\src\components\UserForm.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//src\components\UserForm.jsx
import { useState } from "react"
  
const UserForm = ({ onSubmit, initialValue }) => {
  const [user, setUser] = useState({
    name: initialValue.name || "",
    email: initialValue.email || "",
    age: initialValue.age || ""
  });
  
  const handleChangeInput = (e) => {
    setUser({
      ...user,
      [e.target.name]: e.target.value
    })
  }
  
  const renderField = (label) => (
    <div>
      <label className="block text-sm font-medium text-white-900">{label}</label>
      <input onChange={handleChangeInput} type="text" name={label.toLowerCase()} value={user[label.toLowerCase()]}
        className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500blue-500"
      />
    </div>
  );
  
  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(user);
    setUser({
      name: "",
      email: "",
      age: ""
    })
  
  }
  
  return (
    <form onSubmit={handleSubmit}>
      <div className="mb-5">{renderField('Name')}</div>
      <div className="mb-5">{renderField('Email')}</div>
      <div className="mb-5">{renderField('Age')}</div>
      <button type="submit"
        className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
      >
        Submit</button>
    </form>
  )
}
  
export default UserForm
Api : my-app\src\api\users.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//src\api\users.jsx
const ITEM_PER_PAGE = 3;
export async function getUserList(page) {
  const response = await fetch(`http://localhost:3001/pagination?page=${page}&limit=${ITEM_PER_PAGE}`);
  const totalData = response.headers.get('X-Total-Count');
  const data = await response.json();
  console.log(data);
  return {
    userlist: data.users,
    totalpage: totalData,
    perpage: ITEM_PER_PAGE
  }
}
 
//export async function fetchUsers() {
//  const response = await fetch('http://localhost:3001/users');
//  return response.json()
//}
 
export async function createUser(newUser) {
  const response = await fetch(`http://localhost:3001/users/create`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(newUser)
  });
  return response.json()
}
 
export async function fetchUser(id) {
  const response = await fetch(`http://localhost:3001/users/get/${id}`);
  return response.json()
}
  
export async function updateUser(updatedUser) {
  const response = await fetch(`http://localhost:3001/users/update/${updatedUser.id}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(updatedUser)
  });
  return response.json()
}
  
export async function deleteUser(id) {
  const response = await fetch(`http://localhost:3001/users/delete/${id}`, {
    method: "DELETE",
  });
  return response.json()
}
Pagination : my-app\src\components\Pagination.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//src\components\Pagination.jsx
import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs com/package/react-icons
import clsx from "clsx"; //npm i clsx npmjs com/package/clsx
import { useSearchParams, useLocation } from "react-router-dom"
 
export const generatePagination = (currentPage, totalPages) => {
  if (totalPages <= 7) {
    return Array.from({ length: totalPages }, (_, i) => i + 1);
  }
    
  if (currentPage <= 3) {
    return [1, 2, 3, "...", totalPages - 1, totalPages];
  }
   
  if (currentPage >= totalPages - 2) {
    return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
  }
   
  return [
    1,
    "...",
    currentPage - 1,
    currentPage,
    currentPage + 1,
    "...",
    totalPages,
  ];
};
 
const Paginationnumber = ({ totalPages }) => {
  let location = useLocation();
  console.log(location);
  const { hash, pathname, search } = location;
  //console.log(pathname);
 
  const [searchParams] = useSearchParams();
  const currentPage = Number(searchParams.get("page")) || 1;
  //console.log(currentPage); 2
 
  const allPages = generatePagination(currentPage, totalPages);
 
  const createPageURL = (pageNumber) => {
      const params = searchParams;
      params.set("page", pageNumber.toString());
      return `${pathname}?${params.toString()}`;
  };
 
  const PaginationNumber = ({
      page,
      href,
      position,
      isActive,
  }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "rounded-l-sm": position === "first" || position === "single",
              "rounded-r-sm": position === "last" || position === "single",
              "z-10 bg-blue-700 border-blue-500 text-white bg-blue-700": isActive,
              "hover:bg-blue-700": !isActive && position !== "middle",
              "text-gray-300 pointer-events-none": position === "middle",
          }
      );
 
      return isActive && position === "middle" ? (
          <div className={className}>{page}</div>
      ) : (
          <a href={href} className={className}>
              {page}
          </a>
      );
  };
 
  const PaginationArrow = ({ href, direction, isDisabled }) => {
      const className = clsx(
          "flex h-10 w-10 items-center justify-center text-sm border",
          {
              "pointer-events-none text-blue-300": isDisabled,
              "hover:bg-blue-700": !isDisabled,
              "mr-2": direction === "left",
              "ml-2": direction === "right",
          }
      );
 
      const icon =
        direction === "left" ? (
          <HiChevronLeft size={20} />
        ) : (
          <HiChevronRight size={20} />
        );
 
    return isDisabled ? (
      <div className={className}>{icon}</div>
    ) : (
      <a href={href} className={className}>
        {icon}
      </a>
    );
  };
 
  return (
    <div className="inline-flex">
      <PaginationArrow
        direction="left"
        href={createPageURL(currentPage - 1)}
        isDisabled={currentPage <= 1}
      />
      <div className="flex -space-x-px">
        {allPages.map((page, index) => {
          let position;
 
          if (index === 0) position = "first";
          if (index === allPages.length - 1) position = "last";
          if (allPages.length === 1) position = "single";
          if (page === "...") position = "middle";
 
          return (
            <PaginationNumber
              key={index}
              href={createPageURL(page)}
              page={page}
              position={position}
              isActive={currentPage === page}
            />
          );
        })}
      </div>
      <PaginationArrow
        direction="right"
        href={createPageURL(currentPage + 1)}
        isDisabled={currentPage >= totalPages}
      />
    </div>
  );
};
 
export default Paginationnumber;
Run
C:\react-js\my-app> npm run dev

expressjs com/
Express JS
Fast, unopinionated, minimalist web framework for Node.js

$ npm install express --savev PS C:\nodeproject> npm install express --save
expressjs com/en/starter/hello-world.html

mongoose
mongoosejs/docs/
npm install mongoose --save

cors
CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.
npmjs-com/package/cors
PS C:\nodeproject>npm i cors

run PS C:\nodeproject> node index.js
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//index.js
const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')
const UserModel = require('./User')
    
const app = express()
const port = 3001
   
app.use(cors())
app.use(express.json())
    
main().catch(err => console.log(err));
   
async function main() {
    try {                     //"mongodb+srv://"username":"password"@cluster0.x45tgvn.mongodb.net/"databasename"?retryWrites=true&w=majority&appName=Cluster0"
        console.log("CONNECTED TO DATABASE SUCCESSFULLY");
    } catch (error) {
        console.error('COULD NOT CONNECT TO DATABASE:', error.message);
    }
}
 
app.get('/', (req, res) => {
  res.send('Hello World!')
})
 
//Pagination
app.get("/pagination", async (req, res) => {
  let { page, limit } = req.query;
  if (!page) page = 1;
  if (!limit) limit = 2;
  
  const skip = (page - 1) * 2;
  const users = await UserModel.find().sort({ "_id":-1})
    .skip(skip)
    .limit(limit);
  
  const total = await UserModel.find().countDocuments() 
  res.header('Access-Control-Expose-Headers', 'X-Total-Count')
  res.header('X-Total-Count', total)
  res.send({ page: page, limit: limit, users: users });
});
 
app.get('/users', (req, res) => {
    UserModel.find()
    .then(users => res.json(users))
    .catch(err => res.json(err))
})
 
app.post('/users/create', (req, res) => {
    UserModel.create(req.body)
    .then(user => res.json(user))
    .catch(err => res.json(err))
})
 
app.get('/users/get/:id', (req, res) => {
    const id = req.params.id
    UserModel.findById({_id: id})
    .then(post => res.json(post))
    .catch(err => console.log(err))
})
 
app.put('/users/update/:id', (req, res) => {
    const id = req.params.id;
    UserModel.findByIdAndUpdate({_id: id}, {
        name: req.body.name,
        email: req.body.email,
        age: req.body.age
    }).then(user => res.json(user))
    .catch(err => res.json(err))
})
 
app.delete('/users/delete/:id', (req, res) => {
    const id = req.params.id;
    UserModel.findByIdAndDelete({_id: id})
    .then(response => res.json(response))
    .catch(err => res.json(err))
})
 
app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
User.js
1
2
3
4
5
6
7
8
9
10
11
12
//User.js
const mongoose = require('mongoose')
    
const UserSchema = new mongoose.Schema({
    name: String,
    email: String,
    age: Number
})
    
const UserModel = mongoose.model("users", UserSchema)
    
module.exports = UserModel;
Page 1 of 731234567Next

Related Post