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.ts Add daisyui to plugins
edit tailwind.config.ts
//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))', }, }, }, plugins: [require('daisyui')], //https://www.npmjs.com/package/react-daisyui } export default config.env
MONGODB_URI=mongodb://127.0.0.1/nextjs14app\layout.tsx
//app\layout.tsx import type { Metadata } from 'next' import { Inter } from 'next/font/google' import './globals.css' const inter = Inter({ subsets: ['latin'] }) export const metadata: Metadata = { title: 'Next.js 14 MongoDB User Registration', description: 'Generated by create next app', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body className={inter.className}>{children}</body> </html> ) }app\register\page.tsx
//app\register\page.tsx import { Metadata } from 'next' import Form from './Form' export const metadata: Metadata = { title: 'Register', } export default async function Register() { return} app\register\Form.tsx
//app\register\Form.tsx 'use client' import Link from 'next/link' import { useRouter, useSearchParams } from 'next/navigation' import { useEffect } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' //npm install react-hook-form https://www.npmjs.com/package/react-hook-form import toast from 'react-hot-toast' type Inputs = { name: string email: string password: string confirmPassword: string } const Form = () => { const params = useSearchParams() const router = useRouter() let callbackUrl = params.get('callbackUrl') || '/' const { register, handleSubmit, getValues, formState: { errors, isSubmitting }, } = useForm<Inputs>({ defaultValues: { name: '', email: '', password: '', confirmPassword: '', }, }) const formSubmit: SubmitHandler<Inputs> = async (form) => { const { name, email, password } = form try { const res = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name, email, password, }), }) console.log(res); console.log("Success login"); if (res.ok) { return router.push( `/signin?callbackUrl=${callbackUrl}&success=Account has been created` ) } else { const data = await res.json() throw new Error(data.message) } } catch (err: any) { const error = err.message && err.message.indexOf('E11000') === 0 ? 'Email is duplicate' : err.message toast.error(error || 'error') } } return ( <div className="mx-auto max-w-2xl lg:max-w-7xl"> <div className="flex justify-between items-center"> <h1 className="font-bold py-10 text-2xl">Next.js 14 MongoDB User Registration</h1> </div> <div className="max-w-sm mx-auto card bg-base-300 my-4"> <div className="card-body"> <h1 className="card-title">Register</h1> <form onSubmit={handleSubmit(formSubmit)}> <div className="my-2"> <label className="label" htmlFor="name"> Name </label> <input type="text" id="name" {...register('name', { required: 'Name is required', })} className="input input-bordered w-full max-w-sm" /> {errors.name?.message && ( <div className="text-error">{errors.name.message}</div> )} </div> <div className="my-2"> <label className="label" htmlFor="email"> Email </label> <input type="text" id="email" {...register('email', { required: 'Email is required', pattern: { value: /[a-z0-9]+@[a-z]+\.[a-z]{2,3}/, message: 'Email is invalid', }, })} className="input input-bordered w-full max-w-sm" /> {errors.email?.message && ( <div className="text-error"> {errors.email.message}</div> )} </div> <div className="my-2"> <label className="label" htmlFor="password"> Password </label> <input type="password" id="password" {...register('password', { required: 'Password is required', })} className="input input-bordered w-full max-w-sm" /> {errors.password?.message && ( <div className="text-error">{errors.password.message}</div> )} </div> <div className="my-2"> <label className="label" htmlFor="confirmPassword"> Confirm Password </label> <input type="password" id="confirmPassword" {...register('confirmPassword', { required: 'Confirm Password is required', validate: (value) => { const { password } = getValues() return password === value || 'Passwords should match!' }, })} className="input input-bordered w-full max-w-sm" /> {errors.confirmPassword?.message && ( <div className="text-error">{errors.confirmPassword.message}</div> )} </div> <div className="my-2"> <button type="submit" disabled={isSubmitting} className="btn btn-primary w-full" > {isSubmitting && ( <span className="loading loading-spinner"></span> )} Register </button> </div> </form> <div className="divider"> </div> <div> Already have an account?{' '} <Link className="link" href={`/signin?callbackUrl=${callbackUrl}`}> Login </Link> </div> </div> </div> </div> ) } export default Formapp\signin\page.tsx
//app\signin\page.tsx import { Metadata } from 'next' import Form from './Form' export const metadata: Metadata = { title: 'Sign in', } export default async function Signin() { return <Form /> }app\signin\Form.tsx
//app\signin\Form.tsx 'use client' import Link from 'next/link' const Form = () => { return ( <div className="max-w-sm mx-auto card bg-base-300"> <div className="card-body"> <h1 className="card-title">Sign in</h1> <form> <div className="my-2"> <label className="label" htmlFor="email"> Email </label> <input type="text" id="email" className="input input-bordered w-full max-w-sm" /> </div> <div className="my-2"> <label className="label" htmlFor="password"> Password </label> <input type="password" id="password" className="input input-bordered w-full max-w-sm" /> </div> <div className="my-4"> <button type="submit" className="btn btn-primary w-full" > Sign in </button> </div> </form> <div> <Link className="link" href={'/register'}> Register </Link> </div> </div> </div> ) } export default Formapp\api\auth\register\route.ts
//app\api\auth\register\Form.ts import { NextRequest } from 'next/server' import bcrypt from 'bcryptjs' //npm i bcryptjs https://www.npmjs.com/package/bcryptjs import dbConnect from '@/lib/dbConnect' import UserModel from '@/lib/models/UserModel' export const POST = async (request: NextRequest) => { const { name, email, password } = await request.json() console.log(name); await dbConnect() const hashedPassword = await bcrypt.hash(password, 5) const newUser = new UserModel({ name, email, password: hashedPassword, }) try { await newUser.save() return Response.json( { message: 'User has been created' }, { status: 201, } ) } catch (err: any) { return Response.json( { message: err.message }, { status: 500, } ) } }lib\dbConnect.ts
//lib\dbConnect.ts import mongoose from 'mongoose' async function dbConnect() { try { await mongoose.connect(process.env.MONGODB_URI!) } catch (error) { throw new Error('Connection failed!') } } export default dbConnectlib\models\UserModel.ts
//lib\models\UserModel.ts import mongoose from 'mongoose' export type User = { _id: string name: string email: string isAdmin: boolean } const UserSchema = new mongoose.Schema( { name: { type: String, required: true, }, email: { type: String, required: true, unique: true, }, password: { type: String, required: true, }, isAdmin: { type: Boolean, required: true, default: false }, }, { timestamps: true } ) const UserModel = mongoose.models?.User || mongoose.model('User', UserSchema) export default UserModelrun C:\nextjs>npm run dev