GraphQL Mongoose promise best practices - node.js

Using GraphiQL, I'm able to update a user with the following command (1).
I'm able to return the actual user with the service (3).
Is there simpler/more compact way of returning the user after an update? (seen how easy it is when creating a user).
(1) GraphiQL mutation command
mutation {
updateUser(id: "1", firstName: "Bob"){
firstName
}
}
(2) the mutation:
const mutation = new GraphQLObjectType({
updateUser: {
type: UserType,
args: {
id: { type: new GraphQLNonNull(GraphQLString) },
firstName: { type: GraphQLString },
age: { type: GraphQLInt },
companyId: { type: GraphQLString }
},
resolve(parentValue, args){
return UserService.updateUser(args);
}
},
})
(3) the service:
function updateUser(args) {
var {id} = args;
return User.findOne({id})
.then(user => {
if(!user){throw new Error('No user found')}
return User.update({id}, args)
.then(() => {
return User.findOne({id})
})
})
},
function addUser(args) {
const user = new User(args);
return user.save(args);
}

Assuming you're using mongoose:
function updateUser(args) {
var {id} = args;
return await User.findOneAndUpdate({ id }, args, { new: true });
},

Related

MongoDB array of objects update

I'm trying to update array of user information objects in mongoose.
I've stored the user core information in the login process, I want to update some of user information when user tries to make an order.
Here is the code for the model
const mongoose = require('mongoose');
const { ObjectId } = mongoose.Schema;
const userSchema = new mongoose.Schema(
{
name: String,
email: {
type: String,
required: true,
index: true,
},
role: {
type: String,
default: 'subscriber',
},
info: [
{ country: String },
{ city: String },
{ address: String },
{ phone: String },
{ birthdate: Date },
{ gender: { type: String, enum: ['Male', 'Female'] } },
],
// wishlist: [{ type: ObjectId, ref: "Product" }],
},
{ timestamps: true }
);
module.exports = mongoose.model('User', userSchema);
In my controller I'm getting the data from front-end react app as JSON format, I want to push some data to info which is an array of objects in the users model above.
exports.createOrder = async (req, res) => {
// Here I constract the data
const { plan, service, fullName, country, city, address } = req.body.order;
const { user_id } = req.body;
// This the method I tried
try {
const user = await User.updateOne(
{
_id: user_id,
},
{
$set: {
'info.$.country': country,
'info.$.city': city,
'info.$.address': address,
},
},
{ new: true }
);
if (user) {
console.log('USER UPDATED', user);
res.json(user);
} else {
res.json((err) => {
console.log(err);
});
}
const newOrder = await new Order({
orderPlan: plan,
orderService: service,
orderUser: user_id,
}).save();
console.log(newOrder);
console.log(req.body);
} catch (error) {
console.log(error);
}
};
I tired other solutions like
const user = await User.updateOne(
{
_id: user_id,
info: { $elemMatch: { country, city, address } },
},
{ new: true }
);
So do I need to reformat my model or there is a way to update this array of objects?
Option 1
Use $[]
db.collection.update(
{},
{ $set: { "info.$[i].country": "a1" }} ,
{ arrayFilters: [ { "i.country": "a" } ] }
)
Demo - https://mongoplayground.net/p/UMxdpyiKpa9
Option 2
if you know the index
Demo - https://mongoplayground.net/p/41S7qs6cYPT
db.collection.update({},
{
$set: {
"info.0.country": "a1",
"info.1.city": "b1",
"info.2.address": "c1",
"info.3.phone": "d1"
}
})
Suggestions -
Change info schema to object instead of an array

Mongoose: how to only populate, sort and return a nested object?

I have a User schema, with a messages array. The message array is filled by conversations id and referenced to a Conversation schema.
I want to fetch all conversations from a user, sort them by unread and then most recent messages. Finally, I must only return an array of lastMessage object.
For the moment, I have only managed to populate the whole user object.
Here is the Conversation Schema:
const conversationSchema = new mongoose.Schema(
{
name: { type: String, required: true, unique: true },
messages: [{ message: { type: String }, authorId: { type: String } }],
lastMessage: {
authorId: { type: String },
snippet: { type: String },
read: { type: Boolean },
},
},
{ timestamps: true }
);
conversationSchema.index({ name: 1 });
module.exports = mongoose.model("Conversation", conversationSchema);
And here is my code:
router.get("/conversations", async (req, res) => {
try {
const { userId } = req.query;
const user = await User.findById({ _id: userId }).populate("messages");
.sort({ updatedAt: 1, "lastMessage.read": 1 });
return res.json({ messages: user.messages });
} catch (err) {
console.log("error", err);
return res.json({ errorType: "unread-messages-list" });
}
});
How to do this?

Accessing a schema inside a schema using Express Router and MongoDG

I'm trying to create a route where it takes in a parameter for a username and then displays that users information. Only thing is, the username is in the user schema from when the user signs up. The profile schema references the user schema. How do I use the username parameter in the findOne call to display the users profile data?
User schema:
const UserSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model("users", UserSchema);
Profile schema:
const ProfileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "users"
},
name: {
type: String
},
image: {
type: String
},
bio: {
type: String
},
location: {
type: String
},
website: {
type: String
},
social: {
youtube: {
type: String
},
facebook: {
type: String
},
instagram: {
type: String
},
twitter: {
type: String
}
}
});
module.exports = User = mongoose.model("profile", ProfileSchema);
Route:
router.get("/user/:username", (req, res) => {
const errors = {};
Profile.findOne({ user: req.params.user.username })
.populate("user", "username")
.then(profile => {
if (!profile) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile);
})
.catch(err => res.status(404).json(err));
});
Please try this :
router.get("/user/:username", async (req, res) => {
const errors = {};
try {
const profile = await User.aggregate([
{ $match: { username: req.params.username } },
{ $lookup: { from: "profile", localField: "_id", foreignField: "user", as: "userProfile" } },
{ $project: { userProfile: { $arrayElemAt: ["$userProfile", 0] }, username: 1, _id:0 } }
]).exec();
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
} catch (error) {
console.log('Error in retrieving user from DB ::', error);
return res.status(404);
}
})
Try using aggregate, firstly you check-in user table for getting details of a specific username then fetch the profile details as below using lookup, if no profile found after unwind the document will not be fetched and you can check on aggregate result's length as aggregate always return an array in result :
User.aggregate([
{$match:{ username: req.params.user.username }},
{$lookup:{from:"profile",localField:"_id",foreignField:"userId",as:"profileData"}},
{$unwind:"$profileData"},
{$project:{profileData:1,username:1}}
{$limit:1}
])
.then(profile => {
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
})
You can do it in 2 steps.
Look for users containing username in userSchema, get it's id.
Then in promise, use that id to, look for profileSchema contains.
router.get("/user/:username", (req, res) => {
users.findOne({ username: req.params.username }).then(_user=>{
profile.findOne({ user: _user._id }).populate('user').then(_profile => {
res.json(_profile);
})
})
});
This code will look for username in userSchema and look for userSchema's id in profileSchema then returns profileSchema populated with user.

Graphql execute where condition only if the argument is passed

I am using graphql in node js for my oracle database wherein I connect to the remote database and fetch some details. I am fairly new to these technologies so please pardon me. I have a customer table with below schema:
const Customer = new GraphQLObjectType({
description: 'Customer data schema',
name: 'Customer',
fields: () => ({
name: {
type: GraphQLString,
sqlColumn: 'NAME',
},
city: {
type: GraphQLString,
sqlColumn: 'CITY'
},
country: {
type: GraphQLString,
sqlColumn: 'COUNTRY'
},
gender: {
type: GraphQLString,
sqlColumn: 'GENDER'
},
emp_id: {
type: GraphQLString,
sqlColumn: 'EMP_ID'
}
})
});
Customer._typeConfig = {
sqlTable: 'CUSTOMER',
uniqueKey: ['NAME','EMP_ID']
}
Using join monster I create my Query root as:
const QueryRoot = new GraphQLObjectType({
description: 'global query object',
name: 'RootQuery',
fields: () => ({
customer: {
type: new GraphQLList(Customer),
args: {
emp_id: {
description: 'Emp Id',
type: GraphQLString
},
name: {
description: 'Customer Name',
type: GraphQLString
}
},
where: (customer, args, context) => {
return `${customer}."EMP_ID" = :emp_id AND ${customer}."NAME" = :name`;
},
resolve: (parent, args, context, resolveInfo) => {
return joinMonster(resolveInfo, context, sql => {
console.log('joinMaster', sql);
return database.simpleExecute(sql, args,{
outFormat: database.OBJECT
});
});
}
}
})
})
When I pass my query in graphql in browser with emp_id and name parameters I get data. But there are cases when I cannot pass any parameters and would want all the rows to be fetched.
When I do not send the parameters I get error as:
ORA-01008 : Not all variables bound
I want the arguments to be optional, and if I don't send them then it should return all rows.
Thank you.
Both the where and resolver functions are passed an args argument. This will have the parameter names and values if any. You can use that argument to build a dynamic where clause. Here's an untested example:
const QueryRoot = new GraphQLObjectType({
description: 'global query object',
name: 'RootQuery',
fields: () => ({
customer: {
type: new GraphQLList(Customer),
args: {
emp_id: {
description: 'Emp Id',
type: GraphQLString
},
name: {
description: 'Customer Name',
type: GraphQLString
}
},
where: (customer, args, context) => {
if (Object.keys(args).length === 0) {
return false;
}
let whereClause = '1 = 1';
if (args.emp_id != undefined) {
whereClause += `\n AND ${customer}."EMP_ID" = :emp_id`;
}
if (args.name != undefined) {
whereClause += `\n AND ${customer}."NAME" = :name`;
}
return whereClause;
},
resolve: (parent, args, context, resolveInfo) => {
return joinMonster(resolveInfo, context, sql => {
console.log('joinMaster', sql);
return database.simpleExecute(sql, args,{
outFormat: database.OBJECT
});
});
}
}
})
})
Since the where clause would then match the number of arguments, you shouldn't get the ORA-01008 error.

Creating a document and adding to set in same route

I have the following route:
app.post('/accounts', (req, res) => {
obj = new ObjectID()
var account = new Account({
name: req.body.name,
_owner: req.body._owner
}
)
return account.save()
.then((doc) => {
Account.update(
{
"_id": account._id
},
{
$addToSet: {
subscriptions: obj
}
}
)
})
.then((doc) => {
res.send(doc)
}
)
});
I am trying to create a document and then update a field in it (array) with a created objectID. When I call this route the new document is created however the new objectID is not being added to the subscription set.
Here is my model:
var Account = mongoose.model('Account', {
name: {
type: String,
required: true,
minlength: 1,
trim: true
},
_owner: {
type: mongoose.Schema.Types.ObjectId,
required: true
},
subscriptions: [{
type: mongoose.Schema.Types.ObjectId,
required: true
}]
module.exports = {
Account
};
if you have found the id and subscriptions is array then
app.post('/accounts', (req, res) => {
obj = new ObjectID()
var account = new Account({
name: req.body.name,
_owner: req.body._owner
});
return account.save()
.then((doc) => {
return Account.update( //return was missing which was causing the issue because of promise chain
{
"_id": account._id
},
{
$addToSet: {
subscriptions: obj
}
}
)
}).then((doc) => {
res.send(doc)
}
)
});

Resources