Mongoose - multiple database connections - node.js

I want to understand how to switch between databases within mongoose global promise connection.
My current connection is established this way app.ts
import * as mongoose from 'mongoose';
...
try {
await mongoose.createConnection(`mongodb://localhost:27017/db1`, {
useNewUrlParser: true,
})
console.log("Connected")
} catch (error) {
console.log(error)
}
And then I am accessing it in different files some.model.ts
import { Schema, Document, model } from 'mongoose';
const SomeSchema: Schema = new Schema({
name: { type: String, required: true },
owner: { type: string, required: true }
});
export default model('Some', SomeSchema);
According to documentation.
So far we've seen how to connect to MongoDB using Mongoose's default connection. At times we may need multiple connections open to Mongo, each with different read/write settings, or maybe just to different databases for example. In these cases we can utilize mongoose.createConnection() which accepts all the arguments already discussed and returns a fresh connection for you.
const conn = mongoose.createConnection('mongodb://[username:password#]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options);
I can create multiple database connections like this
try {
const db1 = await mongoose.createConnection(`mongodb://localhost:27017/db1`, {
useNewUrlParser: true,
})
const db2 = await mongoose.createConnection(`mongodb://localhost:27017/db2`, {
useNewUrlParser: true,
})
console.log("Connected")
} catch (error) {
console.log(error)
}
I can see both connection in console.log(mongoose.connections)
But how can I specify what database should be used for the Model in some.model.ts?
import { Schema, Document, model } from 'mongoose';
const SomeSchema: Schema = new Schema({
name: { type: String, required: true },
owner: { type: string, required: true }
});
export default SPECIFY_DATABASE.model('Some', SomeSchema);
I have found other questions like this but there are connections created "localy", I need to use mongoose connection across many different files.
Thank you for answers, if you need more explanation please let me now.

You need to actually return the connection, and then register a given model to each of the connections. To clarify, you need:
something to create a (named, specific) connection
schemas
you create models by registering schemas to the given connections,
you also need something to orchestrate it.
Example, lets have a "db.js" file (I call mine "repo.js" usually) with a single export, a function that returns the initialized database Promise.
You'd use it by importing the function and awaiting for the db.
I have a bit of a longer example, so error handling etc is ommited for brevity.
import { createConnections } from './create-connections';
import { UsersSchema } from './users-schema';
import { PostsSchema } from './posts-schema';
let db: any;
export function getDatabase(): Promise<any> {
if (this.db) return Promise.resolve(db);
return createDatabases();
}
async function createDatabases() {
const { db1, db2 } = await createConnections('mongodb://localhost/db1', 'mongodb://localhost/db2');
const UserModel = db1.model('users', UserSchema);
const PostModel = db2.model('posts', PostSchema);
db = {
UserModel,
PostModel,
// also if you need this
connections: {
db1,
db2,
}
}
return db;
}
Now, I've used './create-connections' here, which is almost what you have:
// create-connection.js
const { createConnection } = require('mongoose');
// You create connections by calling this function and giving it the URL to the server
export function createConnections(url1, url2) {
const db1 = await createConnection(url1);
const db2 = await createConnection(url2);
return {
db1,
db2
}
}
Now, let's say you have two models: users and posts, let's have their schemas.
// users schema
import { Schema, Document } from 'mongoose';
export const UserSchema: Schema = new Schema({
name: { type: String, required: true },
});
// posts schema
import { Schema, Document } from 'mongoose';
export const PostSchema: Schema = new Schema({
text: { type: String, required: true },
owner: { type: SchemaID, required: true }
});
So now you need to bind it all in that fdirst file.
But how to use it? As I've said, since it's async, you always import it and use it as a simple async getDB:
// some controller, route handler, service etc.
import { getDatabase } from './get-database';
router.get('/users', async (req, res) => {
const User = await getDatabase().UserModel;
const users = await User.find();
return res.json(users);
});
router.post('/posts', async (req, res) {
const { text } = req.body;
const owner = req.user.id;
const Post = await getDatabase().PostModel;
const post = await Post.create({ text, owner });
return res.json(post);
});

Related

Mongoose is not connecting to local database

This is my app.js file. Please help me out to connect it with my local database. Sometimes it gets connected to the database and logs to the console but it doesn't add any collection to the local database.
const mongoose = require('mongoose')
main().catch(err=>console.log(err))
async function main() {
await mongoose.connect("mongodb://localhost:27017/fruitsDB", {
useNewUrlParser: true,
useUnifiedTopology: true
});
//Creating new schema
const fruitSchema = new mongoose.Schema({
name: String,
rating: Number,
review: String
});
const Fruit = mongoose.model("Fruit", fruitSchema);
const fruit = new Fruit ({
name: "Apple",
rating: 7,
review: "Pretty solid"
});
await fruit.save()
}
Insist localhost use 127.0.0.1:27017 This will work for sure.
OR
This happened probably because the MongoDB service isn't started. Follow the below steps to start it:
Go to Control Panel and click on Administrative Tools.
Double-click on Services. A new window opens up.
Search MongoDB.exe. Right-click on it and select Start
The server will start. Now execute npm start again and the code might work this time.
You can use mongo connection like this in typescript for ES6.
Schema like below
import mongoose from "mongoose"
export const RequestLogsSchema = new mongoose.Schema(
{
request_id: String,
...
},
{
collection: "request_logs"
}
)
example connection like below
import mongoose from 'mongoose'
import { RequestLogsSchema } from './mongo-schemas/RequestLogsSchema'
export class MongoClient {
mongodb: any
constructor(private host: string) { }
async MongoConnect() {
return new Promise(async (resolve, _reject): Promise<void> => {
console.log('🟡 MongoDB Connecting !')
this.mongodb = await mongoose.connect(this.host).then(() => {
console.log('🟢 MongoDB Connected !')
resolve(true)
}).catch((err) => console.log(err))
})
}
}
export const schemas = {
RequestLogsModal: mongoose.model("RequestLogs", RequestLogsSchema),
...
}
new MongoClient('mongodb://username:password#localhost:27017/db_name?authSource=db_name').MongoConnect()
To save your data like
import { schemas } from '../connections/mongo'
const saver = (data) => {
const request_logs = new schemas.RequestLogsModal({
request_id: data.request_id,
...
})
await request_logs.save()
}

Why am I not able query ( User.findOne() ) my mongoDB after setting up a Schema?

I can't seem to query my MongoDB after setting up the MongoDB schema. I don't understand where I am going wrong with my MongoDB schema, any help is appreciated!
I WAS earlier successful in querying my mongodb before creating a schema, using queries like this:
const uri = process.env.MONGODB_URI;
const client = new MongoClient(uri);
const result = await client.db("inride_adverts").collection("adverts").findOne({OrgEmail: OrgEmailToSignIn});
However, according to a YouTube tutorial am following (10:40 mins), after setting up a mongodb schema, I am NOT successful in using the following query to interact with my mongodb:
User.findOne( {OrgEmail: signUpEmail} ).exec();
Find below my simple User Schema:
./models/User.js
import { mongoose} from 'mongoose';
const UserSchema = new mongoose.Schema({
OrgName: {
type: String,
required: true
},
OrgEmail: {
type: String,
required: true
},
OrgPwd: {
type: String,
required: true
}
}, { collection: 'adverts' });
export const User = mongoose.model('User', UserSchema);
Also, find below my server.js file
./server.js
import express from 'express';
import { User } from './models/User.js';
import mongodb from 'mongodb';
import { mongoose} from 'mongoose';
mongoose.connect(db, { useNewUrlParser: true })
mongoose.connect(db, { useNewUrlParser: true })
.then( () => console.log("MongoDB Connected..." ))
.catch(error => console.log(err))
app.route('/advertiserRegister')
.post( async (req, res) => {
let formData = req.body;
let signUpName = formData.signUpName;
let signUpEmail = formData.signUpEmail;
let signUpPwd = formData.signUpPwd;
console.log("signUpName: " +signUpName);
console.log("signUpEmail: " +signUpEmail);
console.log("signUpPwd: " +signUpPwd);
if(signUpPwd !== signUpPwdConfirm){
console.log("Passwords arent EQUAL!");
return 0;
} else {
try{
console.log("signUpEmail>>> : " + signUpEmail );
// Validation passed!
const testing = await User.findOne( {OrgEmail: signUpEmail} ).exec();
console.log("testing >>> : " ,testing );
res.redirect('/advertiserLogin')
} catch {
//console.error('error', code);
console.log("Error logging in ");
res.redirect('/advertiserRegister')
};
}
});
The server.js file yields:
MongoDB Connected...
signUpName: Merc Enterprise LTD
signUpEmail: hundredcent.a#gmail.com
signUpPwd: 555
signUpEmail>>> : hundredcent.a#gmail.com
testing >>> : **null**
Error logging in
Turns out that the reason I was NOT able to query my collection was due to the fact that I was unknowingly querying the incorrect collection being: adverts under the incorrect database being: inride_adverts.
I came to understand and realise that In my ./models/User.js, the mongoose.model('User', UserSchema); code creates a new database in Atlas MongoDB called test and creates a totally new collection called User.
Having understood this, I am able to populate and query this collection successfully!

How to use `mongoose-delete` plugin with NestJs and typescript?

I'm using Mongoose in NestJs library and want to use mongoose-delete plugin for all of my schemas.
But I don't know how to use it with nestJS And Typescript.
First i installed both mongoose-delete and #Types/mongoose-delete libraries but there is no typescript documentary for This plugin.
This is the recommended method for adding plugin by nest:
MongooseModule.forRoot(MONGO_URI, {
connectionFactory: connection => {
connection.plugin(require('mongoose-delete'));
return connection;
},
}),
And absolutely this generates esLint error:
Require statement not part of import statement.eslint
And I cannot use delete function. It's not defined in the mongoose.Dcoument
export type ChannelDocument = Channel & Document;
constructor(
#InjectModel(Channel.name) private _channelModel: Model<ChannelDocument>,
) {}
async delete(id: string) {
this._channelModel.delete({ id });
// This is undefined -^
}
Try to restart you IDE (vscode if you use) after install this package: #types/mongoose-delete
use soft delete plugin => https://www.npmjs.com/package/soft-delete-mongoose-plugin
A simple and friendly soft delete plugin for mongoose,implementation using TS.
Methods were added and overridden on mongoose model to realize soft deletion logic.
yuo can used as a global plugin:
import { plugin } from 'mongoose';
import { SoftDelete } from 'soft-delete-mongoose-plugin';
// defind soft delete field name
const IS_DELETED_FIELD = 'isDeleted';
const DELETED_AT_FIELD = 'deletedAt';
// use soft delete plugin
plugin(
new SoftDelete({
isDeletedField: IS_DELETED_FIELD,
deletedAtField: DELETED_AT_FIELD,
}).getPlugin(),
);
// other code
// ...
Please take a look at mongoose-softdelete-typescript.
import { Schema, model } from 'mongoose';
import { softDeletePlugin, ISoftDeletedModel, ISoftDeletedDocument } from 'mongoose-softdelete-typescript';
const TestSchema = new Schema({
name: { type: String, default: '' },
description: { type: String, default: 'description' },
});
TestSchema.plugin(softDeletePlugin);
const Test = model<ISoftDeletedDocument, ISoftDeletedModel<ISoftDeletedDocument>>('Test', TestSchema);
const test1 = new Test();
// delete single document
const newTest = await test1.softDelete();
// restore single document
const restoredTest = await test1.restore();
// find many deleted documents
const deletedTests = await Test.findDeleted(true);
// soft delete many documents with conditions
await Test.softDelete({ name: 'test' });
// support mongo transaction
const session = await Test.db.startSession();
session.startTransaction();
try {
const newTest = await test1.softDelete(session);
await session.commitTransaction();
} catch (e) {
console.log('e', e);
await session.abortTransaction();
} finally {
await session.endSession();
}

How does mongoose know what collection I am accessing?

I'm having a trouble grasping a concept in Mongoose.
I'm using MongoDB atlas, got a cluster , a database and 2 collections.
users, characters.
Through a guide I've learned that a good way to write your stuff is to have a model (I use the naming schema) as a file, importing it into your Database module/class
and using it there to perform a query...
const mongoose = require("mongoose");
const process = require("./config.env");
db = () => {
return mongoose
.connect(process.env.URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: true,
})
.then((response) => {
console.log(`Connected to Databse : ${response.connection.host}`);
})
.catch((err) => {
console.log("DB_ERROR:", err);
process.exit(1);
});
};
module.exports = db;
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
});
const User = mongoose.model("User", UserSchema);
module.exports = User;
const User = require("../schemas/User");
const db = require("../config/db");
class Database {
constructor(db, collection) {
this.db = db;
this.collection = collection;
this.User = User;
}
connect() {
return db();
}
}
module.exports = Database;
one file to handle the db connection..another file as the User schema and a third file to handle every function i might use globally...
One thing I cannot wrap my mind around is
how is the findOne() function able to locate the collection I am using without me telling it what collection i want it to search in?
is it somehow translating the
const User = mongoose.model("User", UserSchema);
line and searching for "users" as well? I just can't understand the magic behind this...
what if I want to search specifically in the characters collection...?
Mongoose uses the model name, as passed when it was created: mongoose.model("User", UserSchema), converted to lower case and with an 's' appended.
For the model User it uses the collection users by default. You can change this by explicitly specifying the collection name in the schema.

Mongoose: handling multiple databases when working with one model

What I want is to have arbitrary databases (50 for example) with the same collections (same schemas, exact models, different data) and 1 nodejs (expressjs + mongoose) web app.
Example simplified case:
I have:
a single web application (expressjs + mongoose) with User model.
50 domains 50 databases with users collection.
What behaviour I want to achieve:
GET /api/users/ http request is coming to one of domains (test-domain-39.myapp.com)
app gets the requested domain name (test-domain-39) and somehow mongoose understands that it wants to query database-39 when I just do User.find({isActive: true}) in users.controller
So I just want an abstraction. I pass db name to mongoose and continue to work with the User model (as we all usually do when having single DB connection) and mongoose, if needed, creates connection (if it's the first request to the specific db), keeps it alive for next requests in connection pool and etc.
What's the most simple and efficient way to accomplish that?
Thank's in advance!
IMHO, while this is possible with MongoDB, I wouldn't advise maintaining a separate database for each domain, especially if you are expecting to have a huge number of them. Have you considered a multi-tenant model instead?
The sample code below adds user 'Alex' into two different databases, "domainOne" and "domainTwo". Hope this helps
var mongoose = require('mongoose');
var personSchema = { name: String, domain : String };
var baseUri = 'mongodb://localhost/';
domains.forEach((domain) => {
var conn = mongoose.createConnection(baseUri + domain, (error) => {
if(error){
console.log('Ups! Database connection failed!');
return;
}
//Use the connection object to create your models,
//instead the mongoose object
//so that our data is saved into the database
//associated with this connection
var Person = conn.model('Person', personSchema);
//Lets add user 'Alex' into the database
(new Person({name : 'Alex', domain : domain })).save((error) => {
if(error){
console.log('Ups! Could not save person');
} else {
conn.close();
}
});
});
});
This is how I implemented my project:
// config/db.ts
import {createConnection} from 'mongoose'
const MONGO_URI = process.env.MONGO_URI
if (!MONGO_URI)
throw new Error(
'Please define the MONGO_URI environment variable inside .env'
)
const connections: any = {}
async function db(dbName: string) {
if (connections[dbName]) {
return connections[dbName]
} else {
connections[dbName] = createConnection(`${MONGO_URI}/${dbName}`)
return connections[dbName]
}
}
export default db
// models/Test.ts
import { Schema } from 'mongoose'
export interface ITest {
_id: Schema.Types.ObjectId
name: string
createdAt?: Date
}
const testSchema = new Schema<ITest>(
{
name: { type: String, required: true },
},
{ timestamps: true }
)
export default testSchema
// pages/api/test.ts
import nc from 'next-connect'
import db from '../../config/db'
import testSchema from '../../models/Test'
const handler = nc()
handler.get(
async (req: NextApiRequestExtended, res: NextApiResponseExtended) => {
try {
const conn = await db(req.headers['x-db-key'])
const model = await conn.model('Test', testSchema)
const data = await model.find({})
res.send(data)
} catch (error: any) {
res.status(500).json({ error: error.message })
}
}
)
export default handler

Resources