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
User submits signup form
Password is hashed and stored in MongoDB
User logs in with credentials
Password is verified
Cookie is created for session
Dashboard checks cookie before allowing access
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
Post a Comment