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
//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}`) })User.js
//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
//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
//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
//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
//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 AddUserapp\components\UserForm.tsx
//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 UserFormapp\components\UserList.tsx
//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 UserListapp\components\LoadingSpinner.tsx
//app\components\LoadingSpinner.tsx export default function LoadingSpinner() { return ( <h1>Loading...</h1> ) }app\components\Paginationnumber.tsx
//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
//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 Userapp\user\edit\[id]\page.tsx
//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 EditUserapp\api\page.tsx
//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