├── .eslintrc.json
├── .env.template
├── src
├── app
│ ├── favicon.ico
│ ├── about
│ │ └── page.tsx
│ ├── page.tsx
│ ├── Providers.tsx
│ ├── layout.tsx
│ ├── globals.css
│ ├── dashboard
│ │ └── profile
│ │ │ └── page.tsx
│ ├── api
│ │ └── auth
│ │ │ ├── [...nextauth]
│ │ │ └── route.ts
│ │ │ └── signup
│ │ │ └── route.ts
│ ├── login
│ │ └── page.tsx
│ └── register
│ │ └── page.tsx
├── middleware.ts
├── libs
│ └── mongodb.ts
├── models
│ └── user.ts
└── components
│ └── Navbar.jsx
├── next.config.js
├── postcss.config.js
├── .gitignore
├── tailwind.config.js
├── public
├── vercel.svg
└── next.svg
├── tsconfig.json
├── package.json
└── README.md
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.env.template:
--------------------------------------------------------------------------------
1 | MONGODB_URI=mongodb://localhost/nextmongo
2 | NEXTAUTH_SECRET=secret
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fazt/next-auth-credentials-mongodb/HEAD/src/app/favicon.ico
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/about/page.tsx:
--------------------------------------------------------------------------------
1 | function AboutPage() {
2 | return (
3 |
AboutPage
4 | )
5 | }
6 |
7 | export default AboutPage
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 | export { default } from "next-auth/middleware";
2 |
3 | export const config = { matcher: ["/", "/dashboard/:path*"] };
4 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | function HomePage() {
2 | // const { data: session, status } = useSession({
3 | // required: true,
4 | // });
5 | // console.log(session, status);
6 |
7 | return HomePage
;
8 | }
9 |
10 | export default HomePage;
11 |
--------------------------------------------------------------------------------
/src/app/Providers.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { SessionProvider } from "next-auth/react";
4 |
5 | interface Props {
6 | children: React.ReactNode;
7 | }
8 |
9 | export default function Providers({ children }: Props) {
10 | return {children};
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
5 | './src/components/**/*.{js,ts,jsx,tsx,mdx}',
6 | './src/app/**/*.{js,ts,jsx,tsx,mdx}',
7 | ],
8 | theme: {
9 | extend: {
10 | backgroundImage: {
11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
12 | 'gradient-conic':
13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
14 | },
15 | },
16 | },
17 | plugins: [],
18 | }
19 |
--------------------------------------------------------------------------------
/src/libs/mongodb.ts:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const { MONGODB_URI } = process.env;
4 |
5 | if (!MONGODB_URI) {
6 | throw new Error("MONGODB_URI must be defined");
7 | }
8 |
9 | export const connectDB = async () => {
10 | try {
11 | const { connection } = await mongoose.connect(MONGODB_URI);
12 | if (connection.readyState === 1) {
13 | console.log("MongoDB Connected");
14 | return Promise.resolve(true);
15 | }
16 | } catch (error) {
17 | console.error(error);
18 | return Promise.reject(error);
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import Providers from "./Providers";
2 | import Navbar from "../components/Navbar";
3 | import "./globals.css";
4 |
5 | export const metadata = {
6 | title: "Create Next App",
7 | description: "Generated by create next app",
8 | };
9 |
10 | export default function RootLayout({
11 | children,
12 | }: {
13 | children: React.ReactNode;
14 | }) {
15 | return (
16 |
17 |
18 |
19 |
20 | {children}
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./src/*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-auth-mongodb",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@types/node": "20.3.0",
13 | "@types/react": "18.2.11",
14 | "@types/react-dom": "18.2.4",
15 | "autoprefixer": "10.4.14",
16 | "bcryptjs": "^2.4.3",
17 | "eslint": "8.42.0",
18 | "eslint-config-next": "13.4.5",
19 | "mongoose": "^7.2.3",
20 | "next": "13.4.5",
21 | "next-auth": "^4.22.1",
22 | "postcss": "8.4.24",
23 | "react": "18.2.0",
24 | "react-dom": "18.2.0",
25 | "tailwindcss": "3.3.2",
26 | "typescript": "5.1.3"
27 | },
28 | "devDependencies": {
29 | "@types/bcryptjs": "^2.4.2"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/models/user.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const UserSchema = new Schema(
4 | {
5 | email: {
6 | type: String,
7 | unique: true,
8 | required: [true, "Email is required"],
9 | match: [
10 | /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/,
11 | "Email is invalid",
12 | ],
13 | },
14 | password: {
15 | type: String,
16 | required: [true, "Password is required"],
17 | select: false,
18 | },
19 | fullname: {
20 | type: String,
21 | required: [true, "fullname is required"],
22 | minLength: [3, "fullname must be at least 3 characters"],
23 | maxLength: [20, "fullname must be at most 20 characters"],
24 | },
25 | },
26 | {
27 | timestamps: true,
28 | }
29 | );
30 |
31 | const User = models.User || model("User", UserSchema);
32 | export default User;
33 |
--------------------------------------------------------------------------------
/src/app/dashboard/profile/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useSession, signOut } from "next-auth/react";
3 |
4 | function ProfilePage() {
5 | const { data: session, status } = useSession();
6 |
7 | console.log(session, status);
8 |
9 | return (
10 |
11 |
Profile
12 |
13 |
14 | {JSON.stringify(
15 | {
16 | session,
17 | status,
18 | },
19 | null,
20 | 2
21 | )}
22 |
23 |
24 |
32 |
33 | );
34 | }
35 |
36 | export default ProfilePage;
37 |
--------------------------------------------------------------------------------
/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { getServerSession } from "next-auth";
3 |
4 | async function Navbar() {
5 | const session = await getServerSession();
6 |
7 | return (
8 |
37 | );
38 | }
39 |
40 | export default Navbar;
41 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | ```
14 |
15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
16 |
17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
18 |
19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { connectDB } from "@/libs/mongodb";
2 | import User from "@/models/user";
3 | import NextAuth from "next-auth";
4 | import CredentialsProvider from "next-auth/providers/credentials";
5 | import bcrypt from "bcryptjs";
6 |
7 | const handler = NextAuth({
8 | providers: [
9 | CredentialsProvider({
10 | name: "Credentials",
11 | id: "credentials",
12 | credentials: {
13 | email: { label: "Email", type: "text", placeholder: "jsmith" },
14 | password: { label: "Password", type: "password" },
15 | },
16 | async authorize(credentials) {
17 | await connectDB();
18 | const userFound = await User.findOne({
19 | email: credentials?.email,
20 | }).select("+password");
21 |
22 | if (!userFound) throw new Error("Invalid credentials");
23 |
24 | const passwordMatch = await bcrypt.compare(
25 | credentials!.password,
26 | userFound.password
27 | );
28 |
29 | if (!passwordMatch) throw new Error("Invalid credentials");
30 |
31 | console.log(userFound);
32 |
33 | return userFound;
34 | },
35 | }),
36 | ],
37 | pages: {
38 | signIn: "/login",
39 | },
40 | session: {
41 | strategy: "jwt",
42 | },
43 | callbacks: {
44 | async jwt({ token, user }) {
45 | if (user) token.user = user;
46 | return token;
47 | },
48 | async session({ session, token }) {
49 | session.user = token.user as any;
50 | return session;
51 | },
52 | },
53 | });
54 |
55 | export { handler as GET, handler as POST };
56 |
--------------------------------------------------------------------------------
/src/app/api/auth/signup/route.ts:
--------------------------------------------------------------------------------
1 | import { connectDB } from "@/libs/mongodb";
2 | import User from "@/models/user";
3 | import { NextResponse } from "next/server";
4 | import bcrypt from "bcryptjs";
5 | import mongoose from "mongoose";
6 |
7 | export async function POST(request: Request) {
8 | try {
9 | await connectDB();
10 |
11 | const { fullname, email, password } = await request.json();
12 |
13 | if (password < 6)
14 | return NextResponse.json(
15 | { message: "Password must be at least 6 characters" },
16 | { status: 400 }
17 | );
18 |
19 | const userFound = await User.findOne({ email });
20 |
21 | if (userFound)
22 | return NextResponse.json(
23 | {
24 | message: "Email already exists",
25 | },
26 | {
27 | status: 409,
28 | }
29 | );
30 |
31 | const hashedPassword = await bcrypt.hash(password, 12);
32 |
33 | const user = new User({
34 | fullname,
35 | email,
36 | password: hashedPassword,
37 | });
38 |
39 | const savedUser = await user.save();
40 | console.log(savedUser);
41 |
42 | return NextResponse.json(
43 | {
44 | fullname,
45 | email,
46 | createdAt: savedUser.createdAt,
47 | updatedAt: savedUser.updatedAt,
48 | },
49 | { status: 201 }
50 | );
51 | } catch (error) {
52 | if (error instanceof mongoose.Error.ValidationError) {
53 | return NextResponse.json(
54 | {
55 | message: error.message,
56 | },
57 | {
58 | status: 400,
59 | }
60 | );
61 | }
62 | return NextResponse.error();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/app/login/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { FormEvent, useState } from "react";
3 | import { AxiosError } from "axios";
4 | import { signIn } from "next-auth/react";
5 | import { useRouter } from "next/navigation";
6 |
7 | function Signin() {
8 | const [error, setError] = useState("");
9 | const router = useRouter();
10 |
11 | const handleSubmit = async (event: FormEvent) => {
12 | event.preventDefault();
13 | const formData = new FormData(event.currentTarget);
14 | const res = await signIn("credentials", {
15 | email: formData.get("email"),
16 | password: formData.get("password"),
17 | redirect: false,
18 | });
19 |
20 | if (res?.error) setError(res.error as string);
21 |
22 | if (res?.ok) return router.push("/dashboard/profile");
23 | };
24 |
25 | return (
26 |
55 | );
56 | }
57 |
58 | export default Signin;
59 |
--------------------------------------------------------------------------------
/src/app/register/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { FormEvent, useState } from "react";
3 | import axios, { AxiosError } from "axios";
4 | import { signIn } from "next-auth/react";
5 | import { useRouter } from "next/navigation";
6 |
7 | function Signup() {
8 | const [error, setError] = useState();
9 | const router = useRouter();
10 |
11 | const handleSubmit = async (event: FormEvent) => {
12 | event.preventDefault();
13 | try {
14 | const formData = new FormData(event.currentTarget);
15 | const signupResponse = await axios.post("/api/auth/signup", {
16 | email: formData.get("email"),
17 | password: formData.get("password"),
18 | fullname: formData.get("fullname"),
19 | });
20 | console.log(signupResponse);
21 | const res = await signIn("credentials", {
22 | email: signupResponse.data.email,
23 | password: formData.get("password"),
24 | redirect: false,
25 | });
26 |
27 | if (res?.ok) return router.push("/dashboard/profile");
28 | } catch (error) {
29 | console.log(error);
30 | if (error instanceof AxiosError) {
31 | const errorMessage = error.response?.data.message;
32 | setError(errorMessage);
33 | }
34 | }
35 | };
36 |
37 | return (
38 |
72 | );
73 | }
74 |
75 | export default Signup;
76 |
--------------------------------------------------------------------------------