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
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))',
      },
    },
  },
  plugins: [require('daisyui')], //https://www.npmjs.com/package/react-daisyui
}
export default config
.env
1
MONGODB_URI=mongodb://127.0.0.1/nextjs14
app\layout.tsx
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>
  )
}
app\register\page.tsx
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>
app\register\Form.tsx
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
app\signin\page.tsx
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 />
}
app\signin\Form.tsx
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
app\api\auth\register\route.ts
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 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
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
lib\models\UserModel.ts
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
run C:\nextjs>npm run dev

Related Post