Install nextjs npx create-next-app@latest https://nextjs.org/docs/getting-started/installation
Install the following
npm install react-daisyui
https://www.npmjs.com/package/react-daisyui
daisyUI components built with React, Typescript and TailwindCSS
Mongoose
npm install mongoose
https://www.npmjs.com/package/mongoose
edit tailwind.config.js Add daisyui to plugins
edit tailwind.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //edit tailwind.config.js import type { Config } from 'tailwindcss' const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}' , './components/**/*.{js,ts,jsx,tsx,mdx}' , './app/**/*.{js,ts,jsx,tsx,mdx}' , ], theme: { extend: { backgroundImage: { 'gradient-radial' : 'radial-gradient(var(--tw-gradient-stops))' , 'gradient-conic' : 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))' , }, }, }, } export default config |
1 | MONGODB_URI=mongodb: //127.0.0.1/nextjs14 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //app\layout.js import { Inter } from 'next/font/google' import './globals.css' import Navbar from "@/components/Navbar" ; const inter = Inter({ subsets: [ 'latin' ] }) export const metadata = { title: 'Create Next App' , description: 'Generated by create next app' , } export default function RootLayout({ children }) { return ( <html lang= "en" > <body className={inter.className}> <div className= "container mx-auto px-4" > <Navbar /> <div className= "pb-8" >{children}</div> </div> </body> </html> ) } |
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 | //components\Navbar.jsx import Link from "next/link" ; export default function Navbar() { return ( <div className= "navbar bg-base-100" > <div className= "flex-1" > <a className= "btn btn-ghost text-xl" >Cairocoders</a> </div> <div className= "flex-none" > <div className= "dropdown dropdown-end" > <div tabIndex={0} role= "button" className= "btn btn-ghost btn-circle" > <div className= "indicator" > <svg xmlns= "http://www.w3.org/2000/svg" className= "h-5 w-5" fill= "none" viewBox= "0 0 24 24" stroke= "currentColor" ><path strokeLinecap= "round" strokeLinejoin= "round" strokeWidth= "2" d= "M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /></svg> <span className= "badge badge-sm indicator-item" >8</span> </div> </div> <div tabIndex={0} className= "mt-3 z-[1] card card-compact dropdown-content w-52 bg-base-100 shadow" > <div className= "card-body" > <span className= "font-bold text-lg" >8 Items</span> <span className= "text-info" >Subtotal: $999 </span> <div className= "card-actions" > <button className= "btn btn-primary btn-block" >View cart</button> </div> </div> </div> </div> <div className= "dropdown dropdown-end" > <div tabIndex={0} role= "button" className= "btn btn-ghost btn-circle avatar" > <div className= "w-10 rounded-full" > <img alt= "Tailwind CSS Navbar component" src= "https://daisyui.com/images/stock/photo-1534528741775-53994a69daeb.jpg" /> </div> </div> <ul tabIndex={0} className= "menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52" > <li> <a className= "justify-between" > Profile <span className= "badge" >New</span> </a> </li> <li><a>Settings</a></li> <li><a>Logout</a></li> </ul> </div> </div> </div> ); } |
1 | MONGODB_URI=mongodb: //127.0.0.1/nextjs14 |
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 | //components\EditProductForm.jsx "use client" ; import { useState } from "react" ; import { useRouter } from "next/navigation" ; export default function EditProductForm({ id, name, image, price,category }) { const [newName, setNewTitle] = useState(name); const [newImage, setNewImage] = useState(image); const [newPrice, setNewPrice] = useState(price); const [newCategory, setNewCategory] = useState(category); const router = useRouter(); const handleSubmit = async (e) => { e.preventDefault(); try { const res = await fetch(`http: //localhost:3000/api/products/${id}`, { method: "PUT" , headers: { "Content-type" : "application/json" , }, body: JSON.stringify({ newName, newImage, newPrice, newCategory }), }); if (!res.ok) { throw new Error( "Failed to update Product" ); } router.refresh(); router.push( "/products" ); } catch (error) { console.log(error); } }; return ( <> <div className= "flex justify-between items-center" > <h1 className= "font-bold py-10 text-2xl" >Update Product</h1> </div> <form onSubmit={handleSubmit} className= "flex flex-col gap-3" > <input onChange={(e) => setNewTitle(e.target.value)} value={newName} className= "input input-bordered input-accent w-full max-w-xs" type= "text" /> <input onChange={(e) => setNewImage(e.target.value)} value={newImage} className= "input input-bordered input-accent w-full max-w-xs" type= "text" /> <input onChange={(e) => setNewPrice(e.target.value)} value={newPrice} className= "input input-bordered input-accent w-full max-w-xs" type= "text" /> <input onChange={(e) => setNewCategory(e.target.value)} value={newCategory} className= "input input-bordered input-accent w-full max-w-xs" type= "text" /> <button className= "btn btn-primary w-full max-w-xs" > Update Product </button> </form> </> ); } |
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 | //components\EditProductForm.jsx import Link from "next/link" ; import RemoveBtn from "./RemoveBtn" ; import Image from 'next/image' const getProducts = async () => { try { cache: "no-store" , }); if (!res.ok) { throw new Error( "Failed to fetch products" ); } return res.json(); } catch (error) { console.log( "Error loading products: " , error); } }; export default async function ProductssList() { const { products } = await getProducts(); return ( <> <div className= "overflow-x-auto" > <div className= "flex justify-between items-center" > <h1 className= "font-bold py-10 text-2xl" >Next.js 14 CRUD Crate, Read, Update and Delete - MongoDB Daisyui TailwindCSS</h1> </div> <div className= "text-right" > <Link className= "btn btn-primary" href={ "/addProduct" }> Add Product </Link> </div> <table className= "table" > <thead> <tr> <th> <label> <input type= "checkbox" className= "checkbox" /> </label> </th> <th>Name</th> <th>Price</th> <th>Category</th> <th /> </tr> </thead> <tbody> {products.map((rs) => ( <tr className= "hover" key={rs._id}> <th> <label> <input type= "checkbox" className= "checkbox" /> </label> </th> <td> <div className= "flex items-center gap-3" > <div className= "avatar" > <div className= "mask mask-squircle w-12 h-12" > <Image src={rs.image} alt={rs.name} width={80} height={80} className= "rounded-lg" /> </div> </div> <div> <div className= "font-bold" >{rs.name}</div> </div> </div> </td> <td> ${rs.price} </td> <td>{rs.category}</td> <th> <Link href={`/editProduct/${rs._id}`}> <button className= "btn btn-primary" >Edit</button> </Link> <RemoveBtn id={rs._id} /> </th> </tr> ))} </tbody> </table> </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 | //components\RemoveBtn.jsx "use client" ; import { useRouter } from "next/navigation" ; export default function RemoveBtn({ id }) { const router = useRouter(); const removeProduct= async () => { const confirmed = confirm( "Are you sure?" ); if (confirmed) { const res = await fetch(`http: //localhost:3000/api/products?id=${id}`, { method: "DELETE" , }); if (res.ok) { router.refresh(); } } }; return ( <button onClick={removeProduct} className= "btn btn-error ml-2" > Delete </button> ); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | //libs\mongodb.js import mongoose from "mongoose" ; const connectMongoDB = async () => { try { await mongoose.connect(process.env.MONGODB_URI); console.log( "Connected to MongoDB." ); } catch (error) { console.log(error); } }; export default connectMongoDB; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //models\ProductModel.js import mongoose, { Schema } from "mongoose" ; const topicSchema = new Schema( { name: { type: String, required: true }, category: { type: String, required: true }, image: { type: String, required: true }, price: { type: Number, required: true }, }, { timestamps: true, } ); const ProductModel = mongoose.models.Product || mongoose.model( "Product" , topicSchema); export default ProductModel; |
1 2 3 4 5 6 7 | //app\products\page.js import Productlist from "@/components/ProductsList" ; export default function Home() { return <productlist>; } </productlist> |
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 | //app\addProduct\page.jsx "use client" ; import { useState } from "react" ; import { useRouter } from "next/navigation" ; export default function AddProduct() { const [name, setName] = useState( "" ); const [image, setImage] = useState( "" ); const [price, setPrice] = useState( "" ); const [category, setCategory] = useState( "" ); const router = useRouter(); const handleSubmit = async (e) => { e.preventDefault(); if (!name || !image) { alert( "Name and image are required." ); return ; } try { method: "POST" , headers: { "Content-type" : "application/json" , }, body: JSON.stringify({ name, image, price, category }), }); if (res.ok) { router.push( "/products" ); } else { throw new Error( "Failed to create a Product" ); } } catch (error) { console.log(error); } }; return ( <> <div className= "flex justify-between items-center" > <h1 className= "font-bold py-10 text-2xl" >Add New Product</h1> </div> <form onSubmit={handleSubmit} className= "flex flex-col gap-3" > <input onChange={(e) => setName(e.target.value)} value={name} className= "input input-bordered input-accent w-full max-w-xs" type= "text" placeholder= "Product Name" /> <input onChange={(e) => setImage(e.target.value)} value={image} className= "input input-bordered input-accent w-full max-w-xs" type= "text" placeholder= "/images/1.jpg" defaultValue= "/images/1.jpg" /> <input onChange={(e) => setPrice(e.target.value)} value={price} className= "input input-bordered input-accent w-full max-w-xs" type= "number" placeholder= "1" defaultValue= "1" /> <input onChange={(e) => setCategory(e.target.value)} value={category} className= "input input-bordered input-accent w-full max-w-xs" type= "text" placeholder= "Product Category" /> <button type= "submit" className= "btn btn-primary w-full max-w-xs" > Add Product </button> </form> </> ); } |
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\editProduct\[id]\page.js import EditProductForm from "@/components/EditProductForm" ; const getProductById = async (id) => { try { const res = await fetch(`http: //localhost:3000/api/products/${id}`, { cache: "no-store" , }); if (!res.ok) { throw new Error( "Failed to fetch product" ); } return res.json(); } catch (error) { console.log(error); } }; export default async function EditProduct({ params }) { const { id } = params; const { product } = await getProductById(id); const { name, image, price,category } = product; return <EditProductForm id={id} name={name} image={image} price={price} category={category} />; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //app\api\products\route.js import connectMongoDB from "@/libs/mongodb" ; import Product from "@/models/ProductModel" ; import { NextResponse } from "next/server" ; export async function GET() { await connectMongoDB(); const products = await Product.find(); return NextResponse.json({ products }); } export async function POST(request) { const { name, image,price,category } = await request.json(); await connectMongoDB(); await Product.create({ name, image, price, category }); return NextResponse.json({ message: "Product Created" }, { status: 201 }); } export async function DELETE (request) { const id = request.nextUrl.searchParams.get( "id" ); await connectMongoDB(); await Product.findByIdAndDelete(id); return NextResponse.json({ message: "Product deleted" }, { status: 200 }); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //app\api\products\[id]\route.js import connectMongoDB from "@/libs/mongodb" ; import Product from "@/models/ProductModel" ; import { NextResponse } from "next/server" ; export async function PUT(request, { params }) { const { id } = params; const { newName: name, newImage: image, newPrice: price, newCategory: category } = await request.json(); await connectMongoDB(); await Product.findByIdAndUpdate(id, { name, image, price, category}); return NextResponse.json({ message: "Product updated" }, { status: 200 }); } export async function GET(request, { params }) { const { id } = params; await connectMongoDB(); const product = await Product.findOne({ _id: id }); return NextResponse.json({ product }, { status: 200 }); } |