mongoose post.save failing on heroku, works on localhost
After reading your question carefully I have a couple of suggestions that I think you might want to try. First, let me explain myself:
In my experience, when one learns how to build REST APIs with Node.js, using mongoose to communicate with a MongoDB cluster, this is what the sequence of events of their index.js
file looks like:
- Execute a function that uses environment variables to establish a connection with the database cluster. This function runs only once and fills de mongoose instance that the app is going to use with whatever models that have been designed for it.
- Set up the app by requiring the appropriate package, defining whatever middleware it's going to require, and calling to all the routes that have been defined for it.
- After the app has incorporated everything it's going to need in order to run properly, app.listen() is invoked, a valid port is provided and... voila! You've got your server running.
When the app and the databsae are simple enough, this works like a charm. I have built several services using these very same steps and brought them to production without noticing any miscommunication between mongoose and the app. But, if you check the App.js code you provided, you'll realize that there is a difference in your case: you only connect to your Mongo cluster after you set up your app, its middleware, and its routes. If any of those depend on the mongoose instance or the models to run, there is a good chance that by the time Heroku's compiler gets to the point where your routes need to connect to your database, it simply hasn't implemented that connection (meaning hasn't run the mongoose.connect() part) jet.
Simple solution
I think that Heroku is simply taking a little bit longer to run your whole code than your local machine. Also, if your local version is connecting to a locally stored MongoDB, there is a good chance things run quite quicker than in a cloud service. Your statement
the 'connected to DB!' message comes in a bit late
gives me hope that this might be the case. If that were so, then I guess that moving
mongoose.connect("mongodb+srv://xxxxx:xxxxx@cluster-rest.4luv0.mongodb.net/cluster-rest?retryWrites=true&w=majority", { useNewUrlParser: true, useUnifiedTopology: true, dbName: "cluster-rest" }, () => {console.log('connected to DB!')});
to the second line of App.js
, right after you call Dotenv, would do the trick. Your mongoose instance would go into App already connected with the database and models that App is going to use.
Asynchronous solution
Even if what I previously wrote fixed your problem, I would like to expand the explanation because, in my experience, that version of the solution wouldn't be right either. The way of doing things that I exposed at the beginning of this answer is ok... As long as you keep your MongoDB cluster and your mongoose instance simple. Not very long ago I had to implement a service that involved a much more complex database pattern, with several different databases being used by the same App, and many of its routes depending directly on those databases' models.
When I tried to use the logic I described before with such a problem, involving heavily populated databases and several models contained in the mongoose instance, I realized that it took Node far less building the App, its middleware, and its routes than connecting to mongoose and loading all the models that that very same App required for it to run. Meaning that I got my Server connected to PORT ****
message before the database was actually connected. And that caused trouble.
That made me realize that the only way to do things properly was to ensure asynchronicity, forcing App to run only after all the databases and the models were loaded into the mongoose instance, and feeding it that very same instance. This way, I ended up having an index.js
that looked like this:
const Dotenv = require("dotenv").config();const { connectMongoose } = require("./server/db/mongoose");const wrappedApp = require("./app");connectMongoose().then((mongoose) => { const App = wrappedApp(mongoose); App.listen(process.env.PORT, () => { console.log( `Hello, I'm your server and I'm working on port ${process.env.PORT}` ); });});
A file with several functions govering the connections to my databases and modeles called mongoose.js
. The function that implements all the connections in the right order looks is:
const connectMongoose = async (mongoose = require("mongoose")) => { try { // Close any previous connections await mongoose.disconnect(); // Connect the Main database console.log( "--> Loading databases and models from MongoDB. Please don't use our server jet. <--" ); mongoose = await connectMother(mongoose); // Connect all the client databases mongoose = await connectChildren(mongoose); console.log( "--> All databases and models loaded correctly. You can work now. <--" ); return mongoose; } catch (e) { console.error(e); }};module.exports = { connectMongoose };
With this, the file app.js
returns a function that needs to be fed a mongoose instance and sets up all the Express environment:
module.exports = (Mongoose) => { const Express = require("express"); //Enrouting // Other required packages go here // EXPRESS SETTINGS const App = Express(); App.set("port", process.env.PORT || 3000); //Use port from cloud server // All the middleware App needs App.use(Flash()); // Routing: load your routes // Return fully formed App return App;};
Only using this pattern I made sure that the chain of events made sense:
- Connect all the databases and load their models.
- Only after the last database has been connected and its last model, loaded, set up the App, its middleware, and your routes.
- Use App.listen() to serve the App.
If your database isn't very large, then the first part of the answer might do the trick. But it's always useful to know the whole story.
I am able to reach an empty error message with your code when i add wrong connection string.
console.log in this callback is incorrect:
mongoose.connect("mongodb+srv://xxxxx:xxxxx@cluster-rest.4luv0.mongodb.net/cluster-rest?retryWrites=true&w=majority", { useNewUrlParser: true, useUnifiedTopology: true, dbName: "cluster-rest" }, () => { console.log('connected to DB!')})
if you wanna know if you are connected to db use this:
mongoose.connect("mongodb+srv://xxxxx:xxxxx@cluster-rest.4luv0.mongodb.net/cluster-rest?retryWrites=true&w=majority", { useNewUrlParser: true, useUnifiedTopology: true, dbName: "cluster-rest" });const db = mongoose.connection;db.once('open', () => { console.log('connection opened')});
I noticed you are using dotenv maybe you are not adding variables to your heroku host.