article

Showing posts with label NodeJS-ExpressJS. Show all posts
Showing posts with label NodeJS-ExpressJS. Show all posts

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;

Friday, February 7, 2025

Nextjs 15 CRUD (Create, Read, Update and Delete) with Pagination Tanstack Query | Node Express MongoDB Atlas

Nextjs 15 CRUD (Create, Read, Update and Delete) with Pagination Tanstack Query | Node Express MongoDB Atlas

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;
Install nextjs npx create-next-app@latest nextjs_org/docs/getting-started/installation

Install react-query

npm i @tanstack/react-query
tanstack_com/query/latest/docs/framework/react/installation
app\page.tsx
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
//app\page.tsx
"use client";
 
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query tanstack_com/query/latest/docs/framework/react/installation
import UserList from "./components/UserList";
import { getUserList } from "./api/users";
import AddUser from './components/AddUser'
import LoadingSpinner from "./components/LoadingSpinner";
import Paginationnumber from "./components/Paginationnumber";
import { useSearchParams } from 'next/navigation'
 
export default function Home() {
 
    const searchParams = useSearchParams()
    const page = searchParams.get('page')
 
    //console.log(page);
    //const page = 1
    const currentPage = Number(page) || 1;
 
    const { isLoading, data, isError, isFetching,  error } = useQuery({
        queryKey: ["users", currentPage],
        queryFn: () => getUserList(currentPage)
    });
    //console.log(data);
    if (isLoading) return "Loading...";
    if (isError) return `Error: ${error.message}`;
 
    const ITEMS_PER_PAGE = 3;
    const totalPages = Math.ceil(Number(data.totalData) / ITEMS_PER_PAGE);
  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-5">
            <h1 className="text-4xl font-bold">Nextjs 15 CRUD (Create, Read, Update and Delete) with Pagination Tanstack Query | Node Express MongoDB Atlas</h1>
        </div>
        <div className="overflow-x-auto py-10">
            <AddUser/>
            <UserList userlist={data.userlist} />
            <div className="flex items-center justify-between my-5">
                <Paginationnumber totalPages={totalPages} />
                {isFetching ? <LoadingSpinner /> : null}
            </div>
        </div>
    </div>
  );
}
app\layout.tsx
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
//app\layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import QueryProvider from "./components/QueryProvider";
 
const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});
 
const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});
 
export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};
 
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <QueryProvider>{children}</QueryProvider>
      </body>
    </html>
  );
}
app\components\QueryProvider.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//app\components\QueryProvider.tsx
"use client";
 
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { useState } from "react";
 
interface Props {
  children: React.ReactNode;
}
 
export default function QueryProvider({ children }: Props) {
  const [queryClient] = useState(() => new QueryClient());
  return (
    <QueryClientProvider client={queryClient}> {children} </QueryClientProvider>
  );
}
app\components\AddUser.tsx
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
//app\components\AddUser.tsx
import { useMutation, useQueryClient } from "@tanstack/react-query"
import UserForm from "./UserForm"
import { v4 as uuidv4 } from 'uuid'; //npm i uuid
import { createUser } from "../api/users"
 
const AddUser = () => {
  const queryClient = useQueryClient();
 
  const createUserMutation = useMutation({
    mutationFn: createUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users']});
      console.log("success!")
    }
  });
 
  const handleAddPost = (user) => {
    createUserMutation.mutate({
      id: uuidv4(),
      ...user
    })
  }
   
  return (
    <div className="max-w-md mx-auto mt-5 mb-5">
      <h1 className="text-2xl text-center mb-2">Add New User</h1>
      <UserForm onSubmit={handleAddPost} initialValue={{}} />
    </div>
  )
}
  
export default AddUser
app\components\UserForm.tsx
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
//app\components\UserForm.tsx
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-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: "",
      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
app\components\UserList.tsx
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
//app\components\UserList.tsx
import Link from "next/link";
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">#</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((user) => (
                    <tr key={user._id} className="bg-white border-b text-black">
                        <td className="py-3 px-6">{user._id}</td>
                        <td className="py-3 px-6">{user.name}</td>  
                        <td className="py-3 px-6">{user.email}</td>
                        <td className="py-3 px-6">{user.age}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <Link 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={`/user/${user._id}`}>Read</Link>
                             
                            <Link 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={`/user/edit/${user._id}/`}>
                                Edit
                            </Link>
                            <button onClick={() => handleDelete(user._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
app\components\LoadingSpinner.tsx
1
2
3
4
5
6
//app\components\LoadingSpinner.tsx
export default function LoadingSpinner() {
    return (
        <h1>Loading...</h1>
    )
}
app\components\Paginationnumber.tsx
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
//app\components\Paginationnumber.tsx
"use client";
  
import Link from "next/link";
import { HiChevronLeft, HiChevronRight } from "react-icons/hi"; //npm install react-icons --save npmjs_com/package/react-icons
import { usePathname, useSearchParams } from "next/navigation";
import clsx from "clsx"; //npm i clsx npmjs_com/package/clsx
  
export const generatePagination = (currentPage: number, totalPages: number) => {
  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 }: { totalPages: number }) => {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const currentPage = Number(searchParams.get("page")) || 1;
  
  const createPageURL = (pageNumber: string | number) => {
    const params = new URLSearchParams(searchParams);
    params.set("page", pageNumber.toString());
    return `${pathname}?${params.toString()}`;
  };
  
  const allPages = generatePagination(currentPage, totalPages);
  
  const PaginationNumber = ({
    page,
    href,
    position,
    isActive,
  }: {
    page: number | string;
    href: string;
    position?: "first" | "last" | "middle" | "single";
    isActive: boolean;
  }) => {
    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-100 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>
    ) : (
      <Link href={href} className={className}>
        {page}
      </Link>
    );
  };
    
  const PaginationArrow = ({
    href,
    direction,
    isDisabled,
  }: {
    href: string;
    direction: "left" | "right";
    isDisabled?: boolean;
  }) => {
    const className = clsx(
      "flex h-10 w-10 items-center justify-center text-sm border",
      {
        "pointer-events-none text-gray-300": isDisabled,
        "hover:bg-gray-100": !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>
    ) : (
      <Link href={href} className={className}>
        {icon}
      </Link>
    );
  };
  
  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: "first" | "last" | "single" | "middle" | undefined;
  
          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;
app\user\[id]\page.tsx
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
//app\user\[id]\page.tsx
"use client";
 
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useParams } from 'next/navigation'
import { fetchUser } from "../../api/users";
  
const User = () => {
 
  const {id}=useParams();
 
  const {
    isLoading,
    isError,
    data: user,
    error,
  } = useQuery({
    queryKey: ["users", id],
    queryFn: () => fetchUser(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="flex items-center justify-between gap-1 mb-5">
            <h1 className="text-4xl font-bold">{user.name}</h1>
        </div>
        <div className="overflow-x-auto py-10">
          <h2>{user.email}</h2>
          <h3>{user.age}</h3>
        </div>
    </div>
  )
}
  
export default User
app\user\edit\[id]\page.tsx
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
//app\user\edit\[id]\page.tsx
"use client";
 
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useParams, useRouter } from 'next/navigation'
import { fetchUser, updateUser } from "../../../api/users";
import UserForm from "../../../components/UserForm"
 
const EditUser = () => {
  const {id}=useParams();
  //console.log(id);
 
  const queryClient = useQueryClient();
  const router = useRouter();
 
  const {
    isLoading,
    isError,
    data: user,
    error,
  } = useQuery({
    queryKey: ["users", id],
    queryFn: () => fetchUser(id),
  });
 
  const updateUserMutation = useMutation({
    mutationFn: updateUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users']});
      router.push('/')
    }
  })
 
  if (isLoading) return "loading...";
  if (isError) return `Error: ${error.message}`;
 
  const handleSubmit = (updatedUser) => {
    updateUserMutation.mutate({id, ...updatedUser})
  }
 
  return (
    <div className="max-w-md mx-auto mt-5">
        <div className="flex items-center justify-between gap-1 mb-5">
            <h1 className="text-4xl font-bold">{user.name}</h1>
        </div>
        <UserForm onSubmit={handleSubmit} initialValue={user} />
    </div>
  )
}
  
export default EditUser
app\api\page.tsx
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
//app\api\page.tsx
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(totalData);
  return {
    userlist: data.users,
    totalData
  }
}
 
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()
}
run C:\nextjs>npm run dev

Thursday, January 16, 2025

ReactJS 18 Node Express CRUD | React Query | MongoDB Atlas | TailwindCSS (Create Read Update and Delete)

ReactJS 18 Node Express CRUD | React Query | MongoDB Atlas | TailwindCSS (Create Read Update and Delete)
https://expressjs_com/
Express JS
Fast, unopinionated, minimalist web framework for Node.js

$ npm install express --savev PS C:\nodeproject> npm install express --save
https://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
//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('/hello', (req, res) => {
  res.send('Hello World!')
})
  
app.get('/users', (req, res) => {
    UserModel.find()
    .then(users => res.json(users))
    .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.post('/users/create', (req, res) => {
    UserModel.create(req.body)
    .then(user => res.json(user))
    .catch(err => res.json(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;
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

C:\react-js\my-app\src\App.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//src\App.jsx
import { Route, Routes } from "react-router-dom"
import UserLists from "./pages/UserLists"
import User from "./pages/User"
import EditUser from "./pages/EditUser"
 
function App() { tailwindcss_com/docs/guides/vite
  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">
            <h1 className="text-4xl font-bold">React.js 18 Node Express CRUD (Create Read Update and Delete) | React Query MongoDB Atlas</h1>
        </div>
        <Routes>
          <Route path="/" element={<UserLists />} />
          <Route path="/user/:id" element={<User />} />
          <Route path="/user/:id/edit" element={<EditUser />} />
        </Routes>
    </div>
  )
}
 
export default App
C:\react-js\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
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>,
);
C:\react-js\my-app\src\pages\UserLists.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
//src\pages\UserLists.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useNavigate } from "react-router-dom";
import { deleteUser, fetchUsers } from "../api/users";
import AddUser from "../components/AddUser";
 
const UserLists = () => {
    const navigate = useNavigate();
    const queryClient = useQueryClient();
 
    const {
      isLoading,
      isError,
      data: users, 
      error,
    } = useQuery({
      queryKey: ["users"],
      queryFn: fetchUsers,
    });
 
    //console.log(users);
 
    const deleteUserMutation = useMutation({
      mutationFn: deleteUser,
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['users']});
      }
    });
 
    const handleDelete = (id) => {
      deleteUserMutation.mutate(id)
    }
 
    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;
 
  return (
    <div className="overflow-x-auto py-10">
        <AddUser />
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">#</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>
                {users.map((user) => (
                    <tr key={user._id} className="bg-white border-b">
                        <td className="py-3 px-6">{user._id}</td>
                        <td className="py-3 px-6">{user.name}</td>
                        <td className="py-3 px-6">{user.email}</td>
                        <td className="py-3 px-6">{user.age}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <button
                                onClick={() => navigate(`/user/${user._id}`)}
                                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">
                                Read
                            </button>
                            <button 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"
                                onClick={() => navigate(`/user/${user._id}/edit`)}
                            >
                                Edit
                            </button>
                            <button onClick={() => handleDelete(user._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>
    </div>
  );
};
 
export default UserLists;
C:\react-js\my-app\src\pages\User.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
//src\pages\User.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 User = () => {
  const navigate = useNavigate();
  const { id } = useParams();
  const {
    isLoading,
    isError,
    data: user,
    error,
  } = useQuery({
    queryKey: ["users", id],
    queryFn: () => fetchUser(id),
  });
 
  if (isLoading) return "loading...";
  if (isError) return `Error: ${error.message}`;
 
 
  return (
    <div className="overflow-x-auto py-10">
      <button onClick={() => navigate("/")}>back to list users</button>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <p>{user.age}</p>
    </div>
  )
}
 
export default User
C:\react-js\my-app\src\pages\User.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
//src\pages\User.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),
  });
  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="overflow-x-auto py-10">
      <UserForm onSubmit={handleSubmit} initialValue={user} />
    </div>
  )
}
 
export default EditUser
C:\react-js\my-app\src\components\AddUser.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
//src\components\AddUser.jsx
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { createUser } from "../api/users"
import UserForm from "./UserForm"
import { v4 as uuidv4 } from 'uuid'; //npm i uuid
 
const AddUser = () => {
  const queryClient = useQueryClient();
 
  const createUserMutation = useMutation({
    mutationFn: createUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users']});
      console.log("success!")
    }
  });
 
  const handleAddPost = (user) => {
    createUserMutation.mutate({
      id: uuidv4(),
      ...user
    })
  }
 
  return (
    <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>
  )
}
 
export default AddUser
C:\react-js\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-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: "",
      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
C:\react-js\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
//src\api\users.jsx
export async function fetchUsers() {
  const response = await fetch('http://localhost:3001/users');
  return response.json()
}
 
export async function fetchUser(id) {
  const response = await fetch(`http://localhost:3001/users/get/${id}`);
  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 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()
}
Run
C:\react-js\my-app> npm run dev

Sunday, July 28, 2024

React.js 18 Node Express CRUD (Create Read Update and Delete) | MySQL Tailwind CSS

React.js 18 Node Express CRUD (Create Read Update and Delete) | MySQL Tailwind CSS

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

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

mysql
https://www.npmjs.com/package/mysql
$ npm install mysql
PS C:\nodeproject>npm install mysql

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

bcrypt
https://www.npmjs.com/package/bcrypt
A library to help you hash passwords.
npm install bcrypt

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
//index.js
const express = require('express')
const bodyParser = require('body-parser');
const cors = require('cors');
const bycrypt = require('bcrypt'); //npm install bcrypt https://www.npmjs.com/package/bcrypt
const db = require('./db');
 
const app = express()
 
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
 
app.use(cors({
      origin: 'http://localhost:3000',
      credentials: true
}));
 
app.get('/', (req, res) => {
  res.send('Hello World!')
})
 
app.post("/api/adduser", async (req, res) => {
  const sql ="INSERT INTO users (name,email,username,password) VALUES (?, ?, ?, ?)";
 
  const hashedPassword =  await bycrypt.hash(req.body.password, 10);
   
  const values = [req.body.name, req.body.email, req.body.username, hashedPassword];
 
  db.query(sql, values, (err, result) => {
    if (err)
      return res.json({ message: "Something unexpected has occured" + err });
    return res.json({ success: "New User added successfully" });
  });
 
});
 
app.get("/api/users", (req, res) => {
  const sql = "SELECT * FROM users";
  db.query(sql, (err, result) => {
    if (err) res.json({ message: "Server error" });
    return res.json(result);
  });
});
 
app.get("/api/getuser/:id", (req, res) => {
  const id = req.params.id;
  const sql = "SELECT * FROM users WHERE id= ?";
  db.query(sql, [id], (err, result) => {
    if (err) res.json({ message: "Server error" });
    return res.json(result);
  });
});
 
app.put("/api/edit/:id", (req, res) => {
  const id = req.params.id;
  const sql ="UPDATE users SET name=?, email=?, username=? WHERE id=?";
    
  const values = [
    req.body.name,
    req.body.email,
    req.body.username,
    id,
  ];
  db.query(sql, values, (err, result) => {
    if (err)
      return res.json({ message: "Something unexpected has occured" + err });
    return res.json({ success: "User updated successfully" });
  });
});
 
app.delete("/api/delete/:id", (req, res) => {
  const id = req.params.id;
  const sql = "DELETE FROM users WHERE id=?";
  const values = [id];
  db.query(sql, values, (err, result) => {
    if (err)
      return res.json({ message: "Something unexpected has occured" + err });
    return res.json({ success: "Student successfully Deleted" });
  });
});
 
app.listen(3001, () => {console.log('Server started on port 3001')});
db.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
//db.js
const mysql = require("mysql");
  
const db = mysql.createPool({
    socketPath: '/Applications/MAMP/tmp/mysql/mysql.sock',
    connectionLimit: 10,
    host: "localhost",
    user: "root",
    password: "root",
    database: "nodeexpressDB"
});
  
// Ping database to check for common exception errors.
db.getConnection((err, connection) => {
    if (err) {
        if (err.code === 'PROTOCOL_CONNECTION_LOST') {
            console.error('Database connection was closed.');
        }
        if (err.code === 'ER_CON_COUNT_ERROR') {
            console.error('Database has too many connections.');
        }
        if (err.code === 'ECONNREFUSED') {
            console.error('Database connection was refused.');
        }
    }
   
    if (connection) connection.release();
   
    return;
});
  
module.exports = db;
React JS
https://react.dev/learn/start-a-new-react-project

npx create-next-app@latest
npx create-react-app@latest {project name}
Create Project
C:\react-js>npx create-react-app@latest my-app
Run
C:\react-js\my-app> npm start

Install tailwindcss https://tailwindcss.com/docs/guides/create-react-app npm install -D tailwindcss npx tailwindcss init Install react-router-dom https://www.npmjs.com/package/react-router-dom Install axios
npm install axios
https://www.npmjs.com/package/axios
C:\react-js\my-app\src\App.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//src\App.js
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom"; //npm i react-router-dom https://www.npmjs.com/package/react-router-dom
import Home from "./elements/Home";
import Create from "./elements/Create";
import Read from "./elements/Read";
import Edit from "./elements/Edit";
 
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
         <Route path="/create" element={<Create />} />
         <Route path="/read/:id" element={<Read />} />
         <Route path="/edit/:id" element={<Edit />} />
      </Routes>
    </BrowserRouter>
  );
}
 
export default App;
C:\react-js\my-app\src\elements\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
//C:\react-js\my-app\src\elements\Home.jsx
import ListUser from './ListUser'
import { Link } from 'react-router-dom'
import { Suspense } from "react";
 
function Home() {
 
  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">
            <h1 className="text-4xl font-bold">React.js 18 Node Express CRUD (Create Read Update and Delete) | MySQL Tailwind CSS</h1>
        </div>
        <div className="overflow-x-auto py-10">
            <div className="mb-2 w-full text-right">
                <Link
                to="/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-4 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
                Add New User
                </Link>
            </div>
            <Suspense fallback="Loading...">
                <ListUser/>
            </Suspense>
        </div>
    </div>
  )
}
 
export default Home
C:\react-js\my-app\src\elements\ListUser.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
//C:\react-js\my-app\src\elements\ListUser.jsx
import axios from 'axios'; //npm install axios https://www.npmjs.com/package/react-axios
import { useEffect, useState } from "react";
import { Link } from 'react-router-dom'
 
export default function ListUser() {
    const [users, setUsers] = useState([]);
   
    useEffect(() => {
        getUsers();
    }, []);
    
    function getUsers() {
        axios.get('http://localhost:3001/api/users').then(function(response) {
            console.log(response.data);
            setUsers(response.data);
        });
    }
 
    const deleteUser = (id) => {
        axios.delete(`http://localhost:3001/api/delete/${id}`).then(function(response){
            console.log(response.data);
            getUsers();
        });
    }
 
    return (
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">#</th>
                    <th className="py-3 px-6">Name</th>
                    <th className="py-3 px-6">Email</th>
                    <th className="py-3 px-6">Username</th>
                    <th className="py-3 px-6 text-center">Actions</th>
                </tr>
            </thead>
            <tbody>
                {users.map((user, key) =>
                    <tr key={key} className="bg-white border-b">
                        <td className="py-3 px-6">{user.id}</td>
                        <td className="py-3 px-6">{user.name}</td>
                        <td className="py-3 px-6">{user.email}</td>
                        <td className="py-3 px-6">{user.username}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <Link
                                to={`/read/${user.id}`}
                                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">
                                Read
                            </Link>
                            <Link 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"
                                to={`edit/${user.id}/`}>
                                Edit
                            </Link>
                            <button onClick={() => deleteUser(user.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>
    )
}
C:\react-js\my-app\src\elements\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
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
//C:\react-js\my-app\src\elements\Create.jsx
import React, { useState } from "react";
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import {useNavigate} from 'react-router-dom'
 
const Addnewuser = () => {
    const [inputs, setInputs] = useState([]);
    
    const handleChange = (event) => {
        const name = event.target.name;
        const value = event.target.value;
        setInputs(values => ({...values, [name]: value}));
    }
   
    const navigate = useNavigate()
  
    const handleSubmit = (event) => {
        event.preventDefault();
   
        axios.post('http://localhost:3001/api/adduser', inputs).then(function(response){
            console.log(response.data);
            navigate('/')
        });
    }
 
    return (
    <div className="max-w-md mx-auto mt-5">
        <h1 className="text-2xl text-center mb-2">Add New User</h1>
        <div>
        <form onSubmit={handleSubmit}>
        <div className="mb-5">
          <label htmlFor="name" className="block text-sm font-medium text-gray-900">
            Name
          </label>
          <input
            type="text"
            name="name"
            id="name"
            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"
            placeholder="Name..."
            onChange={handleChange}
          />
        </div>
        <div className="mb-5">
          <label htmlFor="email" className="block text-sm font-medium text-gray-900">
            Email
          </label>
          <input
            type="email"
            name="email"
            id="email"
            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"
            placeholder="email..."
            onChange={handleChange}
          />
        </div>
        <div className="mb-5">
          <label htmlFor="username" className="block text-sm font-medium text-gray-900">
            Username
          </label>
          <input
            type="text"
            name="username"
            id="username"
            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"
            placeholder="Username..."
            onChange={handleChange}
          />
        </div>
        <div className="mb-5">
          <label htmlFor="password" className="block text-sm font-medium text-gray-900">
            Password
          </label>
          <input
            type="password"
            name="password"
            id="password"
            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"
            placeholder="Password..."
            onChange={handleChange}
          />
        </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">
        Add New User</button>
        </form>
        </div>
    </div>
  );
};
      
export default Addnewuser;
C:\react-js\my-app\src\elements\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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//C:\react-js\my-app\src\elements\Read.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import { useParams } from "react-router-dom";
 
export default function ViewUser() {
    const {id}=useParams();
     
    console.log(id);
     
    const[user,setUser]=useState([]);
      
    useEffect(()=>{
        fetchUser();
    },[id]);
      
    const fetchUser=async()=>{
        try{
        const result=await axios.get("http://localhost:3001/api/getuser/"+id);
          console.log(result.data[0]);
          setUser(result.data[0])
      
        }catch(err){
            console.log("Something Wrong");
        }
    }
     
    return (
    <div className="max-w-2xl mx-auto mt-5">
      <h1 className="text-2xl text-center mb-2">View User</h1>
      <table className="table table-zebra">
          <thead className="text-sm text-gray-700 uppercase bg-gray-50">
            <tr>
              <th>S No.</th>
              <th>Name</th>
              <th>Email</th>        
              <th>Username</th>     
            </tr>
          </thead>
          <tbody>
            <tr>
                <td>{user.id}</td>
                <td>{user.name}</td>
                <td>{user.email}</td>
                <td>{user.username}</td>
            </tr>
          </tbody>
      </table>
    </div>
  );
}
C:\react-js\my-app\src\elements\Edit.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
//C:\react-js\my-app\src\elements\Edit.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import { useParams, useNavigate } from "react-router-dom";
 
export default function Edituser() {
    const [inputs, setInputs] = useState([]);
    const {id}=useParams();
    //console.log(id);
    
    useEffect(() => {
        getUser();
    }, []);
    
    function getUser() {
        axios.get(`http://localhost:3001/api/getuser/${id}`).then(function(response) {
            console.log(response.data[0]);
            setInputs(response.data[0]);
        });
    }
   
    const handleChange = (event) => {
        const name = event.target.name;
        const value = event.target.value;
        setInputs(values => ({...values, [name]: value}));
    }
   
    const navigate = useNavigate()
  
    const handleSubmit = (event) => {
        event.preventDefault();
    
        axios.put(`http://localhost:3001/api/edit/${id}`, inputs).then(function(response){
            console.log(response.data);
            navigate('/')
        });
            
    }
    return (
    <div className="max-w-md mx-auto mt-5">
      <h1 className="text-2xl text-center mb-2">Edit Form</h1>
            <form onSubmit={handleSubmit}>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900"> ID:</label>
                    <input type="text" id="id" name="id" value={id} disabled />
                </div>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900"> Full Name:</label>
                    <input type="text" 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"
                    placeholder="Enter Your Full Name" name="name"
                    value={inputs.name || ''}
                    onChange={handleChange}/>
                </div>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900">Email:</label>
                    <input type="text" 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"
                    id="email" placeholder="Enter email" name="email"
                    value={inputs.email || ''}
                    onChange={ handleChange}/>
                </div>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900">Username:</label>
                    <input type="text" 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"
                    id="username" placeholder="Enter username" name="username"
                    value={inputs.username || ''}
                    onChange={ handleChange}/>
                </div>
                <button type="submit" name="update"  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">
                    Update</button>
            </form>
    </div>
  );
}
Run C:\react-j\my-app>npm start
http://localhost:3000/

Saturday, July 27, 2024

Next.js 14 Node Express CRUD (Create Read Update and Delete) | MySQL

Next.js 14 Node Express CRUD (Create Read Update and Delete) | MySQL

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

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

mysql
$ npm install mysql
PS C:\nodeproject>npm install mysql

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

bcrypt
https://www.npmjs.com/package/bcrypt
A library to help you hash passwords.
npm install bcrypt

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
//index.js
const express = require('express')
const bodyParser = require('body-parser');
const cors = require('cors');
const bycrypt = require('bcrypt'); //npm install bcrypt
const db = require('./db');
 
const app = express()
 
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
 
app.use(cors({
      origin: 'http://localhost:3000',
      credentials: true
}));
 
app.get('/', (req, res) => {
  res.send('Hello World!')
})
 
app.post("/api/adduser", async (req, res) => {
  const sql ="INSERT INTO users (name,email,username,password) VALUES (?, ?, ?, ?)";
 
  const hashedPassword =  await bycrypt.hash(req.body.password, 10);
 
  const values = [req.body.name, req.body.email, req.body.username, hashedPassword];
  db.query(sql, values, (err, result) => {
    if (err)
      return res.json({ message: "Something unexpected has occured" + err });
    return res.json({ success: "New User added successfully" });
  });
});
 
app.get("/api/users", (req, res) => {
  const sql = "SELECT * FROM users";
  db.query(sql, (err, result) => {
    if (err) res.json({ message: "Server error" });
    return res.json(result);
  });
});
 
app.get("/api/getuser/:id", (req, res) => {
  const id = req.params.id;
  const sql = "SELECT * FROM users WHERE id= ?";
  db.query(sql, [id], (err, result) => {
    if (err) res.json({ message: "Server error" });
    return res.json(result);
  });
});
 
app.put("/api/edit/:id", async (req, res) => {
  const id = req.params.id;
  const sql ="UPDATE users SET name=?, email=?, username=? WHERE id=?";
   
  const values = [
    req.body.name,
    req.body.email,
    req.body.username,
    id,
  ];
  db.query(sql, values, (err, result) => {
    if (err)
      return res.json({ message: "Something unexpected has occured" + err });
    return res.json({ success: "User updated successfully" });
  });
});
 
app.delete("/api/delete/:id", (req, res) => {
  const id = req.params.id;
  const sql = "DELETE FROM users WHERE id=?";
  const values = [id];
  db.query(sql, values, (err, result) => {
    if (err)
      return res.json({ message: "Something unexpected has occured" + err });
    return res.json({ success: "Student successfully Deleted" });
  });
});
 
app.listen(3001, () => {console.log('Server started on port 3001')});
db.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
//db.js
const mysql = require("mysql");
 
const db = mysql.createPool({
    socketPath: '/Applications/MAMP/tmp/mysql/mysql.sock',
    connectionLimit: 10,
    host: "localhost",
    user: "root",
    password: "root",
    database: "nodeexpressDB"
});
 
// Ping database to check for common exception errors.
db.getConnection((err, connection) => {
    if (err) {
        if (err.code === 'PROTOCOL_CONNECTION_LOST') {
            console.error('Database connection was closed.');
        }
        if (err.code === 'ER_CON_COUNT_ERROR') {
            console.error('Database has too many connections.');
        }
        if (err.code === 'ECONNREFUSED') {
            console.error('Database connection was refused.');
        }
    }
  
    if (connection) connection.release();
  
    return;
});
 
module.exports = db;
Next.js

Install requirements
npm install axios
https://www.npmjs.com/package/axios

app\page.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
//app\page.js
import ListUser from '@/components/ListUser'
import { Suspense } from "react";
import Link from "next/link";
  
export default function Home() {
  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">
        <h1 className="text-4xl font-bold">Next.js 14 Node Express CRUD (Create Read Update and Delete) | MySQL</h1>
      </div>   
      <div className="overflow-x-auto">
          <div className="mb-2 w-full text-right">
            <Link
              href="/users/create"
              className="btn btn-primary">
              Add New User
            </Link>
          </div>
        <Suspense fallback="Loading...">
            <ListUser/>
          </Suspense>
      </div> 
    </div>
  );
}
app\users\create\page.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
//app\users\create\page.jsx
"use client";
  
import React, { useState } from "react";
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import { useRouter } from 'next/navigation';
 
const Addnewuser = () => {
    const [inputs, setInputs] = useState([]);
   
    const handleChange = (event) => {
        const name = event.target.name;
        const value = event.target.value;
        setInputs(values => ({...values, [name]: value}));
    }
  
    const router = useRouter();
 
    const handleSubmit = (event) => {
        event.preventDefault();
  
        axios.post('http://localhost:3001/api/adduser', inputs).then(function(response){
            console.log(response.data);
            router.push('/')
        });
    }
  
    return (
    <div className="max-w-md mx-auto mt-5">
        <h1 className="text-2xl text-center mb-2">Add New User</h1>
        <div>
        <form onSubmit={handleSubmit}>
        <div className="mb-5">
          <label htmlFor="name" className="block text-sm font-medium text-gray-900">
            Name
          </label>
          <input
            type="text"
            name="name"
            id="name"
            className="input input-bordered input-primary w-full max-w-xs"
            placeholder="Name..."
            onChange={handleChange}
          />
        </div>
        <div className="mb-5">
          <label htmlFor="email" className="block text-sm font-medium text-gray-900">
            Email
          </label>
          <input
            type="email"
            name="email"
            id="email"
            className="input input-bordered input-primary w-full max-w-xs"
            placeholder="email..."
            onChange={handleChange}
          />
        </div>
        <div className="mb-5">
          <label htmlFor="username" className="block text-sm font-medium text-gray-900">
            Username
          </label>
          <input
            type="text"
            name="username"
            id="username"
            className="input input-bordered input-primary w-full max-w-xs"
            placeholder="Username..."
            onChange={handleChange}
          />
        </div>
        <div className="mb-5">
          <label htmlFor="password" className="block text-sm font-medium text-gray-900">
            Password
          </label>
          <input
            type="password"
            name="password"
            id="password"
            className="input input-bordered input-primary w-full max-w-xs"
            placeholder="Password..."
            onChange={handleChange}
          />
        </div>
        <button type="submit" className="btn btn-primary">Add New User</button>
      </form>
    </div>
    </div>
  );
};
     
export default Addnewuser;
app\users\edit\[id]\page.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
//app\users\edit\[id]\page.jsx
"use client";
    
import React, { useState, useEffect } from 'react';
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import { useParams, useRouter } from 'next/navigation'
 
export default function Edituser() {
    const [inputs, setInputs] = useState([]);
    const {id}=useParams();
    //console.log(id);
   
    useEffect(() => {
        getUser();
    }, []);
   
    function getUser() {
        axios.get(`http://localhost:3001/api/getuser/${id}`).then(function(response) {
            console.log(response.data[0]);
            setInputs(response.data[0]);
        });
    }
  
    const handleChange = (event) => {
        const name = event.target.name;
        const value = event.target.value;
        setInputs(values => ({...values, [name]: value}));
    }
  
    const router = useRouter();
 
    const handleSubmit = (event) => {
        event.preventDefault();
   
        axios.put(`http://localhost:3001/api/edit/${id}`, inputs).then(function(response){
            console.log(response.data);
            router.push('/')
        });
           
    }
    return (
    <div className="max-w-md mx-auto mt-5">
      <h1 className="text-2xl text-center mb-2">Edit Form</h1>
            <form onSubmit={handleSubmit}>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900"> ID:</label>
                    <input type="text" id="id" name="id" value={id} disabled />
                </div>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900"> Full Name:</label>
                    <input type="text" className="input input-bordered input-primary w-full max-w-xs" placeholder="Enter Your Full Name" name="name"
                    value={inputs.name || ''}
                    onChange={handleChange}/>
                </div>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900">Email:</label>
                    <input type="text" className="input input-bordered input-primary w-full max-w-xs" id="email" placeholder="Enter email" name="email"
                    value={inputs.email || ''}
                    onChange={ handleChange}/>
                </div>
                <div className="mb-3 mt-3">
                    <label className="block text-sm font-medium text-gray-900">Username:</label>
                    <input type="text" className="input input-bordered input-primary w-full max-w-xs" id="username" placeholder="Enter username" name="username"
                    value={inputs.username || ''}
                    onChange={ handleChange}/>
                </div>
                <button type="submit" name="update"  className="btn btn-primary">Update</button>
            </form>
    </div>
  );
}
app\users\read\[id]\page.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
//app\users\read\[id]\page.jsx
"use client";
    
import React, { useState, useEffect } from 'react';
import axios from 'axios' //npm install axios https://www.npmjs.com/package/axios
import { useParams } from 'next/navigation'
   
export default function ViewUser() {
    const {id}=useParams();
    
    console.log(id);
    
    const[user,setUser]=useState([]);
     
    useEffect(()=>{
        fetchUser();
    },[id]);
     
    const fetchUser=async()=>{
        try{
        const result=await axios.get("http://localhost:3001/api/getuser/"+id);
          console.log(result.data[0]);
          setUser(result.data[0])
     
        }catch(err){
            console.log("Something Wrong");
        }
    }
    
    return (
    <div className="max-w-2xl mx-auto mt-5">
      <h1 className="text-2xl text-center mb-2">View User</h1>
      <table className="table table-zebra">
          <thead className="text-sm text-gray-700 uppercase bg-gray-50">
            <tr>
              <th>S No.</th>
              <th>Name</th>
              <th>Email</th>        
              <th>Username</th>     
            </tr>
          </thead>
          <tbody>
            <tr>
                <td>{user.id}</td>
                <td>{user.name}</td>
                <td>{user.email}</td>
                <td>{user.username}</td>
            </tr>
          </tbody>
      </table>
    </div>
  );
}
app\users\components\ListUser.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
//app\users\components\ListUser.jsx
"use client";
  
import axios from 'axios';
import { useEffect, useState } from "react";
import Link from "next/link";
  
export default function ListUser() {
  
    const [users, setUsers] = useState([]);
  
    useEffect(() => {
        getUsers();
    }, []);
   
    function getUsers() {
        axios.get('http://localhost:3001/api/users').then(function(response) {
            //console.log(response.data);
            setUsers(response.data);
        });
    }
  
    const deleteUser = (id) => {
        axios.delete(`http://localhost:3001/api/delete/${id}`).then(function(response){
            console.log(response.data);
            getUsers();
        });
    }
      
    return (
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">#</th>
                    <th className="py-3 px-6">Name</th>
                    <th className="py-3 px-6">Email</th>
                    <th className="py-3 px-6">Username</th>
                    <th className="py-3 px-6 text-center">Actions</th>
                </tr>
            </thead>
            <tbody>
                {users.map((user, key) =>
                    <tr key={key} className="bg-white border-b">
                        <td className="py-3 px-6">{user.id}</td>
                        <td className="py-3 px-6">{user.name}</td>
                        <td className="py-3 px-6">{user.email}</td>
                        <td className="py-3 px-6">{user.username}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <Link
                                href={`/users/read/${user.id}`}
                                className="btn btn-success">
                                Read
                            </Link>
                            <Link className="btn btn-info" href={`users/edit/${user.id}/`}>
                                Edit
                            </Link>
                            <button onClick={() => deleteUser(user.id)} className="btn btn-error">Delete</button>
                        </td>
                    </tr>
                )}
            </tbody>
        </table>
    )
}
run C:\nextjs>npm run dev

Related Post