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 Form
app\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 Form
app\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 dbConnect
lib\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 UserModel
run C:\nextjs>npm run dev
