Why does Mongoose setClient return MongooseError: Must call `setClient()` with an instance of MongoClient? - mongodb

I am having trouble getting setClient() to work. My understanding of this is, that I can make a connection to MongoDB with the native MongoDB MongoClient and then in another piece of code I should be able to use this instance of the MongoClient to connect to Mongoose. So set the client to Mongoose while using the same identical connection, not a separate one.
https://mongoosejs.com/docs/api/connection.html#connection_Connection-setClient
Set the MongoDB driver MongoClient instance that this connection uses to talk to MongoDB. This is useful if you already have a MongoClient instance, and want to reuse it.
Case A
import mongoose from 'mongoose';
import { MongoClient } from 'mongodb';
async function run() {
try {
const uri = 'mongodb://localhost:27017';
// Create a new MongoClient
const client = new MongoClient(uri);
const conn = mongoose.createConnection().setClient(client);
conn.getClient(); // MongoClient { ... }
conn.readyState; // 1, means 'CONNECTED'
} catch (error) {
console.log(error);
}
}
run();
This returns
MongooseError: Must call setClient() with an instance of MongoClient at NativeConnection.setClient (.../node_modules/mongoose/lib/connection.js:1391:11).
Why am I getting this error ? What is the correct code for setClient()?
Case B
Vercel shows how to used MongoDB and Mongoose in their environment exporting a clientPromise here with MongoDB https://github.com/vercel/next.js/blob/canary/examples/with-mongodb/lib/mongodb.js and a dbConnect here with Mongoose https://github.com/vercel/next.js/blob/canary/examples/with-mongodb-mongoose/lib/dbConnect.js.
So given in db.js I have this exemplary code with the native MongoDB driver
import { MongoClient } from 'mongodb'
const uri = process.env.MONGODB_URI
const options = {}
let client
let clientPromise
if (!process.env.MONGODB_URI) {
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 in another file I like to use this clientPromise with Mongoose what do I do ?
Is my understanding correct here that I in fact can use the exported MongoDB clientPromise with Mongoose at all through the use of setClient() ? Or is setClient() used for something different ?
In another file, if I try this
import mongoose from 'mongoose';
import clientPromise from '$lib/mongodb';
export async function get() {
try {
const client = await clientPromise;
console.log(client); // logs client just fine
const conn = mongoose.createConnection().setClient(client);
// starts complaining here with
// MongooseError: Must call `setClient()` with an instance of MongoClient
conn.getClient(); // MongoClient { ... }
conn.readyState; // 1, means 'CONNECTED'
console.log(conn);
return {
status: 200,
body: {
message: 'ok'
}
};
} catch (error) {
console.log(error);
}
}
the same error is shown.
How do I get setClient() to work on either, the MongoClient instance or the clientPromise?
edit:
I also get this error.
Argument of type 'import(".../node_modules/mongodb/mongodb").MongoClient' is not assignable to parameter of type 'import(".../node_modules/mongoose/node_modules/mongodb/mongodb").MongoClient'.
The types of 'options.credentials' are incompatible between these types.
Type 'import(".../node_modules/mongodb/mongodb").MongoCredentials' is not assignable to type 'import(".../node_modules/mongoose/node_modules/mongodb/mongodb").MongoCredentials'.

Related

Connecting to MongoDB from Vercel

I have a SvelteKit application deployed on vercel.app that uses a MongoDB (Atlas). In most cases the database connection works, but sometimes I get a connection error (connection timed out).
If this error occurs, and I try again to do something that uses the database, it immeadiately logs the same error again. This problem persists for some time, and then suddendly the database connection works again.
(When running the app locally with "npm run dev", using the same database, I've never experienced this error.)
To connect to the database, I defined:
mongodb-client.ts:
import { MongoClient } from 'mongodb';
const uri = process.env.DB_URI;
const dbClient = new MongoClient(uri).connect();
export default dbClient;
and use it like this (in several places):
import dbClient from '$lib/server/mongodb-client';
const user = await (await dbClient).db().collection('users').findOne({username: username});
I guess that, when the dbClient Promise is rejected (for whatever reason), it stays rejected and any subsequent await will immediately result in "rejected" (and therefore it will not try to reconnect; except that at some point it will ...?). Is my understanding correct? How should this be implemented correctly? (E.g. Do I need to add some options to the connection URI when this connection is create from a serverless function? Do I need to add some options when creating/connecting the MongoClient? Do I need to do this manually and add a loop, check if the promise is rejected and try again? Or should this be implemented in a completely different way?)
As you probably have guessed I'm new to JavaScript/TypeScript, MongoDB, Serverless and everything ... Thanks for any help and advice!
You can declare a function handling the connection to the database.
You will handle connection errors there and also check if a connection is already established:
import { MongoClient } from 'mongodb';
const uri = process.env.DB_URI;
const dbClient = new MongoClient(uri);
export const connectDb = async () => {
try {
if (!dbClient.isConnected()) {
await dbClient.connect();
}
return await dbClient.db();
} catch (e) {
console.log(e);
process.exit(1); // Or do something else...
}
};
Usage:
import { connectDb } from '$lib/server/mongodb-client';
const db = await connectDb();
const user = await db.collection('users').findOne({username: username});

How to connect to mongodb database using Nextjs?

Trying to connect to my mongodb database in the latest version of Nextjs. Things have changed so much, so I don't longer know what to do.
There's an example of how to set up the connection here: https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
They use this file:
//The mongodb.js file from the example
import { MongoClient } from 'mongodb'
const uri = process.env.MONGODB_URI
const options = {}
let client
let clientPromise
if (!process.env.MONGODB_URI) {
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
However, they forgot to add how to actually use it. I can't even begin to figure it out.
//pages/api/user.js
import client from '/lib/mongodb.js'
export default async function handler(req, res) {
//How do I connect here?
}
And two bonus questions:
I used to do caching on my database connection. Is it not needed anymore?
What happened to the utils folder? It used to be special, in that it didn't send anything there to the client. Now everyone seem to use lib but I don't think there's anything special with it?
You can do like this:
const dbClient = await client;
const db = dbClient.db('db-name');
const collection = db.collection('collection-name');
// example to get a doc in collection
const doc = await collection.findOne({query:""}, {...options})

SvelteKit With MongoDB ReferenceError: global is not defined

I'm trying to setup MongoDB connection library function. I know this function is solid, its used in a whole lot of places (search for Global is used here to maintain a cached connection across hot reloads) and you'll find a whole lot of uses including next.js releases. Note, the purpose of global storage for the database connection is to reduce the overall # of db connections in use at any one time.
What I'm not understanding is the error I'm getting when I import this library via import { connectToDatabase } from '$lib/database';
database.js
// https://github.com/mongodb-developer/mongodb-next-todo/blob/main/util/mongodb.js
import { ENV_OBJ } from "$lib/env";
import { MongoClient } from "mongodb";
const uri = ENV_OBJ.MONGODB_URI;
if (!uri) {
throw new Error("Please define the Mongodb uri environment variable inside .env");
}
/**
* 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.mongo
if (!cached) {
cached = global.mongo = { conn: null, promise: null }
}
export const connectToDatabase = async() => {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const options = {
useNewUrlParser: true,
useUnifiedTopology: true
};
cached.promise = MongoClient.connect(MONGODB_URI, opts).then((client) => {
return {
client,
db: client.db(MONGODB_DB),
}
})
}
cached.conn = await cached.promise;
return cached.conn;
}
The errors:
global is not defined
ReferenceError: global is not defined
at node_modules/mongodb/lib/promise_provider.js (http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:548:25)
at __require2 (http://localhost:3000/node_modules/.vite/chunk-6ODJH7E3.js?v=3885e04e:10:44)
at node_modules/mongodb/lib/utils.js (http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:6524:30)
at __require2 (http://localhost:3000/node_modules/.vite/chunk-6ODJH7E3.js?v=3885e04e:10:44)
at node_modules/mongodb/lib/cursor/abstract_cursor.js (http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:10873:19)
at __require2 (http://localhost:3000/node_modules/.vite/chunk-6ODJH7E3.js?v=3885e04e:10:44)
at node_modules/mongodb/lib/index.js (http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:25281:29)
at __require2 (http://localhost:3000/node_modules/.vite/chunk-6ODJH7E3.js?v=3885e04e:10:44)
at http://localhost:3000/node_modules/.vite/mongodb.js?v=3885e04e:25616:23
Note, I do see a file in my generated minimal sveltekit repo called global.d.ts I'm not sure of its purpose. It contains only:
/// <reference types="#sveltejs/kit" />
Any ideas on what's causing the error?
Reference: "#sveltejs/kit": "version": "1.0.0-next.118",
Edit: After spending a whole lot of time on this issue, the global not defined error seems to come from import { MongoClient } from "mongodb"; If I add appropriate console.logs, I can see that the MongoClient function works fine on the server, but then I get the global error on the client. The server indicates no errors at all.
So it turns out I was calling import { connectToDatabase } from '$lib/database' not in a .js helper file or api style (.js) endpoints. I was attempting to use that import and make a database call directly from the <script> portion of a xxx.svelte file.
Definite no go. That generates an immediate global not defined error.

create socket instance from vuex

I am using vue socket io for getting data from socket. For getting data I use query like
// ioinstance
import io from 'socket.io-client'
const restaurantId = localStorage.getItem('restaurant-id')
const socketUri = process.env.SOCKET_URI
export default io(socketUri, {
transports: ['websocket'],
query: `channel_id=restaurant-${restaurantId}`,
reconnect: true,
reconnectionDelay: 500,
reconnectionDelayMax: 1000,
pingInterval: 200
})
Here I get restaurantId after i successfully logged in to the panel and dispatch an action after successfully logged in like
// from vuex module
import VueSocketio from 'vue-socket.io-extended'
import ioInstance from '../../socket-instance'
...
...
socketInitialize ({dispatch}) {
let restaurantId = await localStorage.getItem('restaurant-id')
if (restaurantId && restaurantId != null) {
Vue.use(VueSocketio, ioInstance)
this._vm.$socket.on(`restaurant-${restaurantId}`, (data) => {
dispatch('socketIncoming', data)
})
}
}
but creating vue instance is not working from socketInitialize action although create instance from vue component is working fine
// from component
import Vue from 'vue'
import VueSocketio from 'vue-socket.io'
import ioInstance from './socket-instance'
...
...
mounted () {
let restaurantId = await localStorage.getItem('restaurant-id')
if (restaurantId && restaurantId != null) {
Vue.use(VueSocketio, ioInstance)
this.$socket.on(`restaurant-${restaurantId}`, (data) => {
this.$store.dispatch('socketIncoming', data)
})
}
}
Since I have to pass restaurantId for socket instance, I didn't initialize it from main.js (it renders first and restaurantId is not available here if not logged in) file. I need some suggestion, how could i create this initialization after logged in and any alternative way for initializing using Vue.use or this._vm or (new Vue()) or Vue.prototype
From Vue.use(plugin):
This method has to be called before calling new Vue()
So you have to register the plugin first then open the connection after when you ready. This question is already answered in FAQ section from the vue-socket.io-extended How to prevent connection until authed?.
Basically you have to tell socket.io to not open the connection at instantiate by set autoConnect to false:
const socket = io({
autoConnect: false
})
Then when you ready call open function:
this.$socket.io.opts.query = `channel_id=restaurant-${restaurantId}`
this.$socket.open()

Mongo: Is MongoClient.connection just shorthand for new MongoClient(new Server())?

In the MongoDocs they specify this is the correct way to create an open connection:
var MongoClient = require('mongodb').MongoClient
, Server = require('mongodb').Server;
var mongoClient = new MongoClient(new Server('localhost', 27017));
mongoClient.open(function(err, mongoClient) {
var db1 = mongoClient.db("mydb");
mongoClient.close();
});
But later they mention that you can also do it this way:
MongoClient.connect("mongodb://localhost:27017/integration_test", function(err, db) {
test.equal(null, err);
test.ok(db != null);
});
They never explicitly say the difference between the two though. It seems like the second version is just a shorthand for the first. Is this true or is there something else I am not getting here?