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; |
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> ); } |
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> ); } |
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> ); } |
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 |
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 |
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 |
1 2 3 4 5 6 | //app\components\LoadingSpinner.tsx export default function LoadingSpinner() { return ( <h1>Loading...</h1> ) } |
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; |
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 |
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 |
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() { 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() } |