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 |
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>, ); |
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> ); } |
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; |
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 |
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 |
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 |
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 |
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() } |
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; |
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" await mongoose.connect( 'mongodb+srv://cairocoders:123456@cluster0.x45tgvn.mongodb.net/expressdb?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}`) }) |
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; |