article

Wednesday, January 17, 2024

Next.js 14 MongoDB User Registration

Next.js 14 MongoDB User Registration

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/nextjs14
app\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

Related Post