Mongoose `findOne` operation times out after 10000ms - node.js

This error is really frustrating especially as it is intermittent (it comes and goes). I'm using mongoose with express and typescript and I'm connecting to a database on MongoDB atlas. This is resulting into a bad user experience and it isn't really cool. The error message displayed says Operation wallets.findOne() buffering timed out after 10000ms. There are times the app runs fine and executes all operations as purposed but this error suddenly creeps out at some point. Below is my code and if you read through it, you'd see I'm using async await.
import mongoose from "mongoose";
import { Wallet } from "../../core/interfaces";
import { Tokenizers } from "../../core/utils";
export class WalletModel {
model: mongoose.Model<any>;
constructor() {
this.define();
}
private define() {
this.model = mongoose.model(
"Wallet",
new mongoose.Schema({
encryptedPrivateKey: String,
encryptedWallet: {
type: String,
required: true
}
})
);
}
async create(wallet: Wallet, privateKey: string): Promise<Wallet> {
const encryptedWallet = Tokenizers.encryptWallet(wallet);
const walletModel: any = await this.model.create({
encryptedPrivateKey: Tokenizers.encryptPrivateKey(
privateKey,
wallet.publicKey
),
encryptedWallet
});
const decryptedWallet: Wallet = Tokenizers.decryptWallet(
walletModel.encryptedWallet,
wallet.privateKey
);
return Promise.resolve(decryptedWallet);
}
async getWallet(privateKey: string, publicKey: string): Promise<Wallet> {
const encPrivateKey: string = Tokenizers.encryptPrivateKey(
privateKey,
publicKey
);
// Below find utilizes the indexing created on encryptedPrivateKey field
// and is way faster than linear search on whole collection
const encWallet: mongoose.Document & {
encryptedPrivateKey: string;
encryptedWallet: string;
} = (await this.model
.findOne({
encryptedPrivateKey: encPrivateKey
})
.lean()) as mongoose.Document & {
encryptedPrivateKey: string;
encryptedWallet: string;
};
const encryptedWallet: string = encWallet.encryptedWallet;
const decryptedWallet: Wallet = Tokenizers.decryptWallet(
encryptedWallet,
privateKey
);
return Promise.resolve(decryptedWallet);
}
async findByPrivateKey(privateKey: string): Promise<Wallet> {
let wallet: Wallet = null;
const allWallets = (await this.model.find().lean()) as any;
for (const doc of allWallets) {
const pk = Tokenizers.decryptPrivateKey(doc.encryptedPrivateKey);
if (pk === privateKey)
wallet = Tokenizers.decryptWallet(doc.encryptedWallet, privateKey);
}
return Promise.resolve(wallet);
}
async updateWallet(privateKey: string, newWallet: Wallet): Promise<Wallet> {
// const allWallets = await this.model.find();
let w: Wallet = null;
const updatedWallet = (await this.model
.findOneAndUpdate(
{
encryptedPrivateKey: Tokenizers.encryptPrivateKey(
privateKey,
newWallet.publicKey
)
},
{
encryptedWallet: Tokenizers.encryptWallet(newWallet)
},
{
new: true
}
)
.lean()) as mongoose.Document & {
encryptedWallet: string;
encryptedPrivateKey: string;
};
w = Tokenizers.decryptWallet(updatedWallet.encryptedWallet, privateKey);
return Promise.resolve(w);
}
}
Here's the content of the index.ts file where I'm starting all servers and making connections:
import express from "express";
import mongoose from "mongoose";
import config from "./config";
import { Environment } from "./env";
let app: express.Application = express();
const port = process.env.PORT || 7890;
app = config(app);
app.listen(port, async () => {
console.log(`Server listening on port ${port} in ${process.env.NODE_ENV}`);
const mongo = await mongoose.connect(
Environment.MONGO_URI[process.env.NODE_ENV],
{
useNewUrlParser: true,
useFindAndModify: false,
useCreateIndex: true
}
);
if (mongo) console.log("Connected to mongodb");
});
// Export app for tests
export default app;

try adding the index: true in your mongoose model. This can also happen if the database record is large, so making indexes can help find the document fast.

Related

Writing a database seeder class to seed multiple collections. (NodeJS, MongoDB)

Hi all I have been struggling all day with this and I was hopingsomeone might be able to assist me on figuring out the functionality. I'm still very new to asynchronous programming so any advice would be highly appreciated! I wrote two seeder classes which work great independently, and am opting to create a databaseSeeder class that I can use to run all my migrations from one file.
The issue I'm facing is that I need the UserSeeder to complete first because the ProductSeeder uses the ID's from the User model to link products to users. Please find my code for a reference:
User Seeder:
const mongoose = require("mongoose");
const User = require("../models/User");
const dotenv = require("dotenv");
const { faker } = require("#faker-js/faker");
const { v4: uuidv4 } = require("uuid");
dotenv.config();
const database = process.env.MONGOLAB_URI;
mongoose
.connect(database, {
useUnifiedTopology: true,
useNewUrlParser: true,
})
.then(() => console.log("User Collection Connected"))
.catch((err) => console.log(err));
const seedUserList = async () => {
let users = [];
for (let i = 0; i < 5; i++) {
const userSeeder = new User({
_id: uuidv4(),
firstname: faker.name.firstName(),
lastname: faker.name.lastName(),
username: faker.internet.userName(),
email: faker.internet.email(),
contact_number: faker.phone.number("### ### ####"),
password: faker.internet.password(),
address: faker.address.streetAddress(),
avatar: faker.image.people(1920, 1080, true),
rating: 3,
isVerified: false,
isValidated: false,
});
users.push(userSeeder);
}
const seedUsers = async () => {
await User.deleteMany({});
await User.insertMany(users);
};
seedUsers().then(() => {
console.log("Users Seeded Successfully!");
mongoose.connection.close();
});
};
seedUserList();
module.exports = {
seedUserList,
};
Product Seeder:
const mongoose = require("mongoose");
const Product = require("../models/Product");
const User = require("../models/User");
const dotenv = require("dotenv");
const { faker } = require("#faker-js/faker");
const { v4: uuidv4 } = require("uuid");
dotenv.config();
const database = process.env.MONGOLAB_URI;
mongoose
.connect(database, {
useUnifiedTopology: true,
useNewUrlParser: true,
})
.then(() => console.log("Products Collection Connected"))
.catch((err) => console.log(err));
const seedProductList = async () => {
let products = [];
let ids = [];
let idList = await User.find().select("_id");
idList.map((r) => r.toObject());
for (let i = 0; i < idList.length; i++) {
idList[i] = JSON.stringify(idList[i]);
ids.push(idList[i].substring(8, 44));
}
for (let i = 0; i < 25; i++) {
const productSeeder = new Product({
_id: uuidv4(),
name: faker.commerce.product(),
price: faker.commerce.price(),
description: faker.commerce.productDescription(),
image: faker.image.image(1920, 1080, true),
category: "Tools",
times_borrowed: faker.datatype.number(),
last_borrowed: faker.date.past(),
product_status: faker.helpers.arrayElement(["Available", "In Use"]),
user_id: faker.helpers.arrayElement(ids),
});
products.push(productSeeder);
}
const seedProducts = async () => {
await Product.deleteMany({});
await Product.insertMany(products);
};
seedProducts().then(() => {
console.log("Products Seeded Successfully!");
mongoose.connection.close();
});
};
seedProductList();
module.exports = {
seedProductList,
};
Database Seeder:
const { seedUserList } = require("./userSeeder");
const { seedProductList } = require("./productSeeder");
const mongoose = require("mongoose");
const seedDatabase = async () => {
seedUserList().then(() => {
console.log("Seeding Users!");
});
await seedProductList().then(() => {
console.log("Seeding Products!");
});
};
seedDatabase().then(() => {
console.log("Database Successfully Seeded!");
mongoose.connection.close();
});
Terminal Output
E:\Projects\Shopping-Platform>node src/seeders/databaseSeeder.js
Seeding Users!
User Collection Connected
Products Collection Connected
Seeding Products!
Database Successfully Seeded!
Products Seeded Successfully!
Users Seeded Successfully!
I've been playing around with the await functionality and wrapping each function with it's own await call so that the ProductSeeder waits for the UserSeeder to complete first before it executes, but so far no luck!

Cannot read property 'find' of undifined mongodb

I am starting to implement mongoose in a nodejs project. I have created a test record in a collection to test the CRUD operations from the back, I am trying to test the find() property of mongo but I am not sure how to do it.
This is my connection to mongoose:
const mongoose = require('mongoose');
const mongoURI: string = "mongodb://localhost:27017"
const mongoDB: string = "testdb"
export const setMongo = async() => {
try {
let mongodbURI: string = `${mongoURI}/${mongoDB}`
await mongoose.connect(mongodbURI);
console.log('conected DB')
} catch (error) {
console.log('error DB')
}
};
This is my Schema:
const mongoose = require('mongoose');
const companiesSchema = new mongoose.Schema ({
name: {
type: String,
required: true
},
phoneNumber: {
type: Number,
required: true,
unique: true
}
}, {
versionKey: false,
collection: 'companies'
});
module.exports = mongoose.model('Companies', companiesSchema);
This is my resposity.ts:
const companySchema = require("../../schemas/companies")
const db = companySchema.Companies
export class Repository {
public async getAll(): Promise<any> {
try {
console.log('getAll()')
const comp = await db.find({});
console.log(comp)
} catch (error) {
console.log(error)
}
}
}
This is the error it shows:
TypeError: Cannot read property 'find' of undefined
How should I create the connections or queries to mongo?
UPDATE
How can I get the total of the data with the find() method? Is it possible?
you just import your model in your controller and then you can use your query like:
const Companies = require("../../schemas/companies")
export class Repository {
public async getAll(): Promise<any> {
try {
console.log('getAll()')
const comp = await Companies.find({});
console.log(comp)
} catch (error) {
console.log(error)
}
}
}
and for get count of your result you can use .count() after your query to count your result :
const comp = await Companies.find({}).count();

axios.get() isn't fetching the data at client side

I have been trying to implement a LIKE system for my social media application with react at client-side and express at server-side. I'm trying to fetch the current LIKE stats using "axios" within a "useEffect". But, the data returned after "GET" request is an empty array instead of actual data. The data is being saved to mongoDB and it could be fetched using "POSTMAN", but cannot be fetched from the client-side. Any fixes?
Like.jsx
// Client-side where LIKE stats are fetched
import axios from 'axios';
import { useContext, useEffect, useState } from 'react';
import { useLocation } from 'react-router';
import { Context } from '../../context/Context';
import './reactions.css'
export default function Reactions() {
const LIKE = "LIKE"
const { user } = useContext(Context)
const location = useLocation()
const postId = location.pathname.split("/")[2]
const [likes, setLikes] = useState(0)
const [refresh, setRefresh] = useState(false)
useEffect(() => {
const fetchReactions = async () => {
const likeRes = await axios.get(`/reactions/all`, {
data: {
postId,
reactionType: LIKE
}
})
console.log("Like res data", likeRes.data) // The logged array is empty :(
setLikes(likeRes.data.length)
}
fetchReactions()
}, [postId, refresh])
const handleReact = async (reactionType) => {
const newReaction = {
reactionType,
username: user.username,
postId,
}
try {
const res = await axios.post("/reactions", newReaction)
console.log(res.data) // The logged object has data :)
setRefresh(!refresh)
} catch(err) {
console.log(err)
}
}
Like.js
// Reaction model at server-side
const mongoose = require("mongoose")
const ReactionSchema = new mongoose.Schema(
{
reactionType: {
type: String,
required: true,
},
username: {
type: String,
required: true,
},
postId: {
type: String,
required: true,
}
},
{
timestamps: true,
}
)
module.exports = mongoose.model("Reaction", ReactionSchema)
likes.js
// API for creating and fetching LIKES
const router = require("express").Router()
const Reaction = require("../models/Reaction")
// CREATE REACTION
router.post("/", async (req, res) => {
const newReaction = new Reaction(req.body)
try {
const savedReaction = await newReaction.save()
res.status(200).json(savedReaction)
} catch(err) {
res.status(500).json(err)
}
})
// GET REACTIONS OF A POST
router.get("/all", async (req, res) => {
try {
const reactions = await Reaction.find({
postId: req.body.postId,
reactionType: req.body.reactionType,
})
res.status(200).json(reactions)
} catch(err) {
res.status(500).json(err)
}
})
module.exports = router
Thanks to #cmgchess for making me think about my approach of making a "GET" request with request body.
I changed the body parameters to query parameters and it did WORK :)
changed Like.jsx
// Client-side where LIKE stats are fetched
import axios from 'axios';
import { useContext, useEffect, useState } from 'react';
import { useLocation } from 'react-router';
import { Context } from '../../context/Context';
import './reactions.css'
export default function Reactions() {
const LIKE = "LIKE"
const { user } = useContext(Context)
const location = useLocation()
const postId = location.pathname.split("/")[2]
const [likes, setLikes] = useState(0)
const [refresh, setRefresh] = useState(false)
useEffect(() => {
const fetchReactions = async () => {
// ---- DIFF OPEN ----
const search = `?postId=${postId}&reactionType=${LIKE}`
const likeRes = await axios.get(`/reactions${search}`)
// ---- DIFF CLOSE ----
console.log("Like res data", likeRes.data) // The logged array has data :)
setLikes(likeRes.data.length)
}
fetchReactions()
}, [postId, refresh])
const handleReact = async (reactionType) => {
const newReaction = {
reactionType,
username: user.username,
postId,
}
try {
const res = await axios.post("/reactions", newReaction)
console.log(res.data) // The logged object has data :)
setRefresh(!refresh)
} catch(err) {
console.log(err)
}
}
changed likes.js
// API for creating and fetching LIKES
const router = require("express").Router()
const Reaction = require("../models/Reaction")
// CREATE REACTION
router.post("/", async (req, res) => {
const newReaction = new Reaction(req.body)
try {
const savedReaction = await newReaction.save()
res.status(200).json(savedReaction)
} catch(err) {
res.status(500).json(err)
}
})
// GET REACTIONS OF A POST
// --- DIFF OPEN ---
router.get("/", async (req, res) => {
const postId = req.query.postId
const reactionType = req.query.reactionType
try {
const reactions = await Reaction.find({
postId,
reactionType
})
// --- DIFF CLOSE ---
res.status(200).json(reactions)
} catch(err) {
res.status(500).json(err)
}
})
module.exports = router

GraphQL Mongoose with MongoDB Atlas return empty array

I'm trying to connect MongoDB Atlas and mongoose but server always returns an empty array '[ ]'. When I load a mock data everything works (Mocked data and MongoDB Atlas have the same values)
I have connect to MongoDB Atlas, but can't get some data.
Controller:
const express = require('express')
const { ApolloServer } = require('apollo-server-express')
const { createServer } = require('http')
const mongoose = require('mongoose')
const typeDefs = require('./schema')
const resolvers = require('./resolvers')
const app = express()
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: 'http://localhost:3000/graphql',
settings: {
'editor.theme': 'light'
}
}
})
server.applyMiddleware({ app })
const httpServer = createServer(app)
server.installSubscriptionHandlers(httpServer)
mongoose
.connect(`mongodb+srv://<username>:<password>#cluster0.brdqp.mongodb.net/${process.env.mongoDatabase}?retryWrites=true&w=majority`, { useNewUrlParser: true, useUnifiedTopology: true })
.then((res) => {
httpServer.listen(3000, () => {
console.log('connected!')
})
})
.catch((err) => {
console.error('Error while connecting to MongoDB', err);
})
Schema:
const { gql } = require('apollo-server-express')
const typeDefs = gql`
type Todo {
_id: String,
title: String,
description: String
status: String
date: String
}
type Query {
todos: [Todo]
}
`
module.exports = typeDefs
Resolvers:
const { Todo, Restaurant, Customer, Order } = require('./models')
const pubsub = require('./pubsub')
const resolvers = {
Query: {
todos(parent, args, context, info) {
return Todo.find()
.then(todo => {
return todo.map(r => ({ ...r._doc }))
})
.catch(err => {
console.error(err)
})
},
},
}
module.exports = resolvers
MongoDB Atlas collection: (Database name : node, collection name in DB : todo)
_id : 5c9bdb721c9d440000345d62
title : "question"
desctiption : "test test"
status: done
date: 1
But the server always returns [ ]
How???? Why&?

TypeError: Cannot read property 'shawarmaOrdered' of undefined

I am new to GraphQL and mongoose and trying to create a function in a resolver which creates a shawarma order, the inputs required are the shawarma ID, the quantity and an address, when I run the code typed below, I get an Error which states
TypeError: Cannot read property 'shawarmaOrdered' of undefined
the resolver code:
import mongoose from 'mongoose';
import AuthenticationError from 'apollo-server';
import {shawarma} from '../models/shawarmaModel';
export default {
Query:{
},
Mutation: {
createOrder: async(parent, {OrderInput}, {models: {orderModel, shawarmaModel}}, info) => {
console.log('reached1')
try{
const {shawarmaOrdered, quantity, address} = OrderInput;
const order = await orderModel.updateOne({shawarmaOrdered, quantity, address})
return order
} catch(err){
console.log('errorr')
console.log(err)
}
}
},
Order: {
shawarmaOrdered: async(parent, {shawarmaOrdered}, {models:shawarmaModel}, info) =>{
return shawarmaOrdered = shawarmaModel.find((shawarma)=>{
return shawarma.id == parent.id
})
}
}
the schema code:
import {gql} from 'apollo-server'
export default gql`
type Order{
id: ID!
shawarmaOrdered: Shawarma!
quantity: Int
orderedTo: String
}
input OrderInput{
shawarmaOrdered: ID!
quantity: Int
orderedTo: String
}
extend type Query {
order(id: ID!): Order!
}
extend type Mutation {
createOrder(shawarmaOrdered: String!, quantity: Int!, orderedTo: String!): Order!
}
`
the order model code:
import mongoose, { Mongoose } from 'mongoose'
import shawarma from './shawarmaModel'
const orderSchema = new mongoose.Schema({
shawarmaOrdered: {
type: mongoose.Schema.Types.ObjectId,
ref: shawarma
},
quantity: {
type: Number
},
completed: {
type: Boolean,
default: false
}
})
const order = mongoose.model('order', orderSchema)
export default order;
kindly let me know if I'm doing something wrong
So here's how I fixed the problem.
in the index.js file
import cors from 'cors';
import express from 'express';
import jwt from 'jsonwebtoken';
import mongoose from 'mongoose';
import { ApolloServer, AuthenticationError } from 'apollo-server-express';
import schemas from './schemas';
import resolvers from './resolvers';
import userModel from './models/userModel';
import orderModel from './models/orderModel';
import shawarmaModel from './models/shawarmaModel';
const app = express();
app.use(cors());
const getUser = async (req) => {
const token = req.headers['token'];
// console.log(token)
if (token) {
try {
return await jwt.verify(token, 'riddlemethis');
} catch (e) {
console.log(e)
throw new AuthenticationError('Your session expired. Sign in again.');
}
}
};
const server = new ApolloServer({
typeDefs: schemas,
resolvers,
context: async ({ req }) => {
if (req) {
const me = await getUser(req);
return {
me,
models: {
userModel,
orderModel, //this was where the orderModel was misspelled as OrderModel
shawarmaModel
},
};
}
},
});
server.applyMiddleware({ app, path: '/graphql' });
app.listen(5000, async () => {
await mongoose.connect('mongodbconnectionString')
});
in the code above, I have commented the section in which the error was from. Carelessly, when debugging I overlooked checking this file. my mistake. I apologise for not including this code in the original question

Resources