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
NextAuth.js
npm install next-auth
https://www.npmjs.com/package/next-auth/v/4.23.1
NextAuth.js is a complete open source authentication solution for Next.js applications.
bcrypt.js
npm install bcryptjs
https://www.npmjs.com/package/bcryptjs
edit tailwind.config.js Add daisyui to plugins
edit tailwind.config.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //edit tailwind.config.ts 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 2 | MONGODB_URI=mongodb: //127.0.0.1/nextjs14 NEXTAUTH_SECRET=cairocoders |
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 | //app\layout.js import Navbar from "@/components/Navbar" ; import "./globals.css" ; import type { Metadata } from "next" ; import { Inter } from "next/font/google" ; import { getServerSession } from "next-auth" ; import SessionProvider from "@/utils/SessionProvider" ; const inter = Inter({ subsets: [ "latin" ] }); export const metadata: Metadata = { title: "Next.js 14 Login Register MongoDB Next-Auth Login Authentication Daisyui Tailwind" , description: "Generated by create next app" , }; export default async function RootLayout({ children, }: { children: React.ReactNode; }) { const session = await getServerSession(); return ( <html lang= "en" > <body className={inter.className}> <SessionProvider session={session}> <div className= "container mx-auto px-4" > <Navbar /> {children} </div> </SessionProvider> </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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | //components\Navbar.jsx "use client" ; import React from "react" ; import Link from "next/link" ; import { signOut, useSession } from "next-auth/react" ; const Navbar = () => { const { data: session }: any = useSession(); return ( <div className= "navbar bg-base-100" > <div className= "flex-1" > <a className= "btn btn-ghost text-xl" >Cairocoders</a> <Link href= "/" > Home </Link> </div> <div className= "flex-none" > {!session ? ( <> <Link href= "/login" className= "btn btn-primary" > Login </Link> <Link href= "/register" className= "btn btn-secondary ml-2" > Register </Link> </> ) : ( <> {session.user?.email} <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> <button onClick={() => { signOut(); }} > Logout </button> </li> </ul> </div> </> )} </div> </div> ); }; export default Navbar; |
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 | //models\User.js import mongoose from "mongoose" ; const { Schema } = mongoose; const userSchema = new Schema( { name: { type: String, required: true, }, email: { type: String, unique: true, required: true, }, password: { type: String, required: false, }, }, { timestamps: true } ); export default mongoose.models.User || mongoose.model( "User" , userSchema); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //utils\db.js import mongoose from "mongoose" ; const connect = async () => { if (mongoose.connections[0].readyState) return ; try { await mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true, }); console.log( "Mongo Connection successfully established." ); } catch (error) { throw new Error( "Error connecting to Mongoose" ); } }; export default connect; |
1 2 3 4 5 6 7 8 9 10 | //utils\SessionProvider.tsx "use client" ; import React from "react" ; import { SessionProvider } from "next-auth/react" ; //https://www.npmjs.com/package/next-auth/v/4.23.1 const AuthProvider = ({ children }: any) => { return <sessionprovider>{children}</sessionprovider>; }; export default AuthProvider; |
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 | //app\pag.tsx export default function Home() { return ( <main> <div className= "carousel w-full" > <div id= "item1" className= "carousel-item w-full" > <img src= "https://daisyui.com/images/stock/photo-1625726411847-8cbb60cc71e6.jpg" className= "w-full" /> </div> <div id= "item2" className= "carousel-item w-full" > <img src= "https://daisyui.com/images/stock/photo-1609621838510-5ad474b7d25d.jpg" className= "w-full" /> </div> <div id= "item3" className= "carousel-item w-full" > <img src= "https://daisyui.com/images/stock/photo-1414694762283-acccc27bca85.jpg" className= "w-full" /> </div> <div id= "item4" className= "carousel-item w-full" > <img src= "https://daisyui.com/images/stock/photo-1665553365602-b2fb8e5d1707.jpg" className= "w-full" /> </div> </div> <div className= "flex justify-center w-full py-2 gap-2" > <a href= "#item1" className= "btn btn-xs" >1</a> <a href= "#item2" className= "btn btn-xs" >2</a> <a href= "#item3" className= "btn btn-xs" >3</a> <a href= "#item4" className= "btn btn-xs" >4</a> </div> </main> ); } |
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 | //app\login\page.tsx "use client" ; import React, { useEffect, useState } from "react" ; import Link from "next/link" ; import { signIn, useSession } from "next-auth/react" ; import { useRouter } from "next/navigation" ; const Login = () => { const router = useRouter(); const [error, setError] = useState( "" ); const { data: session, status: sessionStatus } = useSession(); useEffect(() => { if (sessionStatus === "authenticated" ) { router.replace( "/dashboard" ); } }, [sessionStatus, router]); const isValidEmail = (email: string) => { const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; return emailRegex.test(email); }; const handleSubmit = async (e: any) => { e.preventDefault(); const email = e.target[0].value; const password = e.target[1].value; if (!isValidEmail(email)) { setError( "Email is invalid" ); return ; } if (!password || password.length < 8) { setError( "Password is invalid" ); return ; } const res = await signIn( "credentials" , { redirect: false, email, password, }); if (res?.error) { setError( "Invalid email or password" ); if (res?.url) router.replace( "/dashboard" ); } else { setError( "" ); } }; if (sessionStatus === "loading" ) { return <h1>Loading...</h1>; } return ( sessionStatus !== "authenticated" && ( <div className= "justify-center mt-16" > <div className= "w-full p-6 m-auto bg-white rounded-md shadow-md lg:max-w-lg" > <h1 className= "text-3xl font-semibold text-center text-purple-700" >Login</h1> <form onSubmit={handleSubmit} className= "space-y-4" > <div> <label className= "label" > <span className= "text-base label-text" >Email</span> </label> <input type= "text" placeholder= "Email Address" required className= "w-full input input-bordered input-primary" /> </div> <div> <label className= "label" > <span className= "text-base label-text" >Password</span> </label> <input type= "password" placeholder= "Enter Password" required className= "w-full input input-bordered input-primary" /> </div> <a href= "#" className= "text-xs text-gray-600 hover:underline hover:text-blue-600" >Forget Password?</a> <div> <button type= "submit" className= "btn btn-primary" > { " " } Sign In </button> <p className= "text-red-600 text-[16px] mb-4" >{error && error}</p> </div> </form> <Link className= "block text-center text-blue-500 hover:underline mt-2" href= "/register" > Register Here </Link> </div> </div> ) ); }; export default Login; |
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 | //app\register\page.tsx "use client" ; import React, { useEffect, useState } from "react" ; import Link from "next/link" ; import { useRouter } from "next/navigation" ; import { useSession } from "next-auth/react" ; const Register = () => { const [error, setError] = useState( "" ); const router = useRouter(); const { data: session, status: sessionStatus } = useSession(); useEffect(() => { if (sessionStatus === "authenticated" ) { router.replace( "/dashboard" ); } }, [sessionStatus, router]); const isValidEmail = (email: string) => { const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; return emailRegex.test(email); }; const handleSubmit = async (e: any) => { e.preventDefault(); const name = e.target[0].value; const email = e.target[1].value; const password = e.target[2].value; if (!isValidEmail(email)) { setError( "Email is invalid" ); return ; } if (!password || password.length < 8) { setError( "Password is invalid" ); return ; } try { const res = await fetch( "/api/register" , { method: "POST" , headers: { "Content-Type" : "application/json" , }, body: JSON.stringify({ name, email, password, }), }); if (res.status === 400) { setError( "This email is already registered" ); } if (res.status === 200) { setError( "" ); router.push( "/login" ); } } catch (error) { setError( "Error, try again" ); console.log(error); } }; if (sessionStatus === "loading" ) { return <h1>Loading...</h1>; } return ( sessionStatus !== "authenticated" && ( <div className= "justify-center mt-16" > <div className= "w-full p-6 m-auto bg-white rounded-md shadow-md lg:max-w-lg" > <h1 className= "text-3xl font-semibold text-center text-purple-700" >Register</h1> <form onSubmit={handleSubmit} className= "space-y-4" > <div> <label className= "label" > <span className= "text-base label-text" >Name</span> </label> <input type= "text" placeholder= "Name" required className= "w-full input input-bordered input-primary" /> </div> <div> <label className= "label" > <span className= "text-base label-text" >Email</span> </label> <input type= "text" placeholder= "Email Address" required className= "w-full input input-bordered input-primary" /> </div> <div> <label className= "label" > <span className= "text-base label-text" >Password</span> </label> <input type= "password" placeholder= "Enter Password" required className= "w-full input input-bordered input-primary" /> </div> <button type= "submit" className= "btn btn-primary" > { " " } Register </button> <p className= "text-red-600 text-[16px] mb-4" >{error && error}</p> </form> <div className= "text-center text-gray-500 mt-4" >- OR -</div> <Link className= "block text-center text-blue-500 hover:underline mt-2" href= "/login" > Login with an existing account </Link> </div> </div> ) ); }; export default Register; |
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\api\register\route.ts import User from "@/models/User" ; import connect from "@/utils/db" ; import { NextResponse } from "next/server" ; export const POST = async (request: any) => { const { name, email, password } = await request.json(); await connect(); const existingUser = await User.findOne({ email }); if (existingUser) { return new NextResponse( "Email is already in use" , { status: 400 }); } const hashedPassword = await bcrypt.hash(password, 5); const newUser = new User({ name, email, password: hashedPassword, }); try { await newUser.save(); return new NextResponse( "user is registered" , { status: 200 }); } catch (err: any) { return new NextResponse(err, { status: 500, }); } }; |
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 | //app\api\auth\[...nextauth]\route.ts import NextAuth from "next-auth" ; import { Account, User as AuthUser } from "next-auth" ; import CredentialsProvider from "next-auth/providers/credentials" ; import bcrypt from "bcryptjs" ; import User from "@/models/User" ; import connect from "@/utils/db" ; export const authOptions: any = { // Configure one or more authentication providers secret: process.env.NEXTAUTH_SECRET, providers: [ CredentialsProvider({ id: "credentials" , name: "Credentials" , credentials: { email: { label: "Email" , type: "text" }, password: { label: "Password" , type: "password" }, }, async authorize(credentials: any) { await connect(); try { const user = await User.findOne({ email: credentials.email }); if (user) { const isPasswordCorrect = await bcrypt.compare( credentials.password, user.password ); if (isPasswordCorrect) { return user; } } } catch (err: any) { throw new Error(err); } }, }), ], callbacks: { async signIn({ user, account }: { user: AuthUser; account: Account }) { if (account?.provider == "credentials" ) { return true; } }, }, }; export const handler = NextAuth(authOptions); export { handler as GET, handler as POST }; |