Next.js middleware to connect mongoose - mongodb

I am working on a Next.js project and I want to connect it with MongoDB using mongoose. I want to make a middleware to run the connect function inside so I don't have to call connect function in each file I am using a mongoose model in.
this is my connect file:
// src/utils/connect.js
import mongoose from 'mongoose'
// getting the connection uri
const MONGO_URI = process.env.MONGO_URI
// checking if MONGO_URI is defined
if (!MONGO_URI) {
throw new Error(
'Please define the MONGO_URI environment variable inside .env.local'
)
}
// maintaining a cached connection to prevent reconnection (connections growing exponentially during API Route usage)
let cached = global.mongoose
// restiing the connection if there was no cached connection
if (!cached) {
cached = global.mongoose = null
}
/**
* mongodb connection
*/
export default async function connect() {
// returning the cached connection if it exists
if (cached) {
return cached
}
cached = await mongoose.connect(MONGO_URI, {
bufferCommands: false,
useNewUrlParser: true,
useUnifiedTopology: true
})
return cached
}
it will connect mongoose with my MongoDB database and caches the connection for future use.
this is one of my example API routes:
// src/pages/api/test.js
import connect from '../../utils/connect'
import Test from '../../models/Test';
export default async function handler(req, res) {
// connect
await connect()
const data = await Test.find({});
res.status(200).json({ name: 'John Doe', data })
}
now what I want to do is to get rid of that await connect() function in all routes and instead make a middleware to run the connect. and also is it better do so or not?

Related

MongoDB error when trying to connect to it

trying to get my code to connect to mongodb.
I have start mongo db compass, rund mongosh and mongod in cmd
add a new db to mongodb.
edit a new url to connect.
run npm start and get this and i dont connect to mongodb:
(node:14420) [MONGODB DRIVER] Warning: Current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version.
To use the new Server Discover and Monitoring engine, pass option { useUnifiedTopology: true } to the MongoClient constructor.
(Use `node --trace-warnings ...` to show where the warning was created)
my db.ts
import { MongoClient } from 'mongodb';
// Connection URL
const url = 'mongodb://127.0.0.1:27017';
// Database Name
const dbName = 'xxxxx';
// Create a new MongoClient
export async function initMongo() {
try {
const client = new MongoClient(url,{ useNewUrlParser: true });
await client.connect();
const db = client.db(dbName);
at the bottom of db
} catch (e) {
console.log('Cannot connect to MongoDB. Will retry in 10 seconds ...');
await new Promise<void>(resolve => {
setTimeout(() => resolve(), 10000);
});
console.log('Retrying...');
return await initMongo();
}
await client.connect
connect have a line right true.

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:
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?
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

How to initialize Mongodb in NextJS startup

As you know NextJS is Jamstack framework and I'm migrating from node/express to it but my problem is how to connect server to database at startup of server as i did in express?
there is now where to put my initalizing code in NextJS? Am I saying correct?
I saw some code to to that but there were typescript codes that im not familiar with them
On the other hand i'm able to do that on serverside functions like getStaticProps or getServerSideProps this is my code
dbinit.js
import mongoose from "mongoose";
export const dbStatus = () => mongoose.connection.readyState;
export default function dbConnect() {
if (dbStatus == 1) return "database is connected";
mongoose.connect(
`mongodb://localhost:${process.env.DBPORT}/${process.env.DBNAME}`
);
}
index.js
export async function getServerSideProps() {
const result = await dbConnect();
console.log(dbStatus());
return {
props: {},
};
}
with this code im able to connect to mongodb but there are some problems and the most important is that mycode isnot cleancode
In NextJS, we can connect MongoDB as middleware. This is very similar to the Express middleware approach as shown below.
// middleware/database.js
import { MongoClient } from 'mongodb';
import nextConnect from 'next-connect';
const client = new MongoClient('{YOUR-MONGODB-CONNECTION-STRING}', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
async function database(req, res, next) {
if (!client.isConnected()) await client.connect();
req.dbClient = client;
req.db = client.db('MCT');
return next();
}
const middleware = nextConnect();
middleware.use(database);
export default middleware;
For more details, you can refer to this official how-to doc for step-by-step guidance. Here is the example repository used.

Cannot get value of req.user for Passport.js

I spent hours figuring things out why I cannot get the value of req.user when Passport.js serialized a user. But magically, when I deleted the database collection that holds the session, it worked again.
My stack:
Vue.js
Express
Mongoose MongoDb (I store my data on Atlas)
Node.js
I use express-session and connect-mongo to create and save session data and use it to serialize and deserialize user using Passport.js
App.js:
const session = require("express-session");
const passport = require("passport");
const MongoStore = require("connect-mongo")(session);
// Sessions
app.use(
session({
secret: "this is a sample secret",
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
})
);
//Passport Middleware
app.use(passport.initialize());
app.use(passport.session());
Then I call req.user on a route like this:
router.get("/users", async (req, res) => {
try {
if (req.user) {
res.send(req.user)
} else {
res.send("no-user-found",)
}
} catch (err) {
console.error(err);
}
});
I'm calling /api/users on the front-end with Vue.js and Axios hosted on localhost port 8080. Also, tested on the server itself by calling http://localhost:3000/api/users
Now it works, now that I have deleted the sessions database collection on MongoDb Atlas.
I'm just wondering why this happens? Will it repeat again in the future?

Connected to MLab, but won't connect to localhost

I had this working fine on both localhost and MLab, but then had to switch databases. After much trying I got the database up on MLab, but now it's not connecting to my localhost. Here is my server.js file:
const path = require("path");
const PORT = process.env.PORT || 3001;
const app = express();
const mongoose = require("mongoose");
const routes = require("./routes");
// Connect to the Mongo DB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://XXUSERXX:XXPASSWORDXX#ds217388-a0.mlab.com:17388,ds217388-a1.mlab.com:17388/<dbname>?replicaSet=rs-ds217388', { useNewUrlParser: true });
mongoose.connection.on("open", function (ref) {
console.log("Connected to mongo server.");
});
mongoose.connection.on('error', function (err) { console.log(err) });
// Define middleware here
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// Serve up static assets (usually on heroku)
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
}
// Add routes, both API and view
app.use(routes);
// Define API routes here
// Send every other request to the React app
// Define any API routes before this runs
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "./client/build/index.html"));
});
app.listen(PORT, () => {
console.log(`🌎 ==> API server now on port ${PORT}!`);
});
The only line of code I changed was this one below, this is what it was previously:
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/wineDB', { useNewUrlParser: true });
I have this app connected in Heroku and had MONGODB_URI defined in the Config Vars, but it wasn't working with the second database until I manually put the connection string in my server.js file. It worked fine with the first one, I don't understand why!
How do I get it to connect to find localhost when it's not running off of MLAB so I can test? Thanks for the help.
It looks like the confusion is from a few different combinations of whether the environment variable being defined or not, as well as whether or not your app is using the variable, instead of falling back to what is defined.
The MONGODB_URI environment variable should contain the connection string for your mLab database and be defined in your Heroku environment both locally and when deployed. I'm assuming that the variable process.env.LOCAL will only be present on your local environment, in situations where your app should be connecting to the local database.
In these cases, something like the following should work:
if(process.env.LOCAL || process.env.MONGODB_URI) {
mongoose.connect(process.env.LOCAL || process.env.MONGODB_URI, { useNewUrlParser: true });
...
} else {
console.log("MongoDB connection string not defined!");
}
We place process.env.LOCAL first, followed the by ||, to say that it gets preference when connecting. Mongoose should then connect to whatever is defined in process.env.LOCAL if present (i.e. your local MongoDB database), falling back to process.env.MONGODB_URI (i.e. mLab) otherwise.
Lastly, it's wrapped in a simple if-else to print out an error message if both values are not defined.