35% OFF
Ends14d 00:00:00
Shop
Back to Blog
nextjsmongodbmongoosejavascriptwebdev

How to Connect MongoDB to Next.js 15 with Mongoose

Pixel Anas··7 min read

Learn how to connect MongoDB to Next.js 15 with Mongoose, cache database connections, prevent model errors, handle MongoDB errors, and structure MongoDB code for production.

How to Connect MongoDB to Next.js 15 with Mongoose

Connecting MongoDB to Next.js 15 is straightforward, but building the connection correctly for a production application requires more than simply calling mongoose.connect().

In this guide, you'll learn how to connect MongoDB to Next.js 15 using Mongoose, cache database connections during development, prevent model overwrite errors, handle MongoDB errors, and structure your database code for production applications.

We'll use the Next.js App Router, TypeScript, Mongoose, and MongoDB.


The Problem With Basic Connection Code

Most tutorials show you something like this:

import mongoose from "mongoose";

export async function connectDB() {
  await mongoose.connect(process.env.MONGODB_URI!);
}

This works, but it can cause problems during development.

Next.js hot reloading can cause your application code to run repeatedly. Without connection caching, this can create unnecessary MongoDB connections and eventually exhaust your available connection limit.

That's why a reusable cached connection is a better approach.


Install Mongoose

If you haven't installed Mongoose yet:

npm install mongoose

Then add your MongoDB connection string to .env.local:

MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/dbname

Never commit .env.local to your Git repository.


The Correct MongoDB Connection Pattern

Create a database connection file:

// lib/db.ts
import mongoose from "mongoose";

const MONGODB_URI = process.env.MONGODB_URI!;

if (!MONGODB_URI) {
  throw new Error("MONGODB_URI is not defined in environment variables");
}

// Cache connection across hot reloads in development
let cached = global.mongoose as {
  conn: typeof mongoose | null;
  promise: Promise<typeof mongoose> | null;
};

if (!cached) {
  cached = global.mongoose = { conn: null, promise: null };
}

export async function connectDB() {
  // Return cached connection if it already exists
  if (cached.conn) {
    return cached.conn;
  }

  // Create a new connection if no promise exists
  if (!cached.promise) {
    cached.promise = mongoose
      .connect(MONGODB_URI, {
        bufferCommands: false,
      })
      .then((mongoose) => mongoose);
  }

  try {
    cached.conn = await cached.promise;
  } catch (error) {
    cached.promise = null;
    throw error;
  }

  return cached.conn;
}

The global.mongoose cache allows the connection to survive Next.js hot reloads during development.

In production, the cached connection can be reused instead of creating a new connection for every request.


TypeScript Fix for the Global Cache

Add this to your project:

// types/global.d.ts
import mongoose from "mongoose";

declare global {
  var mongoose: {
    conn: typeof mongoose | null;
    promise: Promise<typeof mongoose> | null;
  };
}

Without this declaration, TypeScript may complain that global.mongoose does not exist.


Recommended Project Structure

A simple Next.js 15 project using MongoDB and Mongoose can look like this:

app/
├── api/
│   └── users/
│       └── route.ts
├── lib/
│   └── db.ts
├── models/
│   └── User.ts
└── types/
    └── global.d.ts

Keeping your database connection and models separate makes them easier to reuse throughout the application.


Creating Mongoose Models Correctly

Another common mistake is defining models without checking if they already exist:

// ❌ This can throw an error during hot reload
const User = mongoose.model("User", UserSchema);

// ✅ Reuse the existing model when available
const User = mongoose.models.User || mongoose.model("User", UserSchema);

Here's a complete model example:

// models/User.ts
import mongoose, { Document, Model } from "mongoose";

export interface IUser extends Document {
  name: string;
  email: string;
  role: "admin" | "user";
  createdAt: Date;
}

const UserSchema = new mongoose.Schema<IUser>(
  {
    name: {
      type: String,
      required: [true, "Name is required"],
      trim: true,
      minlength: 2,
      maxlength: 50,
    },
    email: {
      type: String,
      required: [true, "Email is required"],
      unique: true,
      lowercase: true,
      trim: true,
    },
    role: {
      type: String,
      enum: ["admin", "user"],
      default: "user",
    },
  },
  {
    timestamps: true,
  },
);

// Index for faster email lookups
UserSchema.index({ email: 1 });

const User: Model<IUser> =
  mongoose.models.User || mongoose.model<IUser>("User", UserSchema);

export default User;

Using MongoDB in Next.js API Routes

You can now use the connection in your Route Handlers:

// app/api/users/route.ts
import { NextRequest } from "next/server";
import { connectDB } from "@/lib/db";
import User from "@/models/User";

export async function GET() {
  try {
    await connectDB();

    const users = await User.find({})
      .select("-__v")
      .sort({ createdAt: -1 })
      .limit(50);

    return Response.json({ success: true, data: users });
  } catch (error) {
    console.error("GET /api/users error:", error);

    return Response.json(
      { success: false, error: "Failed to fetch users" },
      { status: 500 },
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    await connectDB();

    const body = await request.json();
    const user = await User.create(body);

    return Response.json({ success: true, data: user }, { status: 201 });
  } catch (error: any) {
    // Handle duplicate email error
    if (error.code === 11000) {
      return Response.json(
        { success: false, error: "Email already exists" },
        { status: 409 },
      );
    }

    return Response.json(
      { success: false, error: "Failed to create user" },
      { status: 500 },
    );
  }
}

The error.code === 11000 check catches MongoDB duplicate key errors, which commonly occur when a field has a unique index.


Environment Variables

Your .env.local file should contain your MongoDB connection string:

MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/dbname?retryWrites=true&w=majority

Never commit this file to Git.

Add .env.local to your .gitignore:

.env.local

Common MongoDB and Mongoose Errors

| Error | Cause | Fix | | -------------------------- | --------------------------------------- | --------------------------------------------- | | buffering timed out | No database connection before the query | Call connectDB() first | | Cannot overwrite model | Model is defined multiple times | Use mongoose.models.X \|\| mongoose.model() | | MongoServerError: E11000 | Duplicate unique field | Handle error.code === 11000 | | Too many connections | Connections are not cached | Use the global cache pattern |


Why Connection Caching Matters in Next.js

The main reason for using a cached MongoDB connection is to avoid repeatedly creating database connections.

This is especially important during development because Next.js can reload modules while you are working on your application.

Instead of creating a new connection every time, the cached connection can be reused:

if (cached.conn) {
  return cached.conn;
}

This pattern works well for Next.js applications using Mongoose and the App Router.


Frequently Asked Questions

How do I connect MongoDB to Next.js 15?

Install Mongoose, add your MongoDB connection string to MONGODB_URI, and create a reusable connection function that caches the Mongoose connection.

Should I use Mongoose with Next.js?

Mongoose is a popular choice when you want MongoDB schemas, models, validation, middleware, and a higher-level API.

How do I prevent MongoDB connection leaks in Next.js?

Use a cached Mongoose connection and connection promise instead of creating a new connection every time your application code runs.

Why does Mongoose say "Cannot overwrite model once compiled"?

This usually happens when a Mongoose model is registered more than once. Use mongoose.models.User || mongoose.model(...) to reuse an existing model.

Can I use MongoDB Atlas with Next.js 15?

Yes. MongoDB Atlas works with Next.js through the MongoDB connection string stored in an environment variable.

Does this work with the Next.js App Router?

Yes. This connection pattern can be used with Next.js Route Handlers and other server-side application code.


Conclusion

Connecting MongoDB to Next.js 15 with Mongoose is simple once the connection is structured correctly.

The important parts are:

  • Use a reusable MongoDB connection
  • Cache connections during development
  • Store your MongoDB URI in environment variables
  • Reuse existing Mongoose models
  • Handle MongoDB errors properly
  • Keep your database code separated from your API routes

I use this pattern in my own Next.js SaaS projects, including my NeuroDash dashboard template.

See it live:
https://neurodash-dashbord.vercel.app/

Get the template:
https://pixelanas.gumroad.com/l/neuro-dash

Questions about MongoDB + Next.js? Drop them below 👇


Anas — full-stack Next.js developer building SaaS products and premium templates.