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
//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))',
},
},
},
plugins: [require('daisyui')], //https://www.npmjs.com/package/react-daisyui
}
export default config
.env
MONGODB_URI=mongodb://127.0.0.1/nextjs14app\layout.js
//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>
)
}
components\Navbar.jsx
//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>
);
}
.env
MONGODB_URI=mongodb://127.0.0.1/nextjs14components\EditProductForm.jsx
//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>
</>
);
}
components\ProductsList.jsx
//components\EditProductForm.jsx
import Link from "next/link";
import RemoveBtn from "./RemoveBtn";
import Image from 'next/image'
const getProducts = async () => {
try {
const res = await fetch("http://localhost:3000/api/products", {
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>
</>
);
}
components\RemoveBtn.jsx
//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>
);
}
libs\mongodb.js
//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;
models\ProductModel.js
//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;
app\products\page.js
//app\products\page.js
import Productlist from "@/components/ProductsList";
export default function Home() {
return ;
}
app\addProduct\page.jsx
//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 {
const res = await fetch("http://localhost:3000/api/products", {
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>
</>
);
}
app\editProduct\[id]\page.js
//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} />;
}
app\api\products\route.js
//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 });
}
app\api\products\[id]\route.js
//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 });
}
run C:\nextjs>npm run dev
