Search code examples
mongodbnext.jsmongodb-atlasvercelnext-auth

MongoDB Atlas connections to cluster(s) exceeded- NextJS + NextAuth


I was creating a website using NextJS and for authentication uses NextAuth. And the database is on free tier in MongoDB Atlas.

I have two versions of code for database connection. One is this:

/**
 *      MongoDB Connection
 * 
 *  */
import mongoose from 'mongoose'

const MONGODB_URI = process.env.MONGODB_URL

if (! process.env.MONGODB_URL) {
  throw new Error(
    'Please define the MONGODB_URI environment variable inside .env.local'
  )
}

/**
 * Global is used here to maintain a cached connection across hot reloads
 * in development. This prevents connections growing exponentially
 * during API Route usage.
 */
let cached = global.mongoose

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

async function dbConnect() {
  if (cached.conn) {
    return cached.conn
  }

  if (!cached.promise) {
    const opts = {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      bufferCommands: false,
    //   bufferMaxEntries: 0,
    //   useFindAndModify: false,
    //   useCreateIndex: true,
    }

    cached.promise = mongoose.connect(process.env.MONGODB_URL, opts).then((mongoose) => {
      return mongoose
    })
  }
  cached.conn = await cached.promise
  return cached.conn
}

export default dbConnect

So, before making any DB related queries via code, I call await dbConnect(). It is working fine.

But for storing the sessions in DB, in NextAuth, I was not able to use the above function. So for that, am using this custom code (/lib/mongodb.js):

/**
 * 
 *      Used only for Next-Auth
 * 
 */

import { MongoClient } from "mongodb"

const uri = process.env.MONGODB_URL
const options = {
  useUnifiedTopology: true,
  useNewUrlParser: true,
}

let client
let clientPromise

if (!process.env.MONGODB_URL) {
  throw new Error("Please add your Mongo URI to .env.local")
}

if (process.env.NODE_ENV === "development") {
  // In development mode, use a global variable so that the value
  // is preserved across module reloads caused by HMR (Hot Module Replacement).
  if (!global._mongoClientPromise) {
    client = new MongoClient(uri, options)
    global._mongoClientPromise = client.connect()
  }
  clientPromise = global._mongoClientPromise
} else {
  // In production mode, it's best to not use a global variable.
  client = new MongoClient(uri, options)
  clientPromise = client.connect()
}

// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise

And the code in my /pages/api/auth/[...nextauth].js is like this:

import NextAuth from 'next-auth'
import { MongoDBAdapter } from "@next-auth/mongodb-adapter"
import mongodb from '../../../lib/mongodb'
//...

export default async function auth(req, res) {
    return await NextAuth(req, res, {    
        //....

        adapter: MongoDBAdapter({
            db: (await mongodb).db("my_db_name_here")            
        }),

        //....
    })
}

Here's the packages am using:

"mongodb": "^4.1.2",
"mongoose": "^6.0.1",    
"next": "11.0.1",
"next-auth": "^4.0.0-beta.6",
"react": "17.0.2",
"react-dom": "17.0.2",

The problem is, am receiving email notifications sometimes like the following:

enter image description here

My website is still in testing phase(tested by two persons only) and is hosting in Vercel server. I believe this could be because NextAuth is creating new db connections each time? Any thoughts on what went wrong?


Solution

  • clientPromise in next-auth is local, you create new client and 5 connections every time. Just use global.mongoose.conn.

    The docs for MongoDBAdapter says it needs a promise that resolves to a client, so it must be something like this:

    export default NextAuth({
      adapter: MongoDBAdapter(dbConnect().then(mon => mon.connection.getClient())),
      ...
    })
    

    In you case you seem to use db. I couldn't find any references for MongoDBAdapter to accept something liek {db: ...} but you can get the db from mongoose as following:

    await (dbConnect().then(mon => mon.connection.getClient().db("my_db_name_here")))
    

    Or without parameter to use the same database as configured in mongoose connection.

    UPDATE

    The issue with number of connections from Vercel is covered in Vercel creates new DB connection for every request