#100DaysOfMERN - Day 33

Subscribe to my newsletter and never miss my upcoming articles

✏ 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

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

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

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 subsribe to my newsletter. Until next time 👋


✏ Previous Posts

You can find an overview of all previous posts with tags and tag search here:

#100DaysOfMERN - The App

No Comments Yet