In the previous article, I demonstrated how to set up Nunjucks template engine in your express project. I decided to make this a full-fledged web app development series of articles by progressively building the example application. In this article, we are going to connect the app to a MongoDB database using Mongoose.
Mongoose is an ODM (Object Document Mapper) that allows interaction with MongoDB databases using JavaScript objects.
It provides extra functionality (such as static methods on the schema) that allows us to enhance database interactions and write cleaner code.
At the time of writing this article, the latest stable version of Mongoose is v5.11.8. This will most likely be different at the time of reading although most of the information here should still be relevant.
Makes sure you have a MongoDB server installed and running on your system before following along. If not, you can sign up for a free cluster at MongoDB Atlas and connect to that instead.
Mongoose setup
Install Mongoose and dotenv using the following command:
npm install mongoose dotenv
Dotenv allows us to load environment variables into our application. We are going to place the MongoDB URI in an environment variable file instead of hard-coding it.
Doing this allows us to connect to different MongoDB instances in different environments by only changing this URI in the environment variable without changing the code itself.
Create a file called .env
in the root of your project. The contents of the files should be as follows:
PORT=8000
MONGO_URI=mongodb://localhost:27017/app
We’ve defined the port here along with the MongoDB URI. make sure to change the values according to your setup.
Now go back to your index.js file (or the file in which your app instance is initialised) and add the following line at the beginning of the file:
if (process.env.ENV === 'dev') require('dotenv').config()
This loads the .env file in our project if we’re in the development environment. We can access each environment variable using process.env.<variable\_name>
.
The dotenv package will look for the .env file in our project when the config method is invoked.
Placing this at the top of the entry point file ensures the environment variables are available to the entire application when we decide to go with a modular approach with our route organisation.
Now import mongoose:
const mongoose = require('mongoose')
Create a mongoose connection by inserting the following code before the route definitions:
const connection = mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
/* Display message in the console if the connection is successful. */
mongoose.connection.once('open', () => {
console.log('connected!')
})
Models
Our mongoose connection has been established. The next step is to define our models. Models are object representations of the documents that will reside in our database.
Models in mongoose require a schema. A schema specifies the structure of the document.
If you’re familiar with NoSQL databases, particularly MongoDB, you might be aware that one of the benefits is that the schema is dynamic. Meaning you can add new fields to a document on the fly upon creation/update.
This may be a good idea depending on your use case but mongoose requires schemas in order to define the shape of the documents in the collection. This ensures we have consistency in a collection and a reference point for what properties are contained in each document.
Let’s begin setting up our models by creating a folder in the root of our project named ‘model’. Next, create a file inside this folder called ‘User.js’. It’s a good idea to separate models into their own files.
Inside User.js, add the following code:
const { Schema, model } = require('mongoose')
var userSchema = new Schema({
name: {
type: Schema.Types.String,
required: [true, 'You must provide a name']
},
email: {
type: Schema.Types.String,
required: [true, 'Email address is required']
},
username: {
type: Schema.Types.String,
required: [true, 'Username is required']
},
password: {
type: Schema.Types.String,
required: [true, 'You must provide a password']
}
})
const User = model('User', userSchema)
module.exports = User
Let’s walk through the contents of this file:
- Import Schema and model from mongoose.
- Create a schema instance that defines the structure of the user document in the User collection.
- Create a model instance and pass it the collection name and schema.
- Export the user model for use in routes.
Now create an index file within the models directory. This file will import all of the models from its sibling files and export them in an object. We’re doing this to reduce the number of require statements in other files when importing models.
You can certainly import models directly from their respective files, but this is definitely a cleaner way of doing it.
The contents of this index.js file should look like this for now:
const User = require('./User')
module.exports = {
User
}
Using the models
It’s time to test if this works. We’re going to insert a user into the collection if the collection is empty and retrieve the users in the collection otherwise.
In the app entry file, import the User model from the models index file as follows:
// Import models
const { User } = require('./models')
Update the home route to the following:
app.get('/', async (req, res) => {
const users = await User.find({})
if (users.length) {
/* Log users if users exists. */
console.log(users)
} else {
/* If no users exist, save new user and log saved user on the console. */
let newUser = new User({
name: 'Kelvin Mwinuka',
email: 'email@kelvinmwinuka.com',
username: 'kelvin',
password: 'password'
})
let savedUser = await newUser.save()
console.log(savedUser)
}
res.render('home.html')
})
Navigate to this route in the browser and you should notice that for the first time, a single object is printed to the console:
{
_id: 5fdab492561efb3e9a2c56c7,
name: 'Kelvin Mwinuka',
email: 'email@kelvinmwinuka.com',
username: 'kelvin',
password: 'password',
__v: 0
}
When you refresh the page, the results should now be as follows:
[
{
_id: 5fdab492561efb3e9a2c56c7,
name: 'Kelvin Mwinuka',
email: 'email@kelvinmwinuka.com',
username: 'kelvin',
password: 'password',
__v: 0
}
]
Notice this is an array of current documents and no new user is created/saved.
That’s it. We’ve successfully set up mongoose and we’re ready to start persisting data in our MongoDB database!
Conclusion
In this article, we’ve gone over connecting our express application to a MongoDB database, creating mongoose models and using those models to save data into our database.
In the next article, I will be going over user registration and authentication using Passport JS.
You can track the progress of this project on Github.