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
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 | 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 | //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> ) } |
1 2 3 4 5 6 7 8 9 10 11 12 | //app\register\page.tsx import { Metadata } from 'next' import Form from './Form' export const metadata: Metadata = { title: 'Register' , } export default async function Register() { return <form> } </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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | //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 Form |
1 2 3 4 5 6 7 8 9 10 11 | //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 /> } |
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 | //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 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 | //app\api\auth\register\Form.ts import { NextRequest } from 'next/server' 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, } ) } } |
1 2 3 4 5 6 7 8 9 10 11 12 | //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 dbConnect |
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 | //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 UserModel |