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

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
}

Related

Get populated data from Mongoose to the client

On the server, I am populating user-data and when I am printing it to the console everything is working fine but I am not able to access the data on the client or even on Playground of GraphQL.
This is my Schema
const { model, Schema } = require("mongoose");
const postSchema = new Schema({
body: String,
user: {
type: Schema.Types.ObjectId,
ref: "User",
},
});
module.exports = model("Post", postSchema);
const userSchema = new Schema({
username: String,
});
module.exports = model("User", userSchema);
const { gql } = require("apollo-server");
module.exports = gql`
type Post {
id: ID!
body: String!
user: [User]!
}
type User {
id: ID!
username: String!
}
type Query {
getPosts: [Post]!
getPost(postId: ID!): Post!
}
`;
Query: {
async getPosts() {
try {
const posts = await Post.find()
.populate("user");
console.log("posts: ", posts[0]);
// This works and returns the populated user with the username
return posts;
} catch (err) {
throw new Error(err);
}
},
}
But on the client or even in Playground, I can't access the populated data.
query getPosts {
getPosts{
body
user {
username
}
}
}
My question is how to access the data from the client.
Thanks for your help.
you are using this feature in the wrong way you should defined a Object in your resolvers with your model name and that object should contain a method that send the realated user by the parant value.
here is a full document from apollo server docs for how to use this feature
use lean() like this :
const posts = await Post.find().populate("user").lean();

Does this method close the MongoDB connection?

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.

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 ?

Cannot return Promise

I'm using Typeorm so I've created a method that return a connection:
public static async getConnection(): Promise<Connection> {
if (DatabaseProvider.connection) {
return DatabaseProvider.connection;
}
const { type, host, port, username, password, database, extra, entities, migrations } = DatabaseProvider.configuration;
DatabaseProvider.connection = await createConnection({
type, host, port, username, password, database,
extra,
entities: [
entities
],
migrations: [
migrations
]
} as any);
return DatabaseProvider.connection;
}
I want assign the connection to a bot instance of Telegraf so I have created a .d.ts file for specify the type:
export interface TelegrafContext extends Context {
db: Connection
}
then:
bot.context.db = DatabaseProvider.getConnection().then((conn) => { return conn; });
and I get:
Type 'Promise' is missing the following properties from type 'Connection': name, options, isConnected, driver, and 32 more.
What I did wrong?
Probably because you're trying to assign Promise<Connection> to bot.context.db and it should be a Connection.
so you can either:
DatabaseProvider.getConnection().then((conn) => {
bot.context.db = conn
});
or:
bot.context.db = await DatabaseProvider.getConnection()

How to get websocket url using nestjs and how to test in postman?

I am create mean stack application using nestjs.In nest js i am using websockets.I don't know how to test websockets in postman. Normally i have test route url in postman and get o/p Like: "http://localhost:3000/{routeUrl}" but how to give using sockets i am confused
example :
#WebSocketGateway()
export class MessagesGateway implements OnGatewayDisconnect {
constructor(#InjectModel(Message) private readonly messagesModel:
ModelType<Message>,
#InjectModel(Room) private readonly roomsModel: ModelType<Room>,
#InjectModel(User) private readonly usersModel: ModelType<User>) { // <1> }
async handleDisconnect(client: Socket) { // <2>
const user = await this.usersModel.findOne({clientId: client.id});
if (user) {
client.server.emit('users-changed', {user: user.nickname, event: 'left'});
user.clientId = null;
await this.usersModel.findByIdAndUpdate(user._id, user);
} }
#SubscribeMessage('enter-chat-room') // <3> async
enterChatRoom(client: Socket, data: { nickname: string, roomId: string
}) {
let user = await this.usersModel.findOne({nickname: data.nickname});
if (!user) {
user = await this.usersModel.create({nickname: data.nickname, clientId: client.id});
} else {
user.clientId = client.id;
user = await this.usersModel.findByIdAndUpdate(user._id, user, {new: true});
}
client.join(data.roomId).broadcast.to(data.roomId)
.emit('users-changed', {user: user.nickname, event: 'joined'}); // <3> }
#SubscribeMessage('leave-chat-room') // <4> async
leaveChatRoom(client: Socket, data: { nickname: string, roomId: string
}) {
const user = await this.usersModel.findOne({nickname: data.nickname});
client.broadcast.to(data.roomId).emit('users-changed', {user: user.nickname, event: 'left'}); // <3>
client.leave(data.roomId); }
#SubscribeMessage('add-message') // <5> async addMessage(client:
Socket, message: Message) {
message.owner = await this.usersModel.findOne({clientId: client.id});
message.created = new Date();
message = await this.messagesModel.create(message);
client.server.in(message.room as string).emit('message', message); } }
In your case, when you do not specify the path of the WS, it is listening on the same port as the server listening and path does not matter at all.
WS is a different protocol and you have to make the mental switch to this way of thinking.
You will not be able to test WS server with Postman. To do that you have to create your WS client, however, I strongly encourage you to create E2E tests with Nest to test it with the code, not manually.
Here's my working example:
import * as WebSocket from 'ws'
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [
SocketModule,
],
})
.compile()
app = moduleFixture.createNestApplication()
app.useWebSocketAdapter(new WsAdapter(app))
await app.init()
})
it('should connect successfully', (done) => {
const address = app.getHttpServer().listen().address()
const baseAddress = `http://[${address.address}]:${address.port}`
const socket = new WebSocket(baseAddress)
socket.on('open', () => {
console.log('I am connected! YEAAAP')
done()
})
socket.on('close', (code, reason) => {
done({ code, reason })
})
socket.on ('error', (error) => {
done(error)
})
})
Detail about this answer is discussed here
The second args of cb from onConnection
incomingMessage
this.server.on('connection', (socket:WebSocket, incomingMessage) => {
console.log(incomingMessage)
console.log('connection')
})
incomingMessage.url
'/v2/ta-websocket/22950b69-7928-43b9-8c38-efc5c126208e'
screen shot here

Categories

Resources