mongoose Model.save() only returns { _id, __v } - javascript

I know this question has been asked before but I feel like I'm doing everything right and I'm still having an issue. I want to save an item from a form into my mongodb collection using mongoose.
My schema:
// stationmodel.js
export const StationSchema = new mongoose.Schema({
"FDID": String,
"Fire dept name": String,
"HQ addr1": String,
"HQ city": String,
"HQ state": String,
"HQ zip": Number,
"HQ phone": String,
"Dept Type": String,
"Organization Type": String,
"Website": String,
"Number Of Stations": Number,
"Primary agency for emergency mgmt": Boolean,
}, {collection: "FEMA_stations"})
In my express app:
// in routes.js
const StationSchema = require('./stationmodel')
const Station = mongoose.model('Station', StationSchema, 'FEMA_stations')
const addstation = (req, res) => {
console.log(req.body)
const newStation = new Station(req.body)
newStation.save( function(err){
if (err) { console.error(err) }
console.log('newStation after save', newStation)
})
}
const routes = app => {
app.route('/api/addstation')
.post(addstation)
}
export default routes
// in index.js
import routes from './routes'
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
routes(app)
In my front end code, calling to the backend in a redux action:
fetch('/api/addstation', {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(stationToAdd)
})
When I console.log(req.body) on my back end, I get the data I'm expecting. It looks something like this:
{
FDID: '202020',
'Fire dept name': 'Some Fire Department',
'HQ addr1': 'Some address',
'HQ city': 'San Dimas',
'HQ state': 'CA',
'HQ zip': 99999,
'HQ phone': '5555555555',
'Dept Type': 'Career',
'Organization Type': 'State',
Website: '',
'Number Of Stations': 0,
'Primary agency for emergency mgmt': true,
}
But when I console.log my newStation that I'm trying to .save(), all I get is a response like this:
{ _id: 5efe29911ea067248f3c39a0, __v: 0 }
I know other people had issues with their schema, their model, making sure that they're truly connected to their mongodb collection, or making sure that the request is made with the application/json header, but I feel I have all those things right. The code was pieced together from a much more modularized app to try to cut the fat and present the core issue, so let me know if I'm missing any glaring information.
What might be going wrong here? Why is the data from req.body not making it into my new document that I'm trying to save to the collection? Thanks for reading.

You are mixing es6 module import/export with Node.js CommonJS require.
In stationmodel.js You are using a "named export"
export const StationSchema = new mongoose.Schema(...
But in routes.js you are using CommonJS require
const StationSchema = require('./stationmodel')
Which is likely to be an empty object. So the following line will create a model with an "empty" schema
const Station = mongoose.model('Station', StationSchema, 'FEMA_stations')
The solution
use named import instead
import { StationSchema } from './stationmodel'
TIP:
Since you are already name the file stationmodel.js, which suggests it's a model. You can put the following in stationmodel.js directly to prevent the model from getting incorrect schema
export const Station = mongoose.model('Station', StationSchema, 'FEMA_stations')

Related

Mongoose and Next.js: Unhandled Runtime Error TypeError: Cannot read properties of undefined (reading 'Token')

I basically defined this Model, much like another which doesn't error out; So I am stumped as to why it's not working...
Here is a Minimal, Reproducible Example
Not working:
import mongoose from 'mongoose';
const TokenSchema = new mongoose.Schema({
_userId: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
token: { type: String, required: true },
createdAt: { type: Date, required: true, default: Date.now, expires: 43200 }
});
export default mongoose.models.Token || mongoose.model('Token', TokenSchema);
Working:
import mongoose from 'mongoose';
import emailValidator from 'email-validator'
import bcrypt from 'bcrypt'
import crypto from 'crypto'
const SALT_ROUNDS = 12;
const UserSchema = new mongoose.Schema(
{
username: {
type: String,
required: true,
trim: true,
lowercase: true,
index: { unique: true },
validate: {
validator: emailValidator.validate,
message: props => `${props.value} is not a valid email address!`
}
},
password: {
type: String,
required: true,
trim: true,
index: { unique: true },
minlength: 7,
maxlength: 11
},
roles: [{ type: 'String' }],
isVerified: { type: Boolean, default: false },
passwordResetToken: String,
resetPasswordExpires: Date
},
{
timestamps: true
}
);
UserSchema.pre('save', async function preSave(next) {
const user = this;
if (!user.isModified('password')) return next();
try {
const hash = await bcrypt.hash(user.password, SALT_ROUNDS);
user.password = hash;
return next();
} catch (err) {
return next(err);
}
});
UserSchema.methods.generatePasswordReset = function () {
this.resetPasswordToken = crypto
.randomBytes(20)
.toString('hex');
this.resetPasswordExpires = Date.now() + 3600000; // expires in an hour
};
UserSchema.methods.comparePassword = async function comparePassword(candidate) {
return bcrypt.compare(candidate, this.password);
};
export default mongoose.models.User || mongoose.model('User', UserSchema)
Also I'm following this example in the Next.js Examples repo.
Please help! :)
Apparently the TypeError: Cannot read properties of undefined (reading 'Token') is happening because the code is executing on the client side when it is intended to execute on the server side.
What is really happening is that mongoose has not initiated a connection to the database, and so mongoose.models is undefined. But the fix isn't to try to initiate a db connection:
I too was having the a similar issue, when I tried to define too many things in the same file, that is... In my case I'm trying to define Typescript interfaces that can be pulled into client-side code, but the mongoose model definitions end up executing as well...
I read that NextJS does a fair bit of work to split up code that gets sent to client-side and what stays on server-side... The developer should keep this in mind and try to split things that relate to client-side and server-side into different files.
In my case, I placed the Interface definitions and my custom Hooks in a different file from the Mongoose Schema definitions; and then imported only the Interfaces and Hooks when I need those, which made the errors go away.
Trying to keep everything in the same place sounds logical and neat, but doesn't work in this case.
I copied your code and it worked fine (went into the tokens collections versus token like expected possibly) one thing I noticed was the expires field on createdAt - was this a NextJS field? It's not a default field so just curious. Also, can you paste the exact error you are encountering, this will help someone track the issue down.
{
_userId: new ObjectId("5e1a0651741b255ddda996c4"),
token: 'abcd123',
createdAt: 2021-09-24T23:10:24.288Z,
_id: new ObjectId("614e5ae04c741f91ac062530"),
__v: 0
}
Also, consider using the timestamps options property when declaring the model as this will save you the headache of setting createdAt up (and you can also have updatedAt automatically update).
token: { type: String, required: true },
},
{
timestamps: true
}
);
I had the same error. Even though my schema file was correct. the issue was for some reason, I have been importing the model in react component file
import room from "../models/room";

Mongoose : Cast to ObjectId failed for value "Some String" at path "_id"

New to MongoDB, Javascript stack and need help understanding cause of this error.
I have my model created :
const
Mongoose = require('mongoose');
Schema = Mongoose.Schema,
Model = Mongoose.model;
module.exports = Model('Project',
new Schema({
icon : String,
name : String,
state : String,
number : String
})
);
This is my MongoDB document :
[![MongoDB Document][1]][1]
I am attempting to receive all the documents in the collection when I call the API so therefore as per the Mongoose document I am using the find() method.
Here is my API Implementation:
const Project = require('../../models/project');
router.get('/projects/:page?/:limit?',
function(req, res, next){
const page = Math.max(req.params.page || 1, 1) - 1;
const limit = Math.max(req.params.limit || 20, 20);
//Verified : I am hitting the API
console.log("Reached API /projects");
Project.find()
.populate('icon')
.populate('name')
.populate('state')
.populate('number')
.limit(limit).skip(page * limit).exec(
function(err, project)
{
if (err) { return next(err); }
res.send(project);
}
); //End of exec()
}//End of unction
);
I am successful in making the API call using fetch() but I am receiving "Cast to ObjectId failed error" for all the String values.
I believe there is something really simple within my Mongo DB document that I might be missing. Please help me understand and solve this issue.
**EDIT ---
The error seems to point at the string values of the keys:
**
Thank you
Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). So you're Id cast is not valid, because of string, you need to have ObjectId, some changes need to be made before it, Let's debug:
const alldata = await Project.find()
console.log(alldata) // ?
does this return something, I'm using async await here if it return data then the problem is with your populate because your Id case isn't valid as you save in schema string and you're referring here populate, example of using populate:
module.exports = Model('Project',
new Schema({
icon : [{ type: Schema.ObjectId, ref: 'your icon document' }],
name : [{ type: Schema.ObjectId, ref: 'you name document' }],
state : [{ type: Schema.ObjectId, ref: 'state document' }],
number : [{ type: Schema.ObjectId, ref: 'number document' }]
})
);
but it seems to me that you don't need to use the populate because you have simple data, name, number... so you should be good to go with the above example
Resources: mongoose fetching data, using populate, relation

Populating an Object with Information from a Child Object with Different Model (JS/Mongoose)

Here is what I have. I created a project model that references a user model for an array of members.
var ProjectSchema = new mongoose.Schema(
{title: {
type: String,
required: true
},
members: [
{user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users'
}
}],
});
User Schema (I have code that creates a model from both of these schemas)
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
}
});
In a separate file, I want to export the JSON of a found project and include the information of the users in the members array, but I am not sure how to do that. This is the code I have right now.
const project = await Project.findById(req.params.proj_id).populate(
'members'
);
res.json(project);
It has no trouble finding the project but the only information I can get on the members is their id. I tried using for loops to gather the information from members separately using the id that I can get from the project, but the code gets messy and I am hoping to find a simpler way to do it.
You can use mongoose populate query to get all members associated with a project. It should populate array of objects users associated to a project. You should be doing something like this:
const project = await Project.findById(req.params.proj_id)
await project.populate('members').execPopulate()
console.log(project.members)
Mongoose docs for the reference: Populate
You can give the model to your mongoose populate.
const project = await Project.findById(req.params.proj_id)
.populate({
'members',
model: UserModel
})
.exec()
res.json(project);
keep in mind you've to create UserModel just like
const UserModel = mongoose.model('User', userSchema);

swagger array of objects not validating in nodejs

I have a swagger.yaml file in nodejs application given below
/updateuser/{userId}:
x-swagger-router-controller: User
put:
tags:
- User
summary: Update User
description: Update User
operationId: updateUser
parameters:
- name: userId
in: path
description: userId for which subscription needs to be updated
type: string
required: true
- name: subData
in: body
description: Subscription To be updated
schema:
type: array
items:
$ref: "#/definitions/userDataInput"
responses:
"200":
description: Success
schema:
$ref: "#/definitions/Response"
"500":
description: Error
schema:
$ref: "#/definitions/ErrorResponse"
definitions:
userDataInput:
required:
- productId
- subscriptionId
properties:
productId:
type: string
subscriptionId:
type: string
Now I'm validating subData in my user controller using nodejs module swagger-model-validator for example given below code
var yaml = require('js-yaml')
var fs = require('fs')
var swaggerObject
try {
swaggerObject = yaml.safeLoad(fs.readFileSync('swagger.yaml'), 'utf8')
} catch (err) {
// Error here
}
var Validator = require('swagger-model-validator')
var validator = new Validator(swaggerObject)
var validation = validator.swagger.validateModel('subData', body,
false, true)
if (validation.valid) {
// logic here
} else {
// show error
}
User will call this API like PUT /updateuser/{userId} and body parameter
subData
[{
"userId": "DSHS333FHFHD",
"productId": "465454445",
},
"userId": "RYY48433FHFHD",
"productId": "435654125",
}
]
But the problem is, it is not validating array of objects userDataInput, I have given productId and subscriptionId required field, if I omit productId it's not giving any error and also if put some extra field for ex productId1 it's not validating also. Any help.
I think the problem is in the Swagger Object you are creating. You might be better off using the validator directly and passing in your models manually.
validator.validate(object, swaggerModel, swaggerModels, allowBlankTarget, disallowExtraProperties);
You appear to be passing in the Swagger Definition and not the swagger client which generally has already decoded your definition.
The swaggerModel would be your subData schema section and the swaggerModels would be your definitions section.

Mongoose JS findOne always returns null

I've been fighting with trying to get Mongoose to return data from my local MongoDB instance; I can run the same command in the MongoDB shell and I get results back. I have found a post on stackoverflow that talks about the exact problem I'm having here; I've followed the answers on this post but I still can't seem to get it working. I created a simple project to try and get something simple working and here's the code.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = new Schema({
userId: Number,
email: String,
password: String,
firstName: String,
lastName: String,
addresses: [
{
addressTypeId: Number,
address: String,
address2: String,
city: String,
state: String,
zipCode: String
}
],
website: String,
isEmailConfirmed: { type: Boolean, default: false },
isActive: { type: Boolean, default: true },
isLocked: { type: Boolean, default: false },
roles: [{ roleName: String }],
claims: [{ claimName: String, claimValue: String }]
});
var db = mongoose.connect('mongodb://127.0.0.1:27017/personalweb');
var userModel = mongoose.model('user', userSchema);
userModel.findOne({ email: 'test#test.com' }, function (error, user) {
console.log("Error: " + error);
console.log("User: " + user);
});
And here is the response of the 2 console.log statements:
Error: null
User: null
When the connect method is called I see the connection being made to my Mongo instance but when the findOne command is issued nothing appears to happen. If I run the same command through the MongoDB shell I get the user record returned to me. Is there anything I'm doing wrong?
Thanks in advance.
Mongoose pluralizes the name of the model as it considers this good practice for a "collection" of things to be a pluralized name. This means that what you are currently looking for in code it a collection called "users" and not "user" as you might expect.
You can override this default behavior by specifying the specific name for the collection you want in the model definition:
var userModel = mongoose.model('user', userSchema, 'user');
The third argument there is the collection name to be used rather than what will be determined based on the model name.
I know 7 years pass, however I'm starting to develop in node JS with MongoDB using mongoose, so searching for solution to the same problem, result = null.
After read this post and all the answers, I release that I totally forget to include the DB name in the string of the connection, 'mongodb://localhost:27017/DB name' so that solved my case. I guessed this can be help other clueless like me! :)

Categories