Building a Simple Login and Signup System with Next.js and MongoDB (Mongoose)

 

Authentication is an essential part of most web applications. In this guide, you will build a complete login and signup system using Next.js, MongoDB, and Mongoose.



This version includes clear comments inside the code so you can understand what each line does.


What You Will Build

  • Signup page

  • Login page

  • Protected dashboard

  • MongoDB database connection

  • Secure password storage


Step 1: Install Dependencies

npm install mongoose bcrypt

Step 2: Project Structure

app/
 ├─ login/page.js
 ├─ signup/page.js
 ├─ dashboard/page.js
 ├─ actions/auth.js
 ├─ lib/db.js
 ├─ models/User.js

Step 3: Database Connection

// app/lib/db.js

import mongoose from "mongoose"

// Function to connect to MongoDB
export async function connectDB() {

  // If already connected, do nothing
  if (mongoose.connection.readyState >= 1) return

  // Otherwise, connect to MongoDB using environment variable
  return mongoose.connect(process.env.MONGODB_URI)
}

Step 4: User Model

// app/models/User.js

import mongoose from "mongoose"

// Define structure of user document
const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: true,     // Email is required
    unique: true,       // No duplicate emails allowed
  },
  password: {
    type: String,
    required: true,     // Password is required
  },
})

// Export model (avoid re-creating model in dev)
export default mongoose.models.User || mongoose.model("User", UserSchema)

Step 5: Authentication Logic (Server Actions)

// app/actions/auth.js

"use server" // This ensures code runs on server only

import { connectDB } from "../lib/db"
import User from "../models/User"
import bcrypt from "bcrypt"
import { cookies } from "next/headers"
import { redirect } from "next/navigation"

// ---------------- SIGNUP ----------------
export async function signup(formData) {

  // Get form values
  const email = formData.get("email")
  const password = formData.get("password")

  // Connect to database
  await connectDB()

  // Check if user already exists
  const existingUser = await User.findOne({ email })
  if (existingUser) {
    throw new Error("User already exists")
  }

  // Hash password before saving (important for security)
  const hashedPassword = await bcrypt.hash(password, 10)

  // Create new user in database
  await User.create({
    email,
    password: hashedPassword,
  })

  // Redirect to login page after signup
  redirect("/login")
}

// ---------------- LOGIN ----------------
export async function login(formData) {

  // Get form values
  const email = formData.get("email")
  const password = formData.get("password")

  // Connect to database
  await connectDB()

  // Find user by email
  const user = await User.findOne({ email })

  // If user not found, throw error
  if (!user) {
    throw new Error("User not found")
  }

  // Compare entered password with hashed password
  const isMatch = await bcrypt.compare(password, user.password)

  // If password does not match
  if (!isMatch) {
    throw new Error("Invalid password")
  }

  // Store user email in cookies (simple session)
  cookies().set("user", user.email)

  // Redirect to dashboard
  redirect("/dashboard")
}

// ---------------- LOGOUT ----------------
export async function logout() {

  // Remove user cookie
  cookies().delete("user")

  // Redirect to login page
  redirect("/login")
}

Step 6: Signup Page

// app/signup/page.js

import { signup } from "../actions/auth"

export default function SignupPage() {
  return (
    <form action={signup}>
      <h1>Signup</h1>

      {/* Email input */}
      <input name="email" placeholder="Email" required />

      {/* Password input */}
      <input name="password" type="password" required />

      {/* Submit button */}
      <button type="submit">Signup</button>
    </form>
  )
}

Step 7: Login Page

// app/login/page.js

import { login } from "../actions/auth"

export default function LoginPage() {
  return (
    <form action={login}>
      <h1>Login</h1>

      {/* Email input */}
      <input name="email" placeholder="Email" required />

      {/* Password input */}
      <input name="password" type="password" required />

      {/* Submit button */}
      <button type="submit">Login</button>
    </form>
  )
}

Step 8: Protected Dashboard

// app/dashboard/page.js

import { cookies } from "next/headers"
import { redirect } from "next/navigation"
import { logout } from "../actions/auth"

export default function Dashboard() {

  // Get user cookie
  const user = cookies().get("user")

  // If no user is logged in, redirect to login
  if (!user) {
    redirect("/login")
  }

  return (
    <div>
      {/* Show logged-in user's email */}
      <h1>Welcome {user.value}</h1>

      {/* Logout button */}
      <form action={logout}>
        <button type="submit">Logout</button>
      </form>
    </div>
  )
}

Step 9: Environment Variable

Create .env.local:

MONGODB_URI=your_mongodb_connection_string

How Everything Works Together

  1. User submits signup form

  2. Password is hashed and stored in MongoDB

  3. User logs in with credentials

  4. Password is verified

  5. Cookie is created for session

  6. Dashboard checks cookie before allowing access

  7. Logout removes cookie


Important Notes

This is a beginner-friendly implementation. For production use, you should:

  • Use HTTP-only secure cookies

  • Add input validation

  • Use JWT or session-based authentication

  • Implement better error handling

  • Use a dedicated authentication library


Conclusion

You now have a complete login and signup system using Next.js and MongoDB with Mongoose. The code is structured, commented, and easy to understand.

Once you are comfortable with this setup, you can expand it by adding features like user profiles, password reset, and role-based access control.

Comments