What is the best way to handle querystrings in an express api? - node.js

I am a beginner nodejs developer, working on developing an express rest api with optional query params.
For example consider the following schema for a user:
phone: {
countryCode: String,
number: phoneType
},
phoneVerified: { type: Boolean, default: false },
emailVerified: { type: Boolean, default: false },
rating: Number,
balance: { type: Number, default: 0 },
occupation: String,
gender: { type: String, enum: genders },
I want to expose this resource at /users and allow querying through optional query strings.
For ex, /users?emailVerified=true&phoneverified=false&gender=male&occupation=plumber&limit=10
This should return all the users which satisfy the criteria, while keeping almost all of the options optional.
What is the best way to do this in a maintenable and futureproof way?
Appraoch 1: My first approach was to use if blocks to check which parameters exist in the query and build mongoose queries accordingly, but that looks ugly and very hard to read.
queryObj = {};
if (req.query.occupation) {
queryObject = {
...queryObject,
occupation: req.query.occuption
};
}
if (req.query.phoneVerified) {
queryObject = {
...queryObject,
phoneVerified: req.query.phoneVerifed
};
}
const users = await User.find(queryObject);
I also found the querymen package which looks promising. If someone experinced could guide me as to what is the best practice? Thanks in advance

you are doing it in right way. you can also try
queryObject = {};
if (req.query.params.occupation) {
queryObject.occupation= req.params.occuption
}
if (req.params.phoneVerified) {
queryObject.phoneVerified= req.params.phoneVerifed
}
const users = await User.find(queryObject);
you can add new properties to json using "."

Related

How to query an attribute from an object inside of an array with Mongoose

I am trying to filter a list of 'professionals' using model.find() from Mongoose. The query I have works normally in MongoDB Compass but with Mongoose it does nothing.
The model goes like this:
{
name: string,
email: string,
password: string,
phoneNumber: number,
city: string,
myDescription: string,
specialty: [
{ name: string, certificate: string, isCertified: boolean }
],
image: { profile: string, jobs: [strings] },
availability: Boolean
}
The query in MongoDB Compass is { specialties: { $elemMatch: { name: 'Soldador General', isCertified: true } } } and it returns the list of 'professionals' as expected. When I use it in Mongoose it returns the entire list, so no filter.
Going through the documentation of Mongoose there is a method Query.elemMatch() but the problem is that the filter is dynamic, I want the users to filter by city, specialty or both, and also I might add some filters in the future. For now the queries look something like:
{ specialties: { $elemMatch: { name: 'Soldador General', isCertified: true } } }
{ city: 'Barranquilla' }
{ city: 'Barranquilla', 'specialties': { $elemMatch: { name: 'Soldador General', isCertified: true } } }
And so I don't see how to do this dynamically with Query.elemMatch(). BTW I retrieve the object using React useSearchParams and send the request via NodeJS.
Any help or guidance is highly appreciated!

How to elegantly PATCH changes into mongodb without path conflicts

So I'm attempting to do a PATCH request to a MongoDB store, by updating an element in an sub-array. This is what the data looks like:
const booksSchema = mongoose.Schema(
{
name: { type: String, trim: true },
author: { type: String, trim: true },
},
{ timestamps: true },
)
const Book = mongoose.model('Book', bookSchema)
const librarySchema = mongoose.Schema(
{ books: [ Books.schema ] },
{ timestamps: true },
)
const Library = mongoose.model('Library', librarySchema)
And this is how I'm updating the data:
const library = await Library.findOneAndUpdate(
{ _id: libraryId, "books._id": bookId },
{ $set: { "books.$": bookBody } },
)
And in the process, I've run into problems with the automated timestamp update conflicting with the $set command:
Updating the path 'books.$.updatedAt' would create a conflict at 'books.$'
The best solution I've come across to make this type of update without removing the automated timestamp update is to manually set each field, like this:
const library = await Library.findOneAndUpdate(
{ _id: libraryId, "book._id": bookId },
{
$set: {
"book.$.name": bookBody.name,
"book.$.author": bookBody.author,
}
},
)
...but this solution is very verbosely.
Is there a more elegant way to do this type of PATCH request, perhaps using some kinda of object destructuring, that doesn't require maintaining a mapping of each field?
In fact, if your intention is to perform a PATCH operation the way it's described in HTTP protocol:
The HTTP PATCH request method applies partial modifications to a
resource.
Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH
You should go with the "verbose" solution in your case, because your alternative update { $set: { "books.$": bookBody } } will delete any property missing in the update from the updated book. It's not how PATCH should work, it's how PUT is intended to work.
You can make your code a little less verbose by introducing a helper function performing something like this (example uses lodash):
import _ from 'lodash'
_.mapValues(book, (value) => {
_.mapKeys(value, (innerValue, innerKey) => (
`book.$.${innerKey}`
)
)

Update Mongoose Array

so basically I have this and I am trying to update the STATUS part of an array.
However, everything I try does nothing. I have tried findOneAndUpdate also. I am trying to identify the specific item in the array by the number then update the status part of that specific array
(Sorry for formatting, I have no idea how to do that on the site yet ...) (Full code can be found here: https://sourceb.in/0811b5f805)
Code
const ticketObj = {
number: placeholderNumber,
userID: message.author.id,
message: m.content,
status: 'unresolved'
}
let tnumber = parseInt(args[1])
let statuss = "In Progress"
await Mail.updateOne({
"number": tnumber
}, { $set: { "status": statuss } })
Schema
const mongoose = require('mongoose')
const mailSchema = new mongoose.Schema({
guildID: { type: String, required: true },
ticketCount: { type: Number, required: true },
tickets: { type: Array, default: [] }
}, { timestamps: true });
module.exports = mongoose.model('Mail', mailSchema)
You need to use something like Mail.updateOne({"guildID": message.guild.id}, {$set: {`tickets.${tnumber}.status`: statuss}})
or for all objects in array:
Mail.updateOne({"guildID": message.guild.id}, {$set: {'tickets.$[].status': statuss}})
Also, you need to create a schema for the tickets, as it is described in docs:
one important reason to use subdocuments is to create a path where there would otherwise not be one to allow for validation over a group of fields

Best way to partial update nested object data in mongodb in node js

I have searched many questions on nested objects, but all I found where related to array[s].
I am looking for a updating simple nested object in mongoose.
Once created a document, I want to partially update basicDetails and agentDetail object.
Example schema :
var applicationSchema = new Schema({
title: String,
author: String,
basicDetails:{
appId:Number,
fname:String,
mname:String,
lname:String
},
agentDetails:{
agentName:String,
bankName:String,
bankCode:Number,
agentCode:Number
}
comments: [{ body: String, date: Date }],
createdDate: { type: Date, default: Date.now }
})
I Want to update only fname and last name from basicDetails,title, bankName and agentCode from agentDetails.
Request is :
{
title:Update,
basicDetails:{
fname:Neeraj,
lname:singh
},
agentDetails:{
bankName:String,
agentCode:Number
}
}
There is no document for the same that I could find.
Just use the dot notation
db.collection.findOneAndUpdate(
{ // match your document }
{ $set: { "basicDetails.fname": newName, "agentDetails.title": newTitle } }
)

How to sort array of embedded documents via Mongoose query?

I'm building a node.js application with Mongoose and have a problem related to sorting embedded documents. Here's the schema I use:
var locationSchema = new Schema({
lat: { type: String, required: true },
lon: { type: String, required: true },
time: { type: Date, required: true },
acc: { type: String }
})
var locationsSchema = new Schema({
userId: { type: ObjectId },
source: { type: ObjectId, required: true },
locations: [ locationSchema ]
});
I'd like to output the locations embedded in the userLocations documented sorted by their time attribute. I currently do the sorting in JavaScript after I retrieved the data from MongoDb like so:
function locationsDescendingTimeOrder(loc1, loc2) {
return loc2.time.getTime() - loc1.time.getTime()
}
LocationsModel.findOne({ userId: theUserId }, function(err, userLocations) {
userLocations.locations.sort(locationsDescendingTimeOrder).forEach(function(location) {
console.log('location: ' + location.time);
}
});
I did read about the sorting API provided by Mongoose but I couldn't figure out if it can be used for sorting arrays of embedded documents and if yes, if it is a sensible approach and how to apply it to this problem. Can anyone help me out here, please?
Thanks in advance and cheers,
Georg
You're doing it the right way, Georg. Your other options are either to sort locations by time upon embedding in the first place, or going the more traditional non-embedded route (or minimally embedded route so that you may be embedding an array of ids or something but you're actually querying the locations separately).
This also can be done using mongoose sort API as well.
LocationsModel.findOne({ userId: theUserId })
// .sort({ "locations.time": "desc" }) // option 1
.sort("-locations.time") // option 2
.exec((err, result) => {
// compute fetched data
})
Sort by field in nested array with Mongoose.js
More methods are mentioned in this answer as well
Sorting Options in mogoose
Mongoose Sort API

Resources