Next.js
Installation:
npx create-next-app@latest
your-app-name
<--- Backend Code Start Here --->
You Can Define environment variables:
Create a .env file in next.js project:
And define environment variables as per your requirement:
MONGO_URI="your mongodb connection string"
SECRET_TOKEN="your secret token" //must be in capital
DOMAIN="http://localhost:3000/"
Connect Next.js to MongoDB:
Install Mongoose
In scr folder create a new folder dbConfig
In dbConfig folder create a new file dbConfig.ts
In dbConfig.ts file:
import mongoose, { connection } from "mongoose";
export async function connect() {
try {
mongoose.connect(process.env.MONGO_URI!);
connection.on("connected", () => {
console.log("Database Connected.");
});
connection.on("error", (err) => {
console.log("Database Connection Error" + err);
});
} catch (error: any) {
console.log("Something went wrong in connecting to Database.");
console.log(error.message);
}
}
Create a New Model:
In scr folder create a new folder models
In models folder create a new file userModel.js
In userModel.js file:
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
userName: {
type: String,
required: [true, "Please provide a user name."]
},
email: {
type: String,
required: [true, "Please provide an email."],
unique: true,
},
password: {
type: String,
required: [true, "Please provide a strong password."]
},
isVerified: {
type: Boolean,
default: false
},
isAdmin: {
type: Boolean,
default: false
},
forgotPasswordToken: String,
forgotPasswordTokenExpiry: Date,
verifyToken: String,
verifyTokenExpiry: Date,
})
const User = mongoose.model.users || mongoose.model("users", userSchema)
export default User;
Create a New User or Register User:
In scr/app folder create a new folder api/users/register
In api/users/register folder create a new file route.ts
In route.ts file:
// Without Verify User
import { connect } from "@/dbConfig/dbConfig";
import User from "@/models/userModel";
import { NextRequest, NextResponse } from "next/server";
import bcryptjs from "bcryptjs";
connect();
export async function POST(request: NextRequest) {
try {
const reqBody = await request.json();
const { username, email, password } = reqBody;
const user = User.find({ email });
if (user) {
return NextResponse.json(
{ error: "User is already exist." },
{ status: 400 }
);
}
const salt = await bcryptjs.genSalt(10);
const hashedPassword = await bcryptjs.hash(password, salt);
const newUser = new User({
username,
email,
password: hashedPassword,
});
const savedUser = await newUser.save();
if (savedUser) {
return NextResponse.json(
{ message: "User Registered Successfully", success: true, savedUser },
{ status: 200 }
);
} else {
return NextResponse.json(
{ error: "Something Went Wrong" },
{ status: 500 }
);
}
} catch (error: any) {
console.log(error.message);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
If you want to verify the user:
In scr folder create a new folder helpers
In helpers folder create a new file mailer.ts
In mailer.ts file:
Install nodemailer:
// With Verify User
import User from "@/models/userModel";
import nodemailer from "nodemailer";
import bcryptjs from "bcryptjs";
export const sendEmail = async ({ email, emailType, userId }: any) => {
try {
const hashedToken = await bcryptjs.hash(userId.toString(), 10);
if (emailType === "VERIFY") {
await User.findByIdAndUpdate(userId, {
$set: {
verifyToken: hashedToken,
verifyTokenExpiry: Date.now() + 3600000,
},
});
} else if (emailType === "RESET") {
await User.findByIdAndUpdate(userId, {
$set: {
forgotPasswordToken: hashedToken,
forgotPasswordTokenExpiry: Date.now() + 3600000,
},
});
}
var transport = nodemailer.createTransport({
host: "sandbox.smtp.mailtrap.io",
port: 2525,
auth: {
user: "your-secret-id",
pass: "your-secret-password",
},
});
const mailOptions = {
from: "ms6498289@gmail.com",
to: email,
subject:
emailType === "VERIFY" ? "Verify your email" : "Reset your password",
html: `<p>Click <a href="${
process.env.DOMAIN
}/verifyemail?token=${hashedToken}">here</a> to ${
emailType === "VERIFY" ? "verify your email" : "reset your password"
}
or copy and paste the link below in your browser.
</br> ${process.env.DOMAIN}/verifyemail?token=${hashedToken}
</p>`,
};
const mailResponse = await transport.sendMail(mailOptions);
return mailResponse;
} catch (error: any) {
throw new Error(error.messsage);
}
};
After that there are some changes:
In api/users/register/route.ts
import { connect } from "@/dbConfig/dbConfig";
import User from "@/models/userModel";
import { NextRequest, NextResponse } from "next/server";
import bcryptjs from "bcryptjs";
import { sendEmail } from "@/helpers/mailer";
connect();
export async function POST(request: NextRequest) {
try {
const reqBody = await request.json();
const { username, email, password } = reqBody;
const user = await User.findOne({ email });
if (user) {
return NextResponse.json(
{ error: "User is already exist" },
{ status: 400 }
);
}
const salt = await bcryptjs.genSalt(10);
const hashedPassword = await bcryptjs.hash(password, salt);
const newUser = new User({
username,
email,
password: hashedPassword,
});
const savedUser = await newUser.save();
// send verification email
const userId = savedUser._id;
await sendEmail({ email, emailType: "VERIFY", userId: userId });
return NextResponse.json({
message: "User registered successfully",
success: true,
savedUser,
});
} catch (error: any) {
console.log(error.message);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
After sending the mail we have to verify the email:
In scr/app folder create a new folder api/users/verifyemail
In api/users/verifyemail folder create a new file route.ts
In route.ts file:
import { connect } from "@/dbConfig/dbConfig";
import User from "@/models/userModel";
import { NextRequest, NextResponse } from "next/server";
connect();
export async function POST(request: NextRequest) {
try {
const reqBody = await request.json();
const { token } = reqBody;
const user = await User.findOne({
verifyToken: token,
verifyTokenExpiry: { $gt: Date.now() },
});
if (!user) {
return NextResponse.json({ error: "Invalid token" }, { status: 400 });
}
user.isVerified = true;
user.verifyToken = undefined;
user.verifyTokenExpiry = undefined;
await user.save();
return NextResponse.json(
{ message: "Email Verified Successfully", success: true },
{ status: 400 }
);
} catch (error: any) {
console.log({ error: error.message });
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
Login User:
In scr/app folder create a new folder api/users/login
In api/users/login folder create a new file route.ts
In route.ts file:
Install jsonwebtoken:
import { connect } from "@/dbConfig/dbConfig";
import User from "@/models/userModel";
import { NextRequest, NextResponse } from "next/server";
import bcryptjs from "bcryptjs";
import jwt from "jsonwebtoken";
connect();
export async function POST(request: NextRequest) {
try {
const reqBody = await request.json();
const { email, password } = reqBody;
const user = await User.findOne({ email });
if (!user) {
return NextResponse.json(
{ error: "User does not exist" },
{ status: 400 }
);
}
const isValidPassword = await bcryptjs.compare(password, user.password);
if (!isValidPassword) {
return NextResponse.json(
{ error: "Check your credentials" },
{ status: 400 }
);
}
const tokenData = {
id: user._id,
username: user.username,
email: user.email,
};
const token = await jwt.sign(tokenData, process.env.SECRET_TOKEN!, {
expiresIn: "1d",
});
const response = NextResponse.json({
message: "Logged In Successfully",
success: true,
});
response.cookies.set("token", token, { httpOnly: true });
return response;
} catch (error: any) {
console.log(error.message);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
Logout User:
In scr/app folder create a new folder api/users/logout
In api/users/logout folder create a new file route.ts
In route.ts file:
import { connect } from "@/dbConfig/dbConfig";
import { NextRequest, NextResponse } from "next/server";
connect();
export async function GET(request: NextRequest) {
try {
const response = NextResponse.json({
message: "Logged Out Successfully",
success: true,
});
response.cookies.set("token", "", {
httpOnly: true,
expires: new Date(0),
});
return response;
} catch (error: any) {
console.log(error.message);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
Before Get User Profile Data we have to get data from the token:
To get data from token:
In helpers folder create a another new file getDataFromToken.ts
In getDataFromToken.ts file:
import { NextRequest, NextResponse } from "next/server";
import jwt from "jsonwebtoken";
export const getDataFromToken = (request: NextRequest) => {
try {
const token = request.cookies.get("token")?.value || "";
const decodedToken: any = jwt.verify(token, process.env.SECRET_TOKEN!);
return decodedToken.id;
} catch (error: any) {
console.log(error.message);
throw new Error(error.message);
}
};
Get User Profile Data:
In scr/app folder create a new folder api/users/my-profile
In api/users/my-profile folder create a new file route.ts
In route.ts file:
import { connect } from "@/dbConfig/dbConfig";
import User from "@/models/userModel";
import { NextRequest, NextResponse } from "next/server";
import { getDataFromToken } from "@/helpers/getDataFromToken";
connect();
export async function GET(request: NextRequest) {
try {
const userId = await getDataFromToken(request);
const user = await User.findOne({ _id: userId }).select("-password");
return NextResponse.json({
message: "User found",
data: user,
});
} catch (error: any) {
console.log(error.message);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
Note: Please Check And Run Your API To Postman:
<--- Backend Code End Here --->
<--- Frontend Code Start Here --->
Register User:
To Register User:
In scr/app folder create a new folder /register
In /register folder create a new file page.tsx
In page.tsx file:
But first we need to install some packages:
- npm i axios
- npm i react-hot-toast
Note: To use Client Components, you can add the React "use client" directive at the top of a file, above your imports.
"use client";
import axios from "axios";
import Link from "next/link";
import { useRouter } from "next/navigation";
import React, { useEffect, useState } from "react";
import toast, { Toaster } from "react-hot-toast";
export default function page() {
const router = useRouter();
const [user, setUser] = useState({
username: "",
email: "",
password: "",
});
const [disabledButton, setDisableButton] = useState(false);
const [loading, setLoading] = useState(false);
const onRegister = async () => {
try {
setLoading(true);
const response = await axios.post("/api/users/register", user);
console.log(response.data);
router.push("/login");
// toast.success(response.message);
} catch (error: any) {
setLoading(false);
console.log(error.response.data.error);
toast.error(error.response.data.error);
}
};
useEffect(() => {
if (
user.username.length > 0 &&
user.email.length > 0 &&
user.password.length > 0
) {
setDisableButton(false);
} else {
setDisableButton(true);
}
}, [user]);
return (
<>
<Toaster />
{loading ? (
<div className="flex justify-center items-center min-h-screen">
<div className="w-16 h-16 border-4 border-blue-500 border-solid rounded-full animate-spin border-t-transparent border-b-transparent"></div>
</div>
) : (
<>
<div className="flex justify-center items-center min-h-screen bg-gray-100">
<form className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full">
<h2 className="text-2xl font-bold text-center mb-6">Register</h2>
<div className="mb-4">
<label
htmlFor="username"
className="block text-gray-700 text-sm font-bold mb-2"
>
Username
</label>
<input
type="text"
id="username"
value={user.username}
onChange={(e) =>
setUser({ ...user, username: e.target.value })
}
className="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-blue-500"
placeholder="Enter your username"
required
/>
</div>
<div className="mb-4">
<label
htmlFor="email"
className="block text-gray-700 text-sm font-bold mb-2"
>
Email
</label>
<input
type="email"
id="email"
value={user.email}
onChange={(e) => setUser({ ...user, email: e.target.value })}
className="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-blue-500"
placeholder="Enter your email"
required
/>
</div>
<div className="mb-6">
<label
htmlFor="password"
className="block text-gray-700 text-sm font-bold mb-2"
>
Password
</label>
<input
type="password"
id="password"
value={user.password}
onChange={(e) =>
setUser({ ...user, password: e.target.value })
}
className="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-blue-500"
placeholder="Enter your password"
required
/>
</div>
<button
onClick={onRegister}
className="w-full bg-blue-500 text-white py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:bg-blue-700"
disabled={disabledButton}
>
Register
</button>
<div className="mt-4 text-center">
<span className="text-gray-600">
Already have an account?{" "}
<Link href="/login" className="text-blue-500 hover:underline">
Login
</Link>
</span>
</div>
</form>
</div>
</>
)}
</>
);
}
Verify User:
To Verify User:
In scr/app folder create a new folder /verifyemail
In /verifyemail folder create a new file page.tsx
In page.tsx file:
"use client";
import axios from "axios";
import Link from "next/link";
import React, { useEffect, useState } from "react";
import toast from "react-hot-toast";
export default function page() {
const [token, setToken] = useState("");
const [verified, setVerified] = useState(false);
const [error, setError] = useState(false);
const verifyUserEmail = async () => {
try {
await axios.post("/api/users/verifyemail", { token });
setVerified(true);
} catch (error: any) {
setError(true);
console.log(error.response.data.error);
toast.error(error.response.data.error);
}
};
useEffect(() => {
setError(false);
const urlToken = window.location.search.split("=")[1];
setToken(urlToken || "");
}, []);
useEffect(() => {
setError(false);
if (token.length > 0) {
verifyUserEmail();
}
}, [token]);
console.log(token);
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full">
<h2 className="text-2xl font-bold text-gray-800 mb-4 text-center">
Verify Email
</h2>
<div className="my-5">Token: {token ? `${token}` : "No Token"}</div>
{verified && (
<div>
<h2>Verified</h2>
<Link href="/login">Login</Link>
</div>
)}
{error && (
<div>
<h2>Error</h2>
</div>
)}
</div>
</div>
);
}
Login User:
To Login User:
In scr/app folder create a new folder /login
In /login folder create a new file page.tsx
In page.tsx file:
"use client";
import axios from "axios";
import Link from "next/link";
import { useRouter } from "next/navigation";
import React, { useEffect, useState } from "react";
import { toast, Toaster } from "react-hot-toast";
export default function page() {
const router = useRouter();
const [user, setUser] = useState({
email: "",
password: "",
});
const [disabledButton, setDisableButton] = useState(false);
const [loading, setLoading] = useState(false);
const onLogin = async () => {
try {
setLoading(true);
const response = await axios.post("/api/users/login", user);
console.log(response.data);
toast.success(response.data.message);
router.push("/my-profile");
} catch (error: any) {
setLoading(false);
console.log(error.response.data);
toast.error(error.response.data.error);
}
};
useEffect(() => {
if (user.email.length > 0 && user.password.length > 0) {
setDisableButton(false);
} else {
setDisableButton(true);
}
}, [user]);
return (
<>
<Toaster />
{loading ? (
<>
<div className="flex justify-center items-center min-h-screen">
<div className="w-16 h-16 border-4 border-blue-500 border-solid rounded-full animate-spin border-t-transparent border-b-transparent"></div>
</div>
</>
) : (
<>
<div className="flex justify-center items-center min-h-screen bg-gray-100">
<form className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full">
<h2 className="text-2xl font-bold text-center mb-6">Login</h2>
<div className="mb-4">
<label
htmlFor="email"
className="block text-gray-700 text-sm font-bold mb-2"
>
Email
</label>
<input
type="email"
id="email"
value={user.email}
onChange={(e) => setUser({ ...user, email: e.target.value })}
className="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-blue-500"
placeholder="Enter your email"
required
/>
</div>
<div className="mb-6">
<label
htmlFor="password"
className="block text-gray-700 text-sm font-bold mb-2"
>
Password
</label>
<input
type="password"
id="password"
value={user.password}
onChange={(e) =>
setUser({ ...user, password: e.target.value })
}
className="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-blue-500"
placeholder="Enter your password"
required
/>
</div>
<button
onClick={onLogin}
className="w-full bg-blue-500 text-white py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:bg-blue-700"
disabled={disabledButton}
>
Login
</button>
<div className="mt-4 text-center">
<span className="text-gray-600">
Don't have an account?{" "}
<Link
href="/register"
className="text-blue-500 hover:underline"
>
Register
</Link>
</span>
</div>
</form>
</div>
</>
)}
</>
);
}
User Profile:
To User Profile:
In scr/app folder create a new folder /my-profile
In /my-profile folder create a new file page.tsx
In page.tsx file:
"use client";
import React, { useEffect, useState } from "react";
import axios from "axios";
import toast from "react-hot-toast";
import { useRouter } from "next/navigation";
export default function page() {
const router = useRouter();
const [data, setData] = useState("");
const getUserDetails = async () => {
try {
const response = await axios.get("/api/users/my-profile");
setData(response.data.data);
toast.success(response.data.message);
router.push("/my-profile");
} catch (error: any) {
toast.error(error.response.data.error);
}
};
useEffect(() => {
getUserDetails();
}, []);
const logout = async () => {
try {
await axios.get("/api/users/logout");
toast.success("Logged Out");
router.push("/login");
} catch (error: any) {
toast.error(error.response.data.error);
}
};
return (
<>
{data === "" ? (
<>
<h1 className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
No Data Found
</h1>
</>
) : (
<>
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<div className="bg-white shadow-lg rounded-lg p-6 w-80">
<div className="flex flex-col items-center">
<img
className="w-24 h-24 rounded-full object-cover mb-4"
src="https://via.placeholder.com/150"
alt="Profile"
/>
<h2 className="text-xl font-semibold mb-2">{data.username}</h2>
<p className="text-gray-600 mb-4">Full Stack Developer</p>
</div>
<div className="text-center">
<p className="text-gray-800 mb-2">Email: {data.email}</p>
</div>
<button
onClick={logout}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Logout
</button>
</div>
</div>
</>
)}
</>
);
}
<--- Frontend Code End Here --->
Protect Routes:
To Protect Routes:
In scr/ folder create a new file middleware.tsx
In middleware.tsx file:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const path = request.nextUrl.pathname;
const isPublicPath =
path === "/login" || path === "/register" || path === "/verifyemail";
const token = request.cookies.get("token")?.value || "";
if (isPublicPath && token) {
return NextResponse.redirect(new URL("/", request.url));
}
if (!isPublicPath && !token) {
return NextResponse.redirect(new URL("/login", request.url));
}
}
export const config = {
matcher: ["/", "/login", "/register", "/verifyemail", "/my-profile"],
};
Comments
Post a Comment