How do I reference documents from other collection in express - node.js

I have 2 collections here >>course & author
I need to prepare my course schema in such a way that it references the author and at the post request I only need to put the id.I am using #joi13 This is what I have done so far.
course schema
const mongoose = require('mongoose')
const Joi = require('joi')
Joi.objectId= require('joi-objectid')(Joi)
// schema
const courseSchema = new mongoose.Schema({
...
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Author'
}
})
// the model
const Course = mongoose.model('Courses', courseSchema)
// validation
const courseValidation = (course) => {
const schema = {
...
authorId: Joi.objectId().required()
}
return Joi.validate(course, schema)
}
the course router
const {Course, validate} = require('../models/course')
const express = require('express')
const router = express.Router()
const {Author} = require('../models/author')
// post
router.post('/', async (req, res) => {
const {error} = validate(req.body)
if (error) return res.status(400).send(error.details[0].message)
const title = await Course.findOne({title : req.body.title})
if (title) return res.status(400).send('That user exists')
const author = await Author.find()
if (!author) return res.status(404).send('No such Author')
let course = new Course({
...
author: {
_id: author._id,
username: author.username
}
})
try {
course = await course.save()
res.send(course)
}
catch(er){
console.log(er)
}
})
The error

At line, const author = await Author.find() will return array of authors
while creating course at following lines of you are using author._id which will be undefined, So you have to find a specific Author using findOne (return a single author as object) or you have to use an indexed element of an author like author[0]._id
let course = new Course({
...
author: {
_id: author._id,
username: author.username
}
})

Related

getting internal Server error and empty Object

made an API for Social media Application to get timeline posts on the app, I requested from Thunder Client
request query is like: http://localhost:8000/post/63d4051a252ef6c3d50d7f17/timeline, where userId is this :63d4051a252ef6c3d50d7f17,
i did log userId and correctly got Data Document from mongoDB
try {
const currentUserPost = await PostModel.find({ userId: userId });
console.log( currentUserPost )
}
but still getting empty object {} error is: Internal server error status code 500 on thunder client,
check out once below code, and let me know what's wrong and why. it seems good to me but it isn't working.
PostControler.js
const PostModel = require("../Models/Postmodels");
const mongoose = require("mongoose");
const UserModel = require("../Models/Usermodel");
/ get timeLine post
const getTimeLinePost = async (req, res) => {
const userId = req.params.id;
try {
const currentUserPost = await PostModel.find({ userId: userId });
console.log( currentUserPost )
const followingPost = await UserModel.aggregate([
{
$match: {
_id: new mongoose.Types.ObjectId(userId),
}
},
{
$lookup: {
from: "posts",
localField: "following",
foreignField: "userId",
as: "followingPosts",
}
},
{
$project: {
followingPost: 1,
_id: 0,
}
},
]);
res.status(200).json(currentUserPost.concat(...followingPost[0].followingPost)
.sort((a,b)=>{
return b.createdAt - a.createdAt
}));
} catch (error) {
res.status(500).send(error);
}
};
module.exports = { createPost,getPost, updatePost, deletePost, likesDislikesPost, getTimeLinePost };
PostRoute.js
const express = require("express");
const {createPost,getPost, updatePost, deletePost, likesDislikesPost, getTimeLinePost} = require("../Controller/PostControler");
const router = express.Router();
router.post('/', createPost)
router.get('/:id', getPost)
router.put('/:id', updatePost)
router.delete('/:id', deletePost)
router.put('/:id/like', likesDislikesPost)
router.get('/:id/timeline', getTimeLinePost)
module.exports = router
index.js
const express = require("express")
require('dotenv').config()
const bodyparser = require("body-parser")
const mongoose = require("mongoose")
const app = express();
const Authroute = require("./Routes/Authroute")
const UserRoute = require("./Routes/userRout")
const PostRout = require("./Routes/PostRoute")
.
.
.
.
.
.
app.use("/auth",Authroute)
app.use("/user",UserRoute)
app.use("/post", PostRout)

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!

How to add data inside nested array in mongodb

I am using mongoose for database functionalities in my nodejs project.Below is my model.
Here is the POST request:
In MongoDb data is saving like this :
Here owers array is empty.
expense.js
const mongoose = require('mongoose');
const ExpenseSchema = new mongoose.Schema({
userid:{
type: String,
required: true
},
owers:[{
owerid:{
type: String
},
amt:{
type: Number
}
}],
name:{
type: String,
required: true
},
amount:{
type: Number,
require: true
}
});
const expense = mongoose.model('expense',ExpenseSchema);
module.exports = expense;
Whenever I am trying to insert something array is showing empty.Below is my code:
addExpense.js
const expense = require('../models/expense.js');
const addExpense = async (req,res) => {
const {name,amount,owerid,amt} = req.body;
console.log(name + " " + owerid);
const {userid} = req.params;
const expens = new expense({userid,name,amount});
try{
const data = await expens.save();
expens.owers.push({"owerid":owerid,"amt":amt});
res.send({"id":data._id});
}
catch(error){
res.send(error);
}
};
module.exports = {addExpense};
Someone let me know what I am doing wrong.
Try This
const {name,amount,owers} = req.body;
console.log(name + " " + owerid);
const {userid} = req.params;
const expens = new expense({userid,name,amount});
try{
const data = await expens.save();
//After you can push multiple data like that
JSON.parse(owers).map((value) => {
data.owers.push({
owerid: value.owerid,
amt: value.amt
})
})
data.save()
res.send({"id":data._id});
}
catch(error){
res.send(error);
}

NodeJs Express API, trying to adding data/updating data to MongoDB but getting "Cannot read property '_id' of undefined"

I am trying to add a new collection, using the same ObjectId from the my users collection that was already created. But when I run the API, I get the following error Cannot read property '_id' of undefined
index.js
const express = require('express');
const authRoutes = require('./auth.routes');
const profileRoutes = require('./profile.routes');
const router = express.Router();
router.use('/auth', authRoutes);
router.use('/profile', profileRoutes);
module.exports = router;
profile.routes.js
const express = require('express');
const profileCtrl = require('../controllers/profile.controller');
const router = express.Router();
router
.route('/')
.post(profileCtrl.create)
.put(profileCtrl.update)
.get(profileCtrl.read)
.delete(profileCtrl.remove);
module.exports = router;
BaseCrudController.js
class BaseCrudController {
constructor(dataService, varName) {
if (!dataService) {
throw new Error('Data service not found', 500);
}
this.varName = varName;
this.dataService = dataService;
this.create = this.create.bind(this);
this.update = this.update.bind(this);
}
create(req, res, next) {
return this.dataService
.create(req.user, req.body)
.then((item) => res.json(item))
.catch(next);
}
update(req, res, next) {
return this.dataService
.update(req.user, req[this.varName], req.body)
.then((item) => res.json(item))
.catch(next);
}
BaseCrudService.js
const _ = require('lodash');
const mongoose = require('mongoose');
const APIError = require('../utils/api-error');
const BaseService = require('./BaseService');
class BaseCrudService extends BaseService {
constructor(
modelName,
safeFields = [],
adminFields = [],
userIdField = null,
populatedFields = [],
listPoluateField = ''
) {
super();
this.modelName = modelName;
this.safeFields = [...safeFields];
this.fields = [...safeFields];
this.adminFields = [...adminFields];
this.userIdField = userIdField;
this.populatedFields = [...populatedFields];
this.listPoluateField = listPoluateField;
this.model = mongoose.model(this.modelName);
this.create = this.create.bind(this);
this.update = this.update.bind(this);
}
_getFiedlNames(user) {
//maybe checking roles later
return [...this.safeFields];
}
create(user, data, extraData = {}) {
const Model = this.model;
const createData = {};
const fields = this._getFiedlNames(user);
if (this.userIdField) {
createData[this.userIdField] = user._id;
}
const item = new Model(
Object.assign(createData, _.pick(data, fields), extraData)
);
return item.save();
}
update(user, item, data) {
const fields = this._getFiedlNames(user);
const updateData = _.pick(data, fields);
Object.assign(item, updateData);
return item.save();
}
profile.model.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
const profileSchema = new Schema({
user: { type: Schema.ObjectId, ref: 'User', required: true },
contactEmail: {
type: String,
required: true,
},
isEnabled: {
type: Boolean,
default: false,
},
about: {
type: String,
default: '',
},
portfolioUrl: {
type: String,
default: '',
},
jobTitle: {
type: String,
default: '',
},
resumeUrl: {
type: String,
default: '',
},
});
module.exports = mongoose.model('Profile', profileSchema);
Then when I try to test this in Postman, I get the following stack "TypeError: Cannot read property '_id' of undefined\n at ProfileService.create (/Users/tj/aydensoft/upwork-ABDS/portfolios/portfolios/services/BaseCrudService.js:46:43)\n at ProfileController.create (/Users/tj/aydensoft/upwork-ABDS/portfolios/portfolios/controllers/BaseCrudContoller.js:20:8)\n
Also the users collection automatically gets the _id:ObjectId("someNumber") when a user is added.
I tried many different ways but it ends up adding a totally different _id:ObjectId("someNumber") instead of the one matching _id:ObjectId("someNumber") in the users collection.

How to use Mongoose with GraphQL and DataLoader?

I am using MongoDB as my database and GraphQL. I am using Mongoose for my model. I realised my GraphQL queries are slow because the same documents are being loaded over and over again. I would like to use DataLoader to solve my problem, but I don't know how.
Example
Let's say I have the following schema, describing users with friends :
// mongoose schema
const userSchema = new Schema({
name: String,
friendIds: [String],
})
userSchema.methods.friends = function() {
return User.where("_id").in(this.friendIds)
}
const User = mongoose.model("User", userSchema)
// GraphQL schema
const graphqlSchema = `
type User {
id: ID!
name: String
friends: [User]
}
type Query {
users: [User]
}
`
// GraphQL resolver
const resolver = {
Query: {
users: () => User.find()
}
}
Here is some example data in my database :
[
{ id: 1, name: "Alice", friendIds: [2, 3] },
{ id: 2, name: "Bob", friendIds: [1, 3] },
{ id: 3, name: "Charlie", friendIds: [2, 4, 5] },
{ id: 4, name: "David", friendIds: [1, 5] },
{ id: 5, name: "Ethan", friendIds: [1, 4, 2] },
]
When I do the following GraphQL query :
{
users {
name
friends {
name
}
}
}
each user is loaded many times. I would like each user Mongoose document to be loaded only once.
What doesn't work
Defining a "global" dataloader for fetching friends
If I change the friends method to :
// mongoose schema
const userSchema = new Schema({
name: String,
friendIds: [String]
})
userSchema.methods.friends = function() {
return userLoader.load(this.friendIds)
}
const User = mongoose.model("User", userSchema)
const userLoader = new Dataloader(userIds => {
const users = await User.where("_id").in(userIds)
const usersMap = new Map(users.map(user => [user.id, user]))
return userIds.map(userId => usersMap.get(userId))
})
then my users are cached forever rather than on a per request basis.
Defining the dataloader in the resolver
This seems more reasonable : one caching mechanism per request.
// GraphQL resolver
const resolver = {
Query: {
users: async () => {
const userLoader = new Dataloader(userIds => {
const users = await User.where("_id").in(userIds)
const usersMap = new Map(users.map(user => [user.id, user]))
return userIds.map(userId => usersMap.get(userId))
})
const userIds = await User.find().distinct("_id")
return userLoader.load(userIds)
}
}
}
However, userLoader is now undefined in the friends method in Mongoose schema. Let's move the schema in the resolver then!
// GraphQL resolver
const resolver = {
Query: {
users: async () => {
const userLoader = new Dataloader(userIds => {
const users = await User.where("_id").in(userIds)
const usersMap = new Map(users.map(user => [user.id, user]))
return userIds.map(userId => usersMap.get(userId))
})
const userSchema = new Schema({
name: String,
friendIds: [String]
})
userSchema.methods.friends = function() {
return userLoader.load(this.friendIds)
}
const User = mongoose.model("User", userSchema)
const userIds = await User.find().distinct("_id")
return userLoader.load(userIds)
}
}
}
Mh ... Now Mongoose is complaining on the second request : resolver gets called again, and Mongoose doesn't like 2 models being defined with the same model name.
"Virtual populate" feature are of no use, because I can't even tell Mongoose to fetch models through the dataloader rather than through the database directly.
Question
Has anyone had the same problem? Does anyone have a suggestion on how to use Mongoose and Dataloader in combination? Thanks.
Note: I know since my schema is "relational", I should be using a relational database rather than MongoDB. I was not the one to make that choice. I have to live with it until we can migrate.
Keep your mongoose schema in a separate module. You don't want to create your schema each request -- just the first time the module is imported.
const userSchema = new Schema({
name: String,
friendIds: [String]
})
const User = mongoose.model("User", userSchema)
module.exports = { User }
If you want, you can also export a function that creates your loader in the same module. Note, however, that we do not want to export an instance of a loader, just a function that will return one.
// ...
const getUserLoader = () => new DataLoader((userIds) => {
return User.find({ _id: { $in: userIds } }).execute()
})
module.exports = { User, getUserLoader }
Next, we want to include our loader in the context. How exactly this is done will depend on what library you're using to actually expose your graphql endpoint. In apollo-server, for example, context is passed in as part of your configuration.
new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({
userLoader: getUserLoader()
}),
})
This will ensure that we have a fresh instance of the loader created for each request. Now, your resolvers can just call the loader like this:
const resolvers = {
Query: {
users: async (root, args, { userLoader }) => {
// Our loader can't get all users, so let's use the model directly here
const allUsers = await User.find({})
// then tell the loader about the users we found
for (const user of allUsers) {
userLoader.prime(user.id, user);
}
// and finally return the result
return allUsers
}
},
User: {
friends: async (user, args, { userLoader }) => {
return userLoader.loadMany(user.friendIds)
},
},
}

Resources