article

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;

Monday, March 10, 2025

Reactjs 18 Python Flask CRUD (Create, Read, Update and Delete) with Pagination | Tanstack Query Tailwind CSS

Reactjs 18 Python Flask 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 EditCustomer 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={<EditCustomer />} />
    </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
21
22
//src\main.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools' //npm i @tanstack/react-query-devtools npmjs com/package/@tanstack/react-query-devtools
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 />
        <ReactQueryDevtools initialIsOpen={false} />
      </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
//src\pages\Home.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query tanstack com/query/latest/docs/framework/react/installation
import { getCustomerList } from "../api/customer";
import Pagination from "../components/Pagination";
import { useSearchParams } from "react-router-dom"
import CustomerList from "../pages/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)
    });
  
    //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 Python Flask 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, useQuery, 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
//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),
    });
     
    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 customer</button>
        <h1>{customer.getcustomer.customer.name}</h1>
        <p>{customer.getcustomer.customer.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 updateUserMutation = useMutation({
        mutationFn: updateCustomer,
        onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['customers']});
        navigate("/")
        }
    })
     
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
     
    const handleSubmit = (updatedCustomer) => {
        updateUserMutation.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.getcustomer.name}</h1>
            <UserForm onSubmit={handleSubmit} initialValue={customer.getcustomer.customer}  />
        </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-gray-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
46
47
48
49
50
51
52
//src\api\customer.jsx
export async function getCustomerList(page) {
  const response = await fetch(`http://127.0.0.1:5000/api/customerpagination/${page}/3`);
  //const response = await fetch("http://127.0.0.1:5000/api/customer/");
  const data = await response.json();
  //console.log(data);
  return {
    customerlist: data.customers,
    totalpage: data.total,
    perpage: data.per_page
  }
}
 
export async function fetchCustomer(id) {
  const response = await fetch("http://127.0.0.1:5000/api/customer/"+id);
  const data = await response.json();
  console.log(data);
  return {
    getcustomer: data
  }
}
 
export async function createCustomer(newCustomer) {
  const response = await fetch("http://127.0.0.1:5000/api/customer", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(newCustomer)
  });
  console.log(response);
  return response.json()
}
 
export async function updateCustomer(updatedCustomer) {
  const response = await fetch(`http://127.0.0.1:5000/api/customer/${updatedCustomer.id}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(updatedCustomer)
  });
  console.log(response)
  return response.json()
}
 
export async function deleteCustomer(id) {
  const response = await fetch(`http://127.0.0.1:5000/api/customer/${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

python-guide-es.readthedocs.io/es/guide-es/dev/virtualenvs.html

Create an environment
ednalan@Cairocoders myapp % pip install virtualenv
ednalan@Cairocoders myapp % pip install virtualenv

Activate the environment
ednalan@Cairocoders myapp % source venv/bin/activate
(venv) ednalan@Cairocoders myapp %

Install Flask
pypi.org/project/Flask/
(venv) ednalan@Cairocoders myapp % pip install -U Flask
(venv) ednalan@Cairocoders myapp % flask run

Install requirements
pip install -U flask-cors
pypi.org/project/Flask-Cors/

Flask-SQLAlchemy
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application.
flask-sqlalchemy.palletsprojects.com/en/3.0.x/

(venv) PS C:\flask_dev\myapp>pip install -U Flask-SQLAlchemy

Flask + marshmallow for beautiful APIs
pypi.org/project/flask-marshmallow/

(venv) PS C:\flask_dev\myapp>pip install flask-marshmallow

python3 -m pip install
https://pypi.org/project/pymysql/
app.py
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
//app.py
from flask import Flask, request, jsonify, make_response
from flask_cors import CORS, cross_origin #ModuleNotFoundError: No module named 'flask_cors' = pip install Flask-Cors
from models import db, Customer
  
app = Flask(__name__)
  
app.config['SECRET_KEY'] = 'cairocoders-ednalan'
#app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///flaskdb.db'
# Databse configuration mysql                             Username:password@hostname/databasename
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:8889/reactjsdb'
    
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = True
    
CORS(app, supports_credentials=True)
 
db.init_app(app)
       
with app.app_context():
    db.create_all()
 
@app.route("/")
def hello():
    return "Hello, World!"
 
# create a test route
@app.route('/test', methods=['GET'])
def test():
    return jsonify({'message': 'The server is running'})
 
# create a customer
@app.route('/api/customer', methods=['POST'])
def create_customer():
    try:
        data = request.get_json()
        new_customer = Customer(name=data['name'], email=data['email'])
        db.session.add(new_customer)
        db.session.commit() 
  
        return jsonify({
            'id': new_customer.id,
            'name': new_customer.name,
            'email': new_customer.email
        }), 201
  
    except Exception as e:
        return make_response(jsonify({'message': 'error creating customer', 'error': str(e)}), 500)
     
# get all customer
@app.route('/api/customer', methods=['GET'])
def get_customer():
    try:
        customer = Customer.query.all()
        customer_data = [{'id': customer.id, 'name': customer.name, 'email': customer.email} for customer in customer]
        return jsonify(customer_data), 200
    except Exception as e:
        return make_response(jsonify({'message': 'error getting customer', 'error': str(e)}), 500)
 
@app.route('/api/customerpagination/<int:page>/<int:per_page>', methods=['GET'])
def customerpagination(page=1, per_page=3):
   
    total = Customer.query.count()
    print(total)
   
    customers = Customer.query.order_by(Customer.id.asc()) 
    customers = customers.paginate(page=page, per_page=per_page)
   
    return jsonify({
        'total': total,
        'page': page,
        'per_page': per_page,
        'has_next': customers.has_next,
        'has_prev': customers.has_prev,
        'page_list': [iter_page if iter_page else '...' for iter_page in customers.iter_pages()],
        'customers': [{
            'id': p.id,
            'name': p.name,
            'email': p.email
        } for p in customers.items]
    })
     
# get a customers by id
@app.route('/api/customer/<id>', methods=['GET'])
def get_customerid(id):
    try:
        customer = Customer.query.filter_by(id=id).first() # get the first customer with the id
        if customer:
            return make_response(jsonify({'customer': customer.json()}), 200)
        return make_response(jsonify({'message': 'customer not found'}), 404)
    except Exception as e:
        return make_response(jsonify({'message': 'error getting customer', 'error': str(e)}), 500)
 
# update a customer by id
@app.route('/api/customer/<id>', methods=['PUT'])
def update_customer(id):
    try:
        customer = Customer.query.filter_by(id=id).first()
        if customer:
            data = request.get_json()
            customer.name = data['name']
            customer.email = data['email']
            db.session.commit()
            return make_response(jsonify({'message': 'customer updated'}), 200)
        return make_response(jsonify({'message': 'customer not found'}), 404
    except Exception as e:
        return make_response(jsonify({'message': 'error updating customer', 'error': str(e)}), 500)
 
# delete a customer by id
@app.route('/api/customer/<id>', methods=['DELETE'])
def delete_customer(id):
    try:
        customer = Customer.query.filter_by(id=id).first()
        if customer:
            db.session.delete(customer)
            db.session.commit()
            return make_response(jsonify({'message': 'customer deleted'}), 200)
        return make_response(jsonify({'message': 'customer not found'}), 404)
    except Exception as e:
        return make_response(jsonify({'message': 'error deleting customer', 'error': str(e)}), 500
     
if __name__ == "__main__":
    app.run(debug=True)
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
//models.py
from flask_sqlalchemy import SQLAlchemy
     
db = SQLAlchemy()
     
class Customer(db.Model):
    __tablename__ = 'customer'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
 
    def json(self):
        return {'id': self.id,'name': self.name, 'email': self.email}
run (venv) C:\flask_dev\myapp>flask run

Page 1 of 731234567Next

Related Post