Model.save() not saving to Atlas

Hi Everyone,

I hope you are well!

At this point of my nodejs, express and mongoose app I have fairly simple routes, views, models and controllers. I have a story_points_create_post route that when called from Postman does call the code. I’ve been able to trace (using a debugger) that the model.save does not save and never returns a response to resolve the post.

Hrere is the story_points_create_post route in project/routes:

import express from 'express';
const router = express.Router();
import {story_point_get, story_points_create_post,
index, story_points_delete_post} from '../controllers/story_point_controller.js'

router.get("/", index)

.get(
    "/story_points/create",
    story_point_get
)
// POST request for creating Story Points.
.post(
    "/story_points/create", 
    story_points_create_post
)

// POST request to delete story points.
.post(
    "/story_points/:id/delete", 
    story_points_delete_post
);

export {router as catalogRouter};

Here is the controller:

export const story_points_create_post = async (req, res) => {
    console.log('made it within the post route')
    console.log(req.body)
    let storyPoints = new StoryPoints({
        firstName: req.body.firstName,
        lastName: req.body.lastName,
        team: req.body.team,
        storyPoints: req.body.storyPoints,
        sprint: {
            name: req.body.sprint.name,
            startDate: req.body.sprint.startDate,
            endDate: req.body.sprint.endDate
        }
    })
   
    try {
        res.send(await storyPoints.save()) 
    } catch(e) {
        console.log(e)
    }
    
};

Notice the try/catch block with res.send(await storyPoints.save()) this never resolves. I did comment this out and returned the storyPoints model res.send(storyPoints) and got the model looking correct with all required fields present, here was the output:

{
    "firstName": "Joe",
    "lastName": "Smoe",
    "team": "Data Science",
    "storyPoints": 3,
    "sprint": {
        "name": "My first Sprint",
        "startDate": "2023-10-18T20:23:23.000Z",
        "endDate": "2023-10-18T20:24:09.000Z"
    },
    "_id": "6540057560a4fde45b3cc521"
}

The call to mode.save() eventually times out with a 500 error however it is not caught by the try/catch ( maybe do the async call?

In atlas right now here are some example document:

{"_id":{"$oid":"6531798a38ae95fe7e56c3cc"},"firstName":"Joe","lastName":"Smoe","team":"Data Science","storyPoints":{"$numberInt":"15"},"sprint":{"name":"My first Sprint","startDate":{"$date":{"$numberLong":"1697660603000"}},"endDate":{"$date":{"$numberLong":"1697660649000"}}},"createdAt":{"$date":{"$numberLong":"1697741194091"}},"updatedAt":{"$date":{"$numberLong":"1697741194091"}},"__v":{"$numberInt":"0"}}

Here is the storyPoint model:

/ Require Mongoose
import mongoose from "mongoose";

// Define a schema
const Schema = mongoose.Schema;
/**
 * Story Point Report Schema
 * The timestamps used in this way create updatedAt and createdAt automatically
 */
const storyPointsSchema = new Schema({
    firstName: { 
        required: true, 
        type: String, 
        maxLength: 100
    },
    lastName: {
        required: true, 
        type: String, 
        maxLength: 100
    },
    team: {
        required: true, 
        type: String}
    ,
    storyPoints: {
        required: true,
        type: Number
    },
    sprint: {
        name: {
            required: true,
            type: String
        },
        startDate: {
            required: true,
            type: Date, 
        },
        endDate: {
            required: true,
            type: Date,
        }
    }
  
}, {timestamps: true });

storyPointsSchema.pre('save', function(next){
    this.spintStartDate = Date.now();
    this.sprintEndDate = Date.now() + 14;
})

/**
 * Calculate Full Name
 * Virtual for ticket assignee's full name
 */
storyPointsSchema.virtual('name').get(() => {
    let fullname = ""
    if (this.firstName && this.lastName) {
        fullname = `${this.firstName}, ${this.lastName}`
    }
    return fullname
})

function convertDateToReadableFormat(Date) {
    return  DateTime.formJSDate(Date).toLocaleString(DateTime.DATE_MED);
}

storyPointsSchema.virtual('prety_dates').get(() => {
   return {
        sprintStartDateF: convertDateToReadableFormat(this.spintStartDate),
        sprintEndDateF: convertDateToReadableFormat(this.spintEndDate),
        updatedAtF:  convertDateToReadableFormat(this.updatedAt)
    }
})

export const StoryPoints =  mongoose.model('StoryPoints', storyPointsSchema)

Here is the atlas db connection which when the app is running reports Connected to MongoDB:

import mongoose from 'mongoose';
import * as dotenv from 'dotenv';
dotenv.config()

// Set `strictQuery: false` to globally opt into filtering by properties that aren't in the schema
// Included because it removes preparatory warnings for Mongoose 7.
// See: https://mongoosejs.com/docs/migrating_to_6.html#strictquery-is-removed-and-replaced-by-strict
mongoose.set("strictQuery", false);

const mongoDBConnect = async () => {
  try {
    await mongoose.connect(process.env.DATABASE_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
    console.log('Connected to MongoDB');
    return 'Connected to MongoDB'
  } catch (error) {
    console.log('Error connecting to MongoDB:', error);
    return JSON.stringify(`Error connecting to MongoDB: ${error}`)
  }
 };

 export const connectDB =  mongoDBConnect;

And finally here is the app:

import createError from 'http-errors';
import express from 'express';
import bodyParser from 'body-parser';
import * as path from 'path';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import cookieParser from 'cookie-parser';
import logger from 'morgan';

/**
 * Database - MongoDB using Mongoose ORM
 */
import {connectDB} from './datatabase/db.js'
// Connect to MongoDB
connectDB();

import {indexRouter} from './routes/index.js';
import {storyPointsRouter} from './routes/metrics.js';
import {catalogRouter} from './routes/catalog.js';

const __dirname = dirname(fileURLToPath(import.meta.url));
export const app = express();


// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));


app.use('/', indexRouter);
app.use('/storyPoints', storyPointsRouter)
app.use('/metrics', catalogRouter)

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

Can you see any issues with the code or reasons why save() is failing?

Your help is appreciated.

Hey @Steve_Browning,

Welcome to the MongoDB Community forums :sparkles:

I’ll suggest splitting the res.send(await storyPoints.save()) to catch any errors that may occur during the save() process. This will help in debugging the issue.

try {
  const savedStoryPoint = await storyPoints.save();
  res.send(savedStoryPoint);
} catch (err) {
  console.error(err);
  res.status(500).send('Error saving story point'); 
}

In case of further issues, please share the error log message you encounter and the workflow you followed so that the community can assist you better.

Best regards,
Kushagra

Thank you Kushagra!

I did ammend the code like you recommended and found the query never times out ( well at least for 30min before I stopped the query from Postman. It never passes the const savedStoryPoint = await storyPoints.save(); and does not throw an error.

I’m not sure if you’ve seen this type of behaviour before?

Regards,
Steve

Hi Kushagra,

I got the .save() to work.

It turns out that one of the virtual fields I had defined where causing issues with save(). I am going to go one by one with the virtual fields to see which one is causing the issue.

Thank you again for your support.

It turns out referencing a sub document in the Story Points Schema within a virtual could not be find. As a reminder here is the model for StoryPoints:

const storyPointsSchema = new Schema({
    firstName: { 
        required: true, 
        type: String, 
        maxLength: 100
    },
    lastName: {
        required: true, 
        type: String, 
        maxLength: 100
    },
    team: {
        required: true, 
        type: String}
    ,
    storyPoints: {
        required: true,
        type: Number
    },
    sprint: {
        name: {
            required: true,
            type: String
        },
        startDate: {
            required: true,
            type: Date, 
        },
        endDate: {
            required: true,
            type: Date,
        }
    }
  
}, {timestamps: true });

function convertDateToReadableFormat(Date) {
    return  DateTime.formJSDate(Date).toLocaleString(DateTime.DATE_MED);
}

storyPointsSchema.virtual('prety_dates').get(() => {
   return {
        sprintStartDateF: convertDateToReadableFormat(this.sprint.startDate),
        sprintEndDateF: convertDateToReadableFormat(this.sprint.endDate),
        updatedAtF:  convertDateToReadableFormat(this.updatedAt)
    }
})

I get and error that sprint is undefined:

TypeError: Cannot read properties of undefined (reading ‘sprint’)

As you can see my Sprint properties of name, startDate and endDate are not assessable in the virtual field. Does this need populate?

Regards,
Steve

Well I resolved every issue with virtuals and it all works now.

Here is the fixed model:

// Require Mongoose
import mongoose from "mongoose";

// Define a schema
const Schema = mongoose.Schema;
/**
 * Story Point Report Schema
 * The timestamps used in this way create updatedAt and createdAt automatically
 */
const storyPointsSchema = new Schema({
    firstName: { 
        required: true, 
        type: String, 
        maxLength: 100
    },
    lastName: {
        required: true, 
        type: String, 
        maxLength: 100
    },
    team: {
        required: true, 
        type: String}
    ,
    storyPoints: {
        required: true,
        type: Number
    },
    sprint: {
        name: {
            required: true,
            type: String
        },
        startDate: {
            required: true,
            type: Date, 
        },
        endDate: {
            required: true,
            type: Date,
        }
    }
  
}, {timestamps: true });

// storyPointsSchema.pre('save', function(next){
//     this.spintStartDate = Date.now();
//     this.sprintEndDate = Date.now() + 14;
// })

/**
 * Calculate Full Name
 * Virtual for ticket assignee's full name
 */
storyPointsSchema.virtual('fullname').get(function () {
    let fullname = ""
    if (this.firstName && this.lastName) {
        fullname = `${this.firstName}, ${this.lastName}`
    }
    return fullname
})

// Virtual for author's URL
storyPointsSchema.virtual("url").get(function () {
    // We don't use an arrow function as we'll need the this object
    return `/story_points/${this._id}`;
});

function convertDateToReadableFormat(date) {
    let year = date.getFullYear();
    let month = date.getMonth()+1;
    let dt = date.getDate();

    if (dt < 10) {
    dt = '0' + dt;
    }
    if (month < 10) {
    month = '0' + month;
    }

    return year+'-' + month + '-'+dt;
}

storyPointsSchema.virtual('prety_dates').get(function () {
   return {
        sprintStartDateF: convertDateToReadableFormat(this.sprint.startDate),
        sprintEndDateF: convertDateToReadableFormat(this.sprint.endDate),
        updatedAtF:  convertDateToReadableFormat(this.updatedAt)
    }
})

export const StoryPoints =  mongoose.model('StoryPoints', storyPointsSchema)

And this is the output from console.log():

{
  firstName: 'Joe',
  lastName: 'Smoe',
  team: 'Data Science',
  storyPoints: 3,
  sprint: {
    name: 'My first Sprint',
    startDate: 2023-10-18T20:23:23.000Z,
    endDate: 2023-10-18T20:24:09.000Z
  },
  _id: new ObjectId("654161f226ab0ec909faf1cd"),
  createdAt: 2023-10-31T20:22:10.974Z,
  updatedAt: 2023-10-31T20:22:10.974Z,
  __v: 0,
  fullname: 'Joe, Smoe',
  url: '/story_points/654161f226ab0ec909faf1cd',
  prety_dates: {
    sprintStartDateF: '2023-10-18',
    sprintEndDateF: '2023-10-18',
    updatedAtF: '2023-10-31'
  },
  id: '654161f226ab0ec909faf1cd'
}

And here is what is returned to Postman:

{
    "firstName": "Joe",
    "lastName": "Smoe",
    "team": "Data Science",
    "storyPoints": 3,
    "sprint": {
        "name": "My first Sprint",
        "startDate": "2023-10-18T20:23:23.000Z",
        "endDate": "2023-10-18T20:24:09.000Z"
    },
    "_id": "654161f226ab0ec909faf1cd",
    "createdAt": "2023-10-31T20:22:10.974Z",
    "updatedAt": "2023-10-31T20:22:10.974Z",
    "__v": 0
}

I believe this thread can be closed.

1 Like