Does this method close the MongoDB connection? - javascript

I have a method that connects to the MongoDB, but I can't figure out if this connection ends after a call was made or not.
This is the method:
import { Db, MongoClient } from "mongodb";
let cachedConnection: { client: MongoClient; db: Db } | null = null;
export async function connectToDatabase(mongoUri?: string, database?: string) {
if (!mongoUri) {
throw new Error(
"Please define the MONGO_URI environment variable inside .env.local"
);
}
if (!database) {
throw new Error(
"Please define the DATABASE environment variable inside .env.local"
);
}
if (cachedConnection) return cachedConnection;
cachedConnection = await MongoClient.connect(mongoUri, {
useNewUrlParser: true,
useUnifiedTopology: true,
}).then((client) => ({
client,
db: client.db(database),
}));
return cachedConnection!;
}
I use this with Next.js and I am afraid that the app I am actually doing goes down if there will be too many connections. Intuitively, I think that the mongoDB connection ends after a call, but I am not sure.

Related

How to dynamically connect to a MongoDB database using Nest.js

I need to create a separate database for each entity in my client's app.
I'm going to determine the database name according
to the subdomain from which the request is coming from.
How can I achieve that and connect dynamically to a database using nestjs and mongoose?
UserService
async findOneByEmail(email: string, subdomain: string): Promise<User | any> {
const liveConnections = await Databases.getConnection(subdomain)
const user = await liveConnections.model(User.name, UserSchema).find()
// { 'securityInfo.email': email }
if (!user)
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
error: 'user not found',
field: 'user',
},
HttpStatus.BAD_REQUEST
)
return user
}
class that I create
class DataBases extends Mongoose {
private clientOption = {
keepAlive: true,
useNewUrlParser: true,
useUnifiedTopology: true,
}
private databases: { [key: string]: Connection } = {}
private getConnectionUri = (companyName = '') =>
`mongodb+srv://${process.env.MONGODB_USERNAME}:${process.env.MONGODB_PASSWORD}#cluster0.2lukt.mongodb.net/${companyName}?retryWrites=true&w=majority`
public async getConnection(companyName = ''): Promise<Connection> {
const connection = this.databases[companyName]
return connection ? connection : await this.createDataBase(companyName)
}
private async createDataBase(comapnyName = ''): Promise<Connection> {
// create new connection and if the database not exists just create new one
const newConnection = await this.createConnection(
this.getConnectionUri(comapnyName),
this.clientOption
)
this.databases[comapnyName] = newConnection
return newConnection
}
}
I fix it. the DataBases Class that I create works really great and if you know a better way please tell me .
what I had to change is the way I use the connection to MongoDB.
and now I can connect to different databases depends on the subdomain.
I hope it will help someone!
UserService
async findOneByEmail(email: string, subdomain: string): Promise<User | any> {
const liveConnections = await Databases.getConnection(subdomain)
const user = await liveConnections
.model(User.name, UserSchema)
.findOne({ 'securityInfo.email': email })
.exec()
if (!user)
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
error: 'user not found',
field: 'user',
},
HttpStatus.BAD_REQUEST
)
return user
}

TypeError: grid.mongo.ObjectID is not a constructor

I've got this JS code for my Next.js API. I'm trying to store images in MongoDB GridFS and retrieve them with a simple API route. The code you are seeing is from a file that gets imported in the API route.
import { MongoClient } from "mongodb"
import Grid from "gridfs-stream"
const { MONGODB_URI, MONGODB_DB } = process.env
if (!MONGODB_URI) {
throw new Error(
"Please define the MONGODB_URI environment variable inside .env.local"
)
}
if (!MONGODB_DB) {
throw new Error(
"Please define the MONGODB_DB environment variable inside .env.local"
)
}
let cached = global.mongo
if (!cached) {
cached = global.mongo = { conn: null, promise: null }
}
export async function connectToDatabase(dbIndex = 0) {
if (cached.conn) {
return cached.conn
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true
}
cached.promise = MongoClient.connect(MONGODB_URI, opts).then((client) => {
const db = client.db(MONGODB_DB.split(",")[dbIndex])
const grid = Grid(db, MongoClient)
grid.collection("fs.files")
return {
client,
db: db,
gfs: grid
}
})
}
cached.conn = await cached.promise
return cached.conn
}
When I try to use createReadStream I get the following error:
TypeError: grid.mongo.ObjectID is not a constructor
The issue is with const grid = Grid(db, MongoClient) but I have no idea how to fix it. Any help would be greatly appreciated.
Edit: Fixed the issue. I wasn't meant to use grid.collection.

Mongoose createConnection and Document.prototype.save()

I am building a multi tenant app with only a few connections (max 3 or 4) on the same mongo host. What I am really doing is establishing mongoose connections at server startup and store it in a context object.
// For each tenants
tenantConnection(name, uri) => new Promise((resolve, reject) => {
const connection = mongoose.createConnection(uri, {
useCreateIndex: true,
useNewUrlParser: true,
useFindAndModify: false,
retryWrites: false,
useUnifiedTopology: true
})
connection.on('connected', async () => {
const MessageModel = connection.model('Message', Message) // <- Message is a classic mongoose Schema
...
return resolve({ connection, models: { Message: MessageModel } })
})
})
All works well except when I am trying to use the prototype .save() (same with Model.create({...}). When I try to create a new record, the function stuck, no callback are triggered nor any error.
const { models: { Message } } = tenant
const messageRecord = new Message({ content }
await messageRecord.save() // -> Stuck here, nothing happens
At this moment, the only way I have found is to use UpdateOne({}, {...}, {upsert: true}) to create records but I'd rather like to use native mongoose prototype .save() to trigger the setters from my schema.
Does anyone has any idea of what I am doing wrong ?

Typescript Error code: 2366, Function lacks ending return statement and return type does not include 'undefined'

I am currently working on the server/database of my project. It is currently composed of Javascript, Typescript, MongoDB, Apollo-Server, and Express. The error above keeps coming up and I am not sure how to solve it. Here is the code I have on my index.ts file for my database folder.
import { MongoClient } from "mongodb";
import { Database, Listing, Booking, User } from '../lib/types';
const url = `mongodb+srv://${process.env.DB_USER}:${process.env.DB_USER_PASSWORD}#${process.env.DB_CLUSTER}.mongodb.net`;
export const connectDatabase = async (): Promise<Database> => {
try {
const client = await MongoClient.connect(url, { useNewUrlParser: true, useUnifiedTopology: true });
const db = client.db("main");
return {
bookings: db.collection<Booking>("bookings"),
listings: db.collection<Listing>("listings"),
users: db.collection<User>("users"),
};
} catch (error) {
console.log(error);
}
};
Any help would be greatly appreciated.
You're catching the error but then you're not returning anything from the function. That is why it's complaining. Either remove the try/catch and handle the error in the function calling this one or return something usable to the caller.
Set "noImplicitReturns" to false in tsconfig.json.

Is it possible to run Mongoose inside next.js api?

I'm building a website for my sister so that she can sell her art. I am using Next.js to set everything up. The website renders the artwork by grabbing an array from a database and mapping through it.
Two Example objects
{
id: 8,
path: "images/IMG_0008.jpg",
size: "9x12x.75",
price: "55",
sold: false
}
{
id: 9,
path: "images/IMG_0009.jpg",
size: "9x12x.75",
price: "55",
sold: false
}
pages/Shop.js
import Card from "../Components/Card";
import fetch from 'node-fetch'
import Layout from "../components/Layout";
function createCard(work) {
return (
<Card
key={work.id}
id={work.id}
path={work.path}
size={work.size}
price={work.price}
sold={work.sold}
/>
);
}
export default function Shop({artwork}) {
return (
<Layout title="Shop">
<p>This is the Shop page</p>
{artwork.map(createCard)}
</Layout>
);
}
export async function getStaticProps() {
const res = await fetch('http://localhost:3000/api/get-artwork')
const artwork = await res.json()
return {
props: {
artwork,
},
}
}
The problem I am running into is that when I try to use mongoose in the api/get-artwork. It will only render the page once and once it is refreshed it will break I believe do to the fact the Schema and Model get redone.
pages/api/get-artwork.js/
const mongoose = require("mongoose");
mongoose.connect('mongodb://localhost:27017/ArtDB', {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false
});
const itemsSchema = {
id: String,
description: String,
path: String,
size: String,
price: Number,
sold: Boolean
};
const Art = mongoose.model("Art", itemsSchema);
export default (req, res) => {
Art.find({sold: false}, (err, foundItems)=> {
if (err) {
console.log(err);
} else {
console.log(foundItems);
res.status(200).json(foundItems);
}
});
};
So to try to fix this I decided to use the native MongoDB driver. Like this.
/pages/api/get-artwork/
const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
// Connection URL
const url = 'mongodb://localhost:27017';
// Database Name
const dbName = 'ArtDB';
// Create a new MongoClient
const client = new MongoClient(url, {useUnifiedTopology: true});
let foundDocuments = ["Please Refresh"];
const findDocuments = function(db, callback) {
// Get the documents collection
const collection = db.collection('arts');
// Find some documents
collection.find({}).toArray(function(err, arts) {
assert.equal(err, null);
foundDocuments = arts;
callback(arts);
});
}
// Use connect method to connect to the Server
client.connect(function(err) {
assert.equal(null, err);
const db = client.db(dbName);
findDocuments(db, function() {
client.close();
});
});
export default (req, res) => {
res.send(foundDocuments);
};
This works for the most part but occasionally the array will not be returned. I think this is because the page is loading before the mongodb part finishes? So I guess my question is how do I make 100% sure that it loads the art correctly every time whether that be using mongoose or the native driver.
Thanks!
The Next.js team has a good set of example code, which they add to regularly, one of them being Next.js with MongoDB and Mongoose. Check it out, https://github.com/vercel/next.js/tree/canary/examples/with-mongodb-mongoose, and hope this helps if you're still searching for solutions.
A little more complete answer might be helpful here. Here's what's working for us.
I would suggest using the next-connect library to make this a little easier and not so redundant.
Noticed unnecessary reconnects in dev so I bind to the global this property in Node. Perhaps this isn't required but that's what I've noticed. Likely tied to hot reloads during development.
This is a lengthy post but not nearly as complicated as it seems, comment if you have questions.
Create Middleware Helper:
import mongoose from 'mongoose';
// Get your connection string from .env.local
const MONGODB_CONN_STR = process.env.MONGODB_CONN_STR;
const databaseMiddleware = async (req, res, next) => {
try {
if (!global.mongoose) {
global.mongoose = await mongoose.connect(MONGODB_CONN_STR, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
});
}
}
catch (ex) {
console.error(ex);
}
// You could extend the NextRequest interface
// with the mongoose instance as well if you wish.
// req.mongoose = global.mongoose;
return next();
};
export default databaseMiddleware;
Create Model:
Typically the path here might be src/models/app.ts.
import mongoose, { Schema } from 'mongoose';
const MODEL_NAME = 'App';
const schema = new Schema({
name: String
});
const Model = mongoose.models[MODEL_NAME] || mongoose.model(MODEL_NAME, schema);
export default Model;
Implement Next Connect:
Typically I'll put this in a path like src/middleware/index.ts (or index.js if not using Typescript).
Note: the ...middleware here just allows you, see below, to pass in additional middleware on the fly when the handler here is created.
This is quite useful as our handler creator here can have things like logging and other useful middleware so it's not so redundant in each page/api file.
export function createHandler(...middleware) {
return nextConnect().use(databaseMiddleware, ...middleware);
}
Use in Api Route:
Putting it together, we can now use our App Model with ease
import createHandler from 'path/to/above/createHandler';
import App from 'path/to/above/model/app';
// again you can pass in middleware here
// maybe you have some permissions middleware???
const handler = createHandler();
handler.get(async (req, res) => {
// Do something with App
const apps = await App.find().exec();
res.json(apps);
});
export default handler;
In pages/api/get-artwork.js/
const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost:27017/ArtDB", {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
});
const itemsSchema = {
id: String,
description: String,
path: String,
size: String,
price: Number,
sold: Boolean,
};
let Art;
try {
// Trying to get the existing model to avoid OverwriteModelError
Art = mongoose.model("Art");
} catch {
Art = mongoose.model("Art", itemsSchema);
}
export default (req, res) => {
Art.find({ sold: false }, (err, foundItems) => {
if (err) {
console.log(err);
} else {
console.log(foundItems);
res.status(200).json(foundItems);
}
});
};
It works just fine for me.
When we use mongoose with express.js, we connect to mongodb and run the code once. But in next.js everytime we need to connect to mongodb, we have to run connection code.
Connection to mongodb
import mongoose from "mongoose";
const dbConnect = () => {
if (mongoose.connection.readyState >= 1) {
// if it is not ready yet return
return;
}
mongoose
.connect(process.env.DB_LOCAL_URI, {
//****** since mongoose 6, we dont need those******
// useNewUrlParser: true,
// useUnifiedTopology: true,
// useFindAndModify: false,
// useCreateIndex: true,
})
.catch((err) => console.log(err))
.then((con) => console.log("connected to db"));
};
export default dbConnect;
Use it inside next api
Before running handler code, you need to connect first
import dbConnect from "../../../config/dbConnect";
dbConnect();
... then write handler code
A concise OO approach
Inspired by #Blujedis answer this is what I ended up with.
/* /api/util/handler.js */
import nextConnect from "next-connect";
import mongoose from "mongoose";
export class Handler {
static dbConnection = null
static dbMiddleware = async (req, res, next) => {
try {
Handler.dbConnection ||= await mongoose.connect(process.env.MONGO_URL)
next()
} catch (err) {
console.error(err);
next(err)
}
}
constructor(...middleware) {
return nextConnect().use(Handler.dbMiddleware, ...middleware);
}
}
Example handler:
/* /api/people.js */
import { Handler } from "./util/handler"
import { Person } from "./models/person"
export default
new Handler()
.get(async (req, res) => {
const people = await Person.find({})
return res.status(200).json(people)
})

Categories

Resources