How to query a single document from a Mongodb collection with react - node.js

I'm trying to build a search bar with a react frontend and node backend, that will let me search a customer ID from a mongoDB collection, then pull all of the data from a single document down from within the collection and display it on my react app.
Currently, I am just trying to get to get the single document bit to work, if this is possible. At the moment, it pulls down the entire collection.
My current Node code:
Search router
const express = require('express');
const app = express();
const tfiPaintCodesRouter = express.Router();
const PaintInfoSchema = require('../models/PaintInfoSchema.js');
tfiPaintCodesRouter.route('/get').get(function (req, res) {
const tfipaintcode = new PaintInfoSchema(req.body);
console.log(req.body)
tfipaintcode.save()
.then(tfipaintcode => {
res.json('Got data!!');
})
.catch(err => {
res.status(400).send("unable to get data");
console.log('CustomerID is required', err.res);
});
});
tfiPaintCodesRouter.route('/').get(function (req, res) {
PaintInfoSchema.find(function (err, tfipaintcodes){
if(err){
console.log('this is an error!', err.res);
}
else {
res.json(tfipaintcodes);
}
});
});
module.exports = tfiPaintCodesRouter;
Mongo schema using mongoose.
const mongoose = require('mongoose')
var uniqueValidator = require('mongoose-unique-validator');
const Schema = mongoose.Schema;
// Create schema
const PaintInfoSchema = new Schema({
customerID: {
required: true,
index: true,
unique: true,
type: String
},
companyName: {
index: true,
type: String
},
curtainCodes: {
index: true,
type: String
},
sinageCodes: {
index: true,
type: String
},
Notes: {
index: true,
type: String
},
Method: {
index: true,
type: String
},
},{
collection: 'tfiPaintCodes'
});
PaintInfoSchema.plugin(uniqueValidator);
module.exports = mongoose.model('PaintInfoSchema', PaintInfoSchema)
My current react code is:
import React from 'react';
import { Form, FormGroup, Input, Container, Row, Col } from 'reactstrap';
import './Search.css'
import axios from 'axios'
class Search extends React.Component {
constructor(props) {
super(props)
this.state = {
searchInfo: []
};
}
handleInputChange = (event) => {
event.preventDefault();
const { value } = event.target;
console.log('Value', value)
this.setState({
query: value
});
this.search(value);
};
search = query => {
axios.get('http://localhost:3001/getData')
.then(res =>{
const searchInfo = (res.data || []).map(obj => ({
company: obj.companyName,
sinage: obj.sinageCodes,
method: obj.Method,
notes: obj.Notes}));
this.setState({ searchInfo });
})
};
componentDidMount() {
this.search("");
}
render() {
return(
<Container>
<Form>
<Row>
<Col md={{ size: 6 ,offset: 3}}>
<FormGroup className="SearchBar">
<Input onChange={this.handleInputChange} type="search" name="search" id="exampleSearch" placeholder="search" />
</FormGroup>
</Col>
</Row>
</Form>
<ul>
{this.state.searchInfo.map(function(searchInfo, index){
return (
<div key={index}>
<h1>NAME: {searchInfo.company}</h1>
<p>{searchInfo.sinage}</p>
<p>{searchInfo.method}</p>
<p>{searchInfo.notes}</p>
</div>
)
}
)}
</ul>
</Container>
);
}
}
export default Search
The code above queries mongodb, then pulls down all of the data stored in my collection, here is an image of the returned data.
Data displayed in frontend
But i want to know if it is possible to just pull down one document in that collection, so it would just display one Name: and then the other 4 bits of data.
I have the data stored in Mlab, here is a screenshot of the documents stored in my collection.
data in mongodb
Is this possible? Thanks!

The best way is to pull only one document from the DB (if you don't need more in your case).
Mongoose, as any other ORM/ODM, gives you those options:
https://mongoosejs.com/docs/api.html#model_Model.findOne
With FindOne you can search for documents but get only one (aka. "the first found") document back.
If you need a fixed number of returned documents, you can use limit(10) to, for example, return only 10 documents.
Though it appears to me that your code-snippets don't show the exact segment where do the query in Mongoose, otherwise we could have shown you what to do in your own example.

Related

Where to pass an objectId as a property when deleting an entry?

I have an app that allows users to add notes, and I'm trying to add a delete functionality to the page. My route:
router.route('/:id').delete((req, res) => {
Note.findByIdAndDelete(req.params.id)
.then(() => res.json('Exercise deleted!'))
.catch(err => res.status(err).json('Error ' + err))
})
works when I test it in Postman, but I haven't managed to get the ObjectId from the database. It throws an error: Invalid status code: CastError: Cast to ObjectId failed for value "undefined" (type string) at path "_id" for model "Note" .
This is my Note schema:
const noteSchema = new Schema({
category: {type: String, required: false},
title: {type : String, required: true},
content: {type: String, required: true},
noteID: { type: mongoose.SchemaTypes.ObjectId, required: true, index: true }
}, {
timestamps: true,
})
This is my Note component:
import React from "react";
function Note(props) {
function handleClick() {
props.onDelete(props.id);
}
return (
<div className="note">
<h1>{props.title}</h1>
<p>{props.content}</p>
<button onClick={handleClick}>
Delete
</button>
<p>{props.category}</p>
</div>
);
}
export default Note
my App component:
function App() {
const [notes, setNotes] = useState([]);
useEffect(() => {
fetch('http://localhost:5000/notes')
.then(res => res.json())
.then(json => setNotes(json))
}, [])
function deleteNote(id) {
axios.delete('http://localhost:5000/notes/'+id)
.then(response => { console.log(response.data)});
}
{notes.map((noteItem, index) => {
return (
<Note
key={index}
//id={index}
title={noteItem.title}
content={noteItem.content}
category={noteItem.category}
onDelete={deleteNote}
/>
);
I'm not sure where to pass the id from the database, I tried passing it as a parameter in App.js (deleteNote(note.id)) or some variation of it, but it doesn't work. Could someone please tell me which step I'm missing to get the ObjectId? I also tried passigng noteItem._id when mapping notes to the Note component, but that deletes all notes at once. I tried these solutions as well: https://stackoverflow.com/questions/71544895/how-do-i-solve-casterror-cast-to-objectid-failed-for-value-undefined-type-s and https://stackoverflow.com/questions/63253129/successfully-delete-an-object-in-mongodb-using-findbyidanddelete-but-returns-an but I still get errors.
Thanks in advance!
Two issues.
CastError: Cast to ObjectId failed for value "undefined" (type string) at path "_id" for model "Note"
First of all, the id you are getting is undefined. This may cause problems, check if your client is sending the id properly, either via logging or debugging. If you pass in a correct string, Mongoose should automatically cast it for you.
If it didn't work, try using mongoose.Types.ObjectId(req.params.id)
I finally figured it out! I also put everything in the Note component to avoid any confusion, and through that I discovered what the problem was my endpoint was incorrect: instead of <button onClick={handleClick}> I had to turn it into an arrow function to call handleClick correctly and pass noteItem._id. I also got rid of the noteID property in the schema.
This is the Note component now:
import React, {useState,useEffect} from "react";
import axios from "axios";
function Note(props) {
const [notes, setNotes] = useState([])
useEffect(() => {
fetch('http://localhost:5000/notes')
.then(res => res.json())
.then(json => {console.log(json)
setNotes(json)})
}, [])
function deleteNote(id) {
axios.delete(`http://localhost:5000/notes/${id}`)
.then(() => { console.log("Note successfully deleted")});
}
return (
<div>
{notes.map((noteItem, index) => {
return (
<div className="note">
<h1>{noteItem.title}</h1>
<p>{noteItem.content}</p>
<button onClick={() => {deleteNote(noteItem._id)}}>
Delete
</button>
<p>{noteItem.category}</p>
</div>
);
})}
</div>
)
}
export default Note

Is there any method to store array values in MongoDB's field?

I have a schema in which in one of the field i wanted it to store array of values. The schema is given below:
const memberSchema=new mongoose.Schema({
id:{
type:String,
unique:true
},
prefCurrency:{
type:String,
default:'AUD',
required:false
},
});
In the front end part, The user will select multiple currencies and which can be stored in the prefCurrency field of schema. The front end code is given below:
export default function MemberInformation() {
const { t } = useTranslation();
const[memberData,setMemberData]=useState([]);
const [member,setMember]=useState({id:"",prefCurrency:""})
var name,valueV;
const handleInputs=e=>{
console.log("Updated ",member)
name=e.target.name;
valueV=e.target.value;
setMember({...member,[name]:valueV})
}
const postData= ()=>{
setMemberData({...memberData,...member})
const {id,prefCurrency}=member;
var UpdatedMemInfo ={id,prefCurrency};
axios.put('/memberInfoUpdate', UpdatedMemInfo)
.then( res => {
alert('Updated successfully!');
}
)
.catch(err => {
console.log(err.response);
alert('An error occurred! Try submitting the form again.');
});
}
useEffect(() => {
async function fetchBooks() {
const response = await fetch('/memberinfo');
const json = await response.json();
setMemberData(json.memberLogin);
setMember(json.memberLogin);
console.log(json.memberLogin)
}
fetchBooks();
},[]);
return (
<Form.Select aria-label="Floating label select example" name="prefCurrency" value={member.prefCurrency} onChange={e=>handleInputs(e)}>
<span><ReactCountryFlag countryCode="AU" svg style={myStyle}/></span>
<option value="AUD" name="prefCurrency">AUD</option>
<option value="CAD" name="prefCurrency">CAD</option>
<option value="CHF" name="prefCurrency">CHF</option>
<option value="CNY" name="prefCurrency">CNY</option>
</Form.Select>
<Button variant="success" onClick={()=>postData()}>
Save Changes
</Button>
)
}
As in the above code, only one value can be selected and stored into the MongoDB but i want select multiple values and store in the form of array in the prefCurrency field of the schema and then retrieve it from the database to display it. What will be the code changes here?
The API for posting the above data in database is given below:
router.put('/memberInfoUpdate', async (req, res) => {
const {id,prefCurrency}=req.body;
var _id = req.body.id;
var UpdatedMemInfo = {
id:id,
prefCurrency:prefCurrency
};
Member.findOneAndUpdate(_id, UpdatedMemInfo, { new: true }, function(
err,
UpdatedMemInfo
) {
if (err) {
console.log("err", err);
res.status(500).send(err);
} else {
console.log("success");
res.send(UpdatedMemInfo);
}
});
});
The above update API is just for one value in the prefCurrency field but i want to have multiple selected values in it.
The prefCurrency Schema would look like this for storing array values.
const memberSchema=new mongoose.Schema({
id:{
type:String,
unique:true
},
prefCurrency:{
type:Array,
default:'AUD',
required:false
},
});
And the query for updating the prefCurrency is
var UpdatedMemInfo = {
id:id,
prefCurrency:[prefCurrency]
};
This will take array of values and update it.

Req.body returns undefined : ExpressJs, NodeJs

Please help me I'm having this error for 5 days.
I'm trying to delete data inside of my array on MongoDB
but my req.body returns undefined even though I have my body-parser. I'm using axios.patch for request.
It works well in my postman but once I sent data that's where the problem occurs.
Here's my axios api call.
export const deleteTask = (id,post) => api.patch(`/main/${id}`, post);
Here's my schema.
const todoSchema = mongoose.Schema({
username: {
type: String,
},
password: {
type: String,
},
task: [String],
time: {
type: Date,
default: Date.now,
}
});
const TodoModels = mongoose.model('TodoModels', todoSchema);
here's my query.
export const deleteTask = async (req,res) => {
const { id } = req.params;
console.log(req.body);
if(!mongoose.Types.ObjectId.isValid(id))
return res.status(404).json(`Invalid ID`);
await TodoModels.findByIdAndUpdate(id,{$pull:{ task: req.body.task }},{
new: true });
}
My req.body has no task and I don't know why. Once I send data it returns undefined but the ID from req.params is not undefined.
Also once I sent the data from client to backend/server req.body returns this { data: '' } the data I sent became the element. I believe it was supposed to be { task: 'data' }
If your deleting a record then why are you using findByIdAndUpdate ; it should be findByIdAndDelete. I have put a sample code you to refer. There are 2ways you can delete a record. You can try them out and see.
Way 1:
router.delete('/:id', [auth, admin, validateObjectId], async(req, res) => {
//check for existing genre
const movieGenre = await Genre.findByIdAndDelete(req.params.id);
if (!movieGenre) {
return res.status(404).send('No such movie genre found with given id.');
}
res.send(movieGenre);
})
Way 2:
router.delete('/:id', [auth, admin, validateObjectId], async(req, res) => {
//second way to delete
let movieGenre = await Genre.findById(req.params.id);
if (!movieGenre) {
return res.status(404).send('No such movie genre found with given id.');
}
await movieGenre.deleteOne();
const index = genres.indexOf(movieGenre);
genres.splice(index, 1);
res.send(movieGenre);
})
Hope the answer helps you in any way.

Accessing child methods in parent for mongoose succeeds in array and fails with single child

UPDATE : Solution is at bottom of question
I have an express site using mongoose.
I'll greatly simplify to say that I have adults, kids, and house models. When I create methods on kids, I can call them from within methods on adults and get a result. I can also call them from my .ejs views. However, when I create methods on house, I can only get a result from my .ejs views and get undefined when called from within methods on adults. Example code follows.
adult.js
const mongoose = require('mongoose');
const adultSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
kids: [{type: mongoose.Schema.Types.ObjectId, ref: 'Kid', required: true}]
house:{type: mongoose.Schema.Types.ObjectId, ref: 'House', required: true}
});
adultSchema.method({
getKidsDescription: function() {
if (this.kids.length < 1) {
return 'No kids yet';
} else {
let ev = 'Kids, aged: ';
let kds = this.kids;
kds.forEach(function(k){
ev = ev + 'k.getAge()' // works
})
return ev;
}
},
getHouseDescription: function(){
return 'A fabulous house on '+this.house.getFullStreet(); // does not work
}
})
module.exports = mongoose.model('Adult', adultSchema);
kid.js
const mongoose = require('mongoose');
const kidSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
birthdate: {type:Date}
});
kidSchema.method({
getAge: function() {
return (Math.floor(new Date() - this.birthdate)/(1000*60*60*24*365))
},
})
module.exports = mongoose.model('Kid', kidSchema);
house.js
const mongoose = require('mongoose');
const houseSchema = mongoose.Schema({
name: { type: String },
city: {type: String},
street: {type:String}
});
houseSchema.method({
getFullStreet: function() {
return this.street + ' Road';
},
})
module.exports = mongoose.model('House', houseSchema);
When I make a query for theAdult, it looks like this:
controller.js
exports.main = async (req, res, next) => {
if (req.theAdult) {
try {
const found = await db.fetchAdult(req.theAdult._id)
res.render('/main', {
//theHouse: found.house //below I show this working
});
} catch(e) {
throw new Error(e.message)
}
} else {
res.redirect('/');
}
}
db.js
exports.fetchAdult = (id) => {
return Adult.findById(id)
.populate({ path: 'kids'})
.populate({ path: 'house'})
.exec()
.then(doc => {
return doc;
});
}
Assuming house is passed to view as an object when rendered (commented out above), this works
view.ejs
<p> <%= theHouse.getFullStreet() %></p>
Assuming house populated on the call to load the Adult, this returns undefined.
view.ejs
<p> <%= theAdult.house.getFullStreet() %></p>
At the same time, both of these work
view.ejs
<ul> <% theAdult.kids.forEach(function(k) { %>
<li><%= k.getAge() %> </li>
<% }); %>
</ul>
<p> <% theAdult.getKidsDescription() %> </p>
What I am not understanding is how the method calls work for objects in array and work in the view but do not work for objects on in an array. This is a single child error (for me). If it did not work in the view, I would assume that the method getFullStreet() was the problem, but it works in the view. If the array methods could not be called within the parent, I would assume the issue was with trying to access getFullStreet() in the parent.
What am I missing?
SOLUTION
I was fetching theAdult in my call to show view.ejs, but I was then actually relying on currentAdult which referred to req.adult and did not have the fields populated. My solution was to add a pre hook to the adult schema that always populates house on find.
in adult.js
adultSchema.pre('find', function() {
this.populate('house')
})
Have you tried passing a hydrated theAdult? It might only see the ObjectID, without any other data or methods.

How to do soft delete with mongodb using nodejs

I'm able to delete data from the view , but at the sametime its getting deleted from mongodb which shouldn't happen.
I tried mongoose-soft-delete plugin to perform soft delete, but it isn't working
//schema
var mongoose= require('mongoose');
let softDelete = require('mongoosejs-soft-delete');
var Schema=mongoose.Schema;
var newblogSchema=new Schema({
user_id:Number,
title:String,
description:String,
summary:String,
hashtag:String
})
var newblogs=mongoose.model('NewBlog',newblogSchema);
newblogSchema.plugin(softDelete);
module.exports=newblogs;
//html template
<table>
<tr>
<th>Title</th>
<th>Description</th>
<th>Summary</th>
<th>HashTags</th>
</tr>
<tr *ngFor="let blog of blogs;">
<td >{{blog.title}}</td>
<td [innerHtml]="blog.description| safeHtml">{{blog.description}}</td>
<td>{{blog.summary}}</td>
<td>{{blog.hashtag}}</td>
<td> <a routerLink="/blog"><button type="button"
(click)="editblog(blog._id,blog.title,blog.description,blog.summary,blog.hashtag)">
Edit</button></a>
<td><button type="button" (click)="deleteblog(blog._id)">Delete</button>
</tr>
</table>
//ts file
deleteblog(blogid) {
var result = confirm('Want to delete?');
if (result === true) {
this.blogservice.deleteblog(blogid).subscribe(response => {this.blogs = response; });
}
//service
deleteblog(blogid):Observable<any>{
return Observable.create(observer=>{
this.http.post('http://localhost:4000/api/deleteblog', {_id: blogid}, {headers: new HttpHeaders({'Content-Type':'application/json'})}
)
.subscribe((response:Response)=>{
observer.next(response);
observer.complete();
});
});
}
//api.js
router.post('/deleteblog',(req,res)=>{
var body=req.body;
newblog.findByIdAndRemove({_id:body._id},(error,newblog)=>{if(error){
console.log(error);
}
else{
return res.json({message:'deleted',data:newblog});
}
});
});
Now the data is getting deleted from view as well as mongodb.
Expected result is to delete data only from the view and not from mongodb
we can implement soft delete with plugin, middleware and $isDeleted document method
soft delete plugin code:
import mongoose from 'mongoose';
export type TWithSoftDeleted = {
isDeleted: boolean;
deletedAt: Date | null;
}
type TDocument = TWithSoftDeleted & mongoose.Document;
const softDeletePlugin = (schema: mongoose.Schema) => {
schema.add({
isDeleted: {
type: Boolean,
required: true,
default: false,
},
deletedAt: {
type: Date,
default: null,
},
});
const typesFindQueryMiddleware = [
'count',
'find',
'findOne',
'findOneAndDelete',
'findOneAndRemove',
'findOneAndUpdate',
'update',
'updateOne',
'updateMany',
];
const setDocumentIsDeleted = async (doc: TDocument) => {
doc.isDeleted = true;
doc.deletedAt = new Date();
doc.$isDeleted(true);
await doc.save();
};
const excludeInFindQueriesIsDeleted = async function (
this: mongoose.Query<TDocument>,
next: mongoose.HookNextFunction
) {
this.where({ isDeleted: false });
next();
};
const excludeInDeletedInAggregateMiddleware = async function (
this: mongoose.Aggregate<any>,
next: mongoose.HookNextFunction
) {
this.pipeline().unshift({ $match: { isDeleted: false } });
next();
};
schema.pre('remove', async function (
this: TDocument,
next: mongoose.HookNextFunction
) {
await setDocumentIsDeleted(this);
next();
});
typesFindQueryMiddleware.forEach((type) => {
schema.pre(type, excludeInFindQueriesIsDeleted);
});
schema.pre('aggregate', excludeInDeletedInAggregateMiddleware);
};
export {
softDeletePlugin,
};
you can use as global for all schemas
mongoose.plugin(softDeletePlugin);
or for concrete schema
For Soft delete, you should maintain an active flag column that should only contain values as 0 and 1.
This way, you could analyse whether a record is deleted or not.
While displaying, add another clause for displaying only the records that have flag value 1. And while deleting, just update that flag's value to 0.
This would do the job.
For Example, here user 2 is deleted. with activeFlag as 0.
ID memberID userStatus groupCode activeFlag
1 user1 1 4455 1
2 user2 1 4220 0
3 user3 2 4220 1
Try to use https://www.npmjs.com/package/soft-delete-mongoose-plugin
A simple and friendly soft delete plugin for mongoose.

Resources