✏ How to deploy to Heroku (with MongoDB connection)
Yesterday, I successfully deployed my #100DaysOfMERN App, with one small issue - it was actually just an ERN App, because it was missing a connection to MongoDB. I have since corrected this little flaw, so here's how to
connect to a MongoDB Atlas cluster
serve the data
deploy the App to Heroku
✏ MongoDB Atlas
You'll first need an account at MongoDB Atlas (alternatively, you can log in with your google account).
After logging in, you should see a button "create a cluster" or "build a cluster" somewhere. You're then asked if you'd like to use the free option, or if you prefer to pay... do what you like, but for a private project, the free option is perfectly fine.
Next, you can pick a provider and region. I don't think it really matters what you pick, so you can just keep the default options. Optionally, you can also give your cluster a name. Finally, click "create cluster" at the bottom of the page. This might take a couple of minutes.
Once it's done, you'll see an overview screen. Find the button "collections" and click it! Now, you can create a new database, and within it, you need at least one collection (can be empty).
Creating a User
Next, you need to create a user and set their permissions (button "Database Access" on left side panel). The name and password of this user are what you'll need later in your code to access the database from your backend.
Click on "Add New Database User" and enter a name and password. Next, set the User Privileges. This user will be you, reading and writing to your own database, so you want to give yourself permission to "read and write to any database".
The Connection String
Once that Admin User is created, go back to "Clusters" and click on "Connect". Next, pick the option "Connect your Application". You will be shown the connection string, so copy and paste that into your code. The syntax is something like this:
mongodb+srv://jsdisco:<password>@jsdisco.1234x.mongodb.net/<database-name>?retryWrites=true
You'll have to put your own database name and password within the brackets.
IMPORTANT: The connection string contains the admin login details, so you never want to show that string anywhere in your code. It should instead be kept as an environment variable in a git-ignored
.env
file (when deploying to Heroku, you'll have to set it using the Heroku CLI - see below)
✏ Creating a .env
file
The .env
file (no filename, just the extension) sits in your root directory. It contains globally available variables like the connection string to the database, which you can then use in your code without actually exposing the value.
.env bash MONGODB_URL = mongodb+srv://jsdisco:<password>@jsdisco.1234x.mongodb.net/<database-name>?retryWrites=true
To access this variable from your code, use Node's process.env
:
const connStr = process.env.MONGODB_URL;
This won't work yet though, you need a package called dotenv
:
npm install dotenv
Require and use .config()
:
server.js js const dotenv = require('dotenv'); dotenv.config();
This should be done at the top of the file, to make sure that all environment variables are loaded before you try to access them anywhere.
(Read more: An Introduction to Environment Variables)
✏ Connecting to the Database
I'll put this in its own connect.js
file, to keep it separate from the server code. First, install mongoose
, and also import the Model for your collection. In my case, that's the Model for a blog post, to write to and read from the posts
collection.
connect.js
const mongoose = require('mongoose');
const Post = require('../models/postmodel.js');
// grab the connection string from the .env
const connStr = process.env.MONGODB_URL;
// avoid deprecation warnings
const connOptions = {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
};
async function connDB(){ try {
const conn = await mongoose.connect(connStr, connOptions)
console.log('Connected to Database')
} catch(error){ console.error(Error in connect.js: ${error.message}); } }
// export the function and import in server.js
module.exports = connDB
✏ The Server
First the setup:
const dotenv = require('dotenv');
dotenv.config();
const express = require('express');
const path = require('path');
const mongoose = require('mongoose');
const Post = require('./models/postmodel.js');
const connDB = require('./db/connect.js');
// establish connection to DB
connDB();
// start server
const app = express();
app.use(express.json());
/* put your routes here */
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => console.log(`server running on port ${PORT}`))
Now for the route to my API, where the frontend fetches the data from:
app.get('/api/posts', (req, res) => {
// if query string is attached to URI
if (req.query.tag){
// find only the posts where the query string
// is included in the post's tags array
Post.find({ 'tags': { $in: req.query.tag }})
// sort ascending by the publication date
.sort({ 'published': 1 })
// and send
.then(data => res.json(data))
} else {
// get the whole collection
Post.find({})
.sort({ 'published': 1 })
.then(data => res.json(data))
}
})
✏ Deploying to Heroku
Like yesterday, I'll have to add a route to serve the build from the frontend folder:
server.js js app.use(express.static(path.resolve(__dirname, '../frontend/build')));
The process is the same as deploying without database connection, with the exception that Heroku needs to know the value of the connection string. So, after staging, committing and pushing the files to Heroku, set the connection string like this:
heroku config:set MONGODB_URL='mongodb+srv://<username>:<password>@<username>.1234x.mongodb.net/<dbname>?retryWrites=true'
For me, that didn't work right away. The problem now is that I did a couple of things, trying to fix it, and something obviously worked, but I'm not sure what did the trick... I'd have to revert the steps, deploy again (it takes so long)... But this wasn't the last App with Database access that I'm going to ship to Heroku, so next time I'll keep an exact log of the steps (and update this post accordingly).
Troubleshooting
From the heroku logs, I could tell that the error was thrown in my connDB()
, with message:
Cannot read property 'split' of null
Before setting the connection string with heroku config
, I had gotten a different error:
The 'uri' parameter to 'openUri()' must be a string, got "undefined"
So, obviouly setting something as connection string had worked, but instead of undefined
, I now had null
.
List of things I did/tried:
comment out the
dotenv
part in the server.js file (seems a bit pointless now, thinking about it)setting the env variable locally in my command line:
SET MONGODB_URL='mongodb+srv://<username>:<password>@<username>.1234x.mongodb.net/<dbname>?retryWrites=true'
- setting the env variable with heroku again, but this time using double quotes for the string:
heroku config:set MONGODB_URL="mongodb+srv://<username>:<password>@<username>.1234x.mongodb.net/<dbname>?retryWrites=true"
I guess I'll find out in the future what the problem was. But what an awesome way to end this week - if you made it work once, you know you can make it work again 🎉
✏ Resources
Guide for using MongoDB and deploying to Heroku
An Introduction to Environment Variables
✏ Recap
I've learned
how to connect to a MongoDB Atlas Cluster from my backend
how to deploy to Heroku (database connection included)
how to set
.env
variables for Heroku
✏ Thanks for reading!
I do my best to thoroughly research the things I learn, but if you find any errors or have additions, please leave a comment below, or @ me on Twitter. If you liked this post, I invite you to subscribe to my newsletter. Until next time 👋
✏ Previous Posts
You can find an overview of all previous posts with tags and tag search here: